darkNet YOLOv4 + labelme 目標(biāo)檢測(cè)任務(wù)半自動(dòng)標(biāo)注

閑話:標(biāo)注數(shù)據(jù)一直都是深度學(xué)習(xí)中代價(jià)非常大的工作,而重復(fù)勞動(dòng)對(duì)人來說又是極痛苦的。做了幾個(gè)目標(biāo)檢測(cè)的項(xiàng)目后一直想要做一個(gè)半自動(dòng)標(biāo)注的工具卓囚,但是對(duì)GUI類界面從設(shè)計(jì)到功能感覺工作量還是挺大的幸海,之前也沒有多少經(jīng)驗(yàn)。突然想到菩貌,為什么一定得自己做一個(gè)呢,把檢測(cè)到的結(jié)果轉(zhuǎn)換成labelme格式的json文件重荠,用labelme來對(duì)結(jié)果進(jìn)行修改不是很好嗎箭阶?本著這樣的想法于是就有了下面的內(nèi)容,這也省掉了非常非常多的精力戈鲁,事情也變得簡(jiǎn)單了仇参。

摘要:

本篇文章針對(duì)的是darkNet YOLOv4目標(biāo)檢測(cè)類的任務(wù)的數(shù)據(jù)半自動(dòng)標(biāo)注問題,具體的流程就是:
1.先手動(dòng)標(biāo)注小批量數(shù)據(jù)訓(xùn)練模型婆殿;
2.用模型對(duì)另一小批數(shù)據(jù)進(jìn)行預(yù)測(cè)诈乒;
3.把檢測(cè)的結(jié)果轉(zhuǎn)換成labelme格式的json文件,用labelme打開進(jìn)行調(diào)整修改婆芦;
4.修改后的數(shù)據(jù)加入訓(xùn)練集怕磨,訓(xùn)練模型喂饥;
5.數(shù)據(jù)量足夠則結(jié)束,否則回到第2步肠鲫。

@[toc]

1. darkNet 讀取圖片預(yù)測(cè)

1.1 打包darknet

darknet配置可參考link
這里采用把項(xiàng)目darkNet框架導(dǎo)出為dll文件調(diào)用的方式员帮,這樣可以是程序變得精簡(jiǎn)有條理。用vs編譯下面的yolo_cpp_dll即可导饲。

在這里插入圖片描述

1.2 配置新項(xiàng)目

  • opencv配置包含目錄捞高,庫(kù)目錄


    在這里插入圖片描述
  • 連接器--->附加依賴項(xiàng)


    在這里插入圖片描述
  • 項(xiàng)目源文件處添加三個(gè)文件
    darknet.h,yolo_v2_class.hpp是darknet項(xiàng)目中的文件
    yolo_cpp_dll.lib由yolo_cpp_dll.sln項(xiàng)目編譯生成


    在這里插入圖片描述
  • 項(xiàng)目exe路徑處添加兩個(gè)dll文件
    yolo_cpp_dll.dll由yolo_cpp_dll.sln項(xiàng)目編譯生成;
    pthreadVC2.dll是darkNet的依賴項(xiàng)帜消,在darkNet項(xiàng)目中的darknet\build\darknet\x64路徑下


    在這里插入圖片描述

2. 預(yù)測(cè)結(jié)果轉(zhuǎn)換為labelme格式

2.1 說明

darknet yolo檢測(cè)出來的結(jié)果是用std::vector<bboxt> 格式存儲(chǔ)的棠枉,bbox_t是結(jié)構(gòu)體,在yolo_v2_class.hpp中定義如下:

struct bbox_t {
    unsigned int x, y, w, h;       // (x,y) - top-left corner, (w, h) - width & height of bounded box
    float prob;                    // confidence - probability that the object was found correctly
    unsigned int obj_id;           // class of object - from range [0, classes-1]
    unsigned int track_id;         // tracking id for video (0 - untracked, 1 - inf - tracked object)
    unsigned int frames_counter;   // counter of frames on which the object was detected
    float x_3d, y_3d, z_3d;        // center of object (in Meters) if ZED 3D Camera is used
};

labelme中標(biāo)注類型為rectangle類型時(shí)的標(biāo)簽文件內(nèi)容如下泡挺,
[圖片上傳失敗...(image-6ca85c-1625645408509)]

2.2 轉(zhuǎn)換函數(shù)

int resultWriteToJson(const std::string jsonPath, const std::string imagePath, const int imgH, const int imgW, const std::vector<bbox_t> &result)
{   
    //input
    //jsonPath:      json file  abspath,
    //imagePath:     labelme contain the image path,(write to json)

    std::ofstream out(jsonPath, std::ios::out);//std::ios::app add to  bottom of the file
    if (!out.is_open())
    {
        std::cout << "cant open the " << jsonPath << "!\n";
        return -1;
    }

    // write the json table head 
    out << "{\n" << "\"version\":\"4.5.7\",\n";
    out << "\"flags\" : {},\n";
    out << "\"shapes\" : [\n";

    //
    for (int i = 0; i < result.size(); i++)
    {
        bbox_t box = result[i];
        out << "{\n";
        out << "\"label\":" << "\"" << box.obj_id << "\",\n";
        out << "\"points\":[\n";
        out << "[\n" << box.x << ",\n" << box.y << "\n],\n";
        out << "[\n" << box.x + box.w << ",\n" << box.y + box.h << "\n]\n";
        out << "],\n";
        out << "\"group_id\":null,\n";
        out << "\"shape_type\":\"rectangle\",\n";
        out << "\"flags\":{}\n";
        out << "}";
        if (i != result.size() - 1) out << ",\n";//最后一個(gè)}后面沒有逗號(hào)","

    }
    out << "],\n";
    out << "\"imagePath\" :" << "\"" << imagePath << "\",\n";
    out << "\"imageData\" :" << "null,\n";
    out << "\"imageHeight\":" << imgH << ",\n";
    out << "\"imageWidth\":" << imgW << "\n";
    out << "}\n";

    out.close();
    return 0;
}

2.3 轉(zhuǎn)換示例

用模型讀取一張圖片預(yù)測(cè)辈讶,把結(jié)果轉(zhuǎn)為labelme格式如下圖,與2.1中的手動(dòng)標(biāo)注的文件比較可以發(fā)現(xiàn)娄猫,除了格式?jīng)]有縮進(jìn)外贱除,其他內(nèi)容都是一樣的了(預(yù)測(cè)的位置和手動(dòng)標(biāo)注的位置有差別是正常的),用labelme是可以讀取的媳溺。


在這里插入圖片描述

3. 完整源碼

函數(shù)說明:

  • selectResults 此函數(shù)刪除邊界上的結(jié)果
  • drawResults 此函數(shù)可視化檢測(cè)結(jié)果
  • demo1 此函數(shù)展示預(yù)測(cè)一張圖片月幌,顯示結(jié)果,保存結(jié)果為labelme格式
  • demo2 對(duì)一個(gè)文件夾中的圖片批量預(yù)測(cè)并顯示結(jié)果
  • demo3 對(duì)一個(gè)文件夾中的圖片批量預(yù)測(cè)并保存為labelme格式


    在這里插入圖片描述
#include <iostream>
#include "yolo_v2_class.hpp"    // imported functions from DLL
#include "opencv.hpp"

int  drawResults(cv::Mat img, std::vector<bbox_t> &results)
{
    if (img.empty())
    {
        std::cout << "drawResults: the image is empty\n";
        return -1;
    }
    if (results.empty())
    {
        std::cout << "drawResults: the results vector is empty\n";
        return -1;
    }
    int img_w = img.cols;
    int img_h = img.rows;
    int expd = 10;
    for (auto &r : results)
    {
        if (int(r.x) - expd <= 0 | int(r.x) + r.w + expd >= img_w | int(r.y) - expd <= 0 | int(r.y) + r.h + expd >= img_h) continue;
        cv::rectangle(img, cv::Rect(r.x, r.y, r.w, r.h), cv::Scalar(0, 255, 255), 2);
        std::string className = std::to_string(r.obj_id);
        putText(img, className, cv::Point2f(r.x, r.y - 5), cv::FONT_HERSHEY_COMPLEX_SMALL, 2, cv::Scalar(0, 0, 255), 5);
        std::cout << "x:" << r.x << " ,y:" << r.y << "w:" << r.w << "h:" << r.h << std::endl;
        /*cv::namedWindow("results", 0);
        cv::imshow("results", img);
        cv::waitKey(0);*/
    }

    cv::namedWindow("results", 0);
    cv::imshow("results", img);
    cv::waitKey(0);
    return 0;
}


std::vector<bbox_t> selectResults(cv::Mat &mat_img, std::vector<bbox_t> &results)
{
    //去除掉檢測(cè)出的在邊界上的結(jié)果
    int img_w = mat_img.cols;
    int img_h = mat_img.rows;
    std::vector<bbox_t> selectedResults;
    int expd = 5;
    for (auto &r : results)
    {
        if (int(r.x) - expd <= 0 | int(r.x) + r.w + expd >= img_w | int(r.y) - expd <= 0 | int(r.y) + r.h + expd >= img_h) continue;
        selectedResults.push_back(r);
    }
    return selectedResults;
}


int resultWriteToJson(const std::string jsonPath, const std::string imagePath, const int imgH, const int imgW, const std::vector<bbox_t> &result)
{   
    //input
    //jsonPath:      json file  abspath,
    //imagePath:     labelme contain the image path,(write to json)

    // a labelme json format annotation
    /*
    {
  "version": "4.5.7",
  "flags": {},
  "shapes": [
    {
      "label": "0",
      "points": [
        [
          1587.25,
          1060.8333333333335
        ],
        [
          1726.8333333333335,
          1221.25
        ]
      ],
      "group_id": null,
      "shape_type": "rectangle",
      "flags": {}
    },
    {
      "label": "1",
      "points": [
        [
          1197.7500000000002,
          1675.5
        ],
        [
          1339.416666666667,
          1810.9166666666665
        ]
      ],
      "group_id": null,
      "shape_type": "rectangle",
      "flags": {}
    }
  ],
  "imagePath": "000000012.bmp",
  "imageData": null,
  "imageHeight": 2000,
  "imageWidth": 2400
}
    */

    std::ofstream out(jsonPath, std::ios::out);//std::ios::app add to  bottom of the file
    if (!out.is_open())
    {
        std::cout << "cant open the " << jsonPath << "!\n";
        return -1;
    }

    // write the json table head 
    out << "{\n" << "\"version\":\"4.5.7\",\n";
    out << "\"flags\" : {},\n";
    out << "\"shapes\" : [\n";

    //
    for (int i = 0; i < result.size(); i++)
    {
        bbox_t box = result[i];
        out << "{\n";
        out << "\"label\":" << "\"" << box.obj_id << "\",\n";
        out << "\"points\":[\n";
        out << "[\n" << box.x << ",\n" << box.y << "\n],\n";
        out << "[\n" << box.x + box.w << ",\n" << box.y + box.h << "\n]\n";
        out << "],\n";
        out << "\"group_id\":null,\n";
        out << "\"shape_type\":\"rectangle\",\n";
        out << "\"flags\":{}\n";
        out << "}";
        if (i != result.size() - 1) out << ",\n";//最后一個(gè)}后面沒有逗號(hào)","

    }
    out << "],\n";
    out << "\"imagePath\" :" << "\"" << imagePath << "\",\n";
    out << "\"imageData\" :" << "null,\n";
    out << "\"imageHeight\":" << imgH << ",\n";
    out << "\"imageWidth\":" << imgW << "\n";
    out << "}\n";

    out.close();
    return 0;
}


 int  demo1()
{
    std::string rootPath = "D:/mydoc/VS-proj/SMTDetector/x64/Release/";
    //label name file path
    std::string  names_file = rootPath + "data/SMTDetector.names";
    //config file path
    std::string  cfg_file = rootPath + "cfg/SMTDetector.cfg";
    //weights file path
    std::string  weights_file = rootPath + "model/SMTDetector.weights";
    //image file path
    //std::string imagePath = rootPath + "data/del/0-5.bmp";
    std::string imagePath = "K:\\imageData\\SMTdataset\\image\\000000001.bmp";

    //init the detector
    Detector detector(cfg_file, weights_file);

    cv::Mat img = cv::imread(imagePath);
    if (img.empty())
    {
        std::cout << "the image is empty\n";
        return -1;
    }

    //detect
    std::vector<bbox_t> results = detector.detect(img);
    results = selectResults(img, results);

    //visualize the results
    drawResults(img, results);

    resultWriteToJson("aaaa.json", "0-1.bmp", img.rows, img.cols, results);

    return 0;
}


 int  demo2()
 {
     std::string rootPath = "D:/mydoc/VS-proj/SMTDetector/x64/Release/";
     //label name file path
     std::string  names_file = rootPath + "data/SMTDetector.names";
     //config file path
     std::string  cfg_file = rootPath + "cfg/SMTDetector.cfg";
     //weights file path
     std::string  weights_file = rootPath + "model/SMTDetector.weights";
     //image file path list
     std::string imageFolder = rootPath + "data/del";

     std::vector<cv::String> imageList;
     cv::glob(imageFolder, imageList);

     //init the detector
     Detector detector(cfg_file, weights_file);

     int num = 0;
     for (auto &r : imageList)
     {
         cv::Mat img = cv::imread(r);
         std::cout << "imagepath:" << r << std::endl;
         if (img.empty())
         {
             std::cout << "the image is empty\n";
             continue;
         }

         //detect
         std::vector<bbox_t> results = detector.detect(img);
         std::vector<bbox_t> ss = selectResults(img, results);
         num += results.size();
         std::cout << "number of thu:" << ss.size() << std::endl;
         //visualize the results
         drawResults(img, ss);
     }

     std::cout << "the total num:" << num << std::endl;

     return 0;
 }


 int  demo3()
 {
     //讀取一個(gè)文件夾中的所有圖片預(yù)測(cè)悬蔽,并把結(jié)果保存到j(luò)son文件中
     std::string rootPath = "K:/model/SMTDetector/";
     //label name file path
     std::string  names_file = rootPath + "names/SMTDetector.names";
     //config file path
     std::string  cfg_file = rootPath + "cfg/SMTDetector.cfg";
     //weights file path
     std::string  weights_file = rootPath + "model/SMTDetector.weights";
     //image file path list
     std::string imageFolder = "K:\\imageData\\SMTdataset\\smi";

     std::vector<cv::String> imageList;
     cv::glob(imageFolder, imageList);

     //init the detector
     Detector detector(cfg_file, weights_file);

     int num = 0;
     for (auto &r : imageList)
     {
         cv::Mat img = cv::imread(r);
         std::cout << "imagepath:" << r << std::endl;
         if (img.empty())
         {
             std::cout << "the image is empty\n";
             continue;
         }

         //detect
         std::vector<bbox_t> results = detector.detect(img);
         results = selectResults(img, results);
         num += results.size();
         //std::cout << "number of thu:" << results.size() << std::endl;

         int index = r.find_last_of("\\");
         std::string imageName = r.substr(index + 1,-1);
         std::string jsonName = imageName.substr(0, imageName.find_last_of(".")) + ".json";
         //std::cout << "json:" << jsonName << "\t image:" << imageName << "\n";
         resultWriteToJson(imageFolder+"\\"+jsonName, imageName, img.rows, img.cols, results);
     }

     std::cout << "the total num:" << num << std::endl;

     return 0;
 }


 int main()
 {
     demo1();
     return 0;
 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扯躺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝎困,更是在濱河造成了極大的恐慌录语,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禾乘,死亡現(xiàn)場(chǎng)離奇詭異澎埠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)始藕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蒲稳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伍派,你說我怎么就攤上這事江耀。” “怎么了诉植?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵祥国,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我倍踪,道長(zhǎng)系宫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任建车,我火速辦了婚禮扩借,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缤至。我一直安慰自己潮罪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布领斥。 她就那樣靜靜地躺著嫉到,像睡著了一般。 火紅的嫁衣襯著肌膚如雪月洛。 梳的紋絲不亂的頭發(fā)上何恶,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音嚼黔,去河邊找鬼细层。 笑死,一個(gè)胖子當(dāng)著我的面吹牛唬涧,可吹牛的內(nèi)容都是我干的疫赎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼碎节,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼捧搞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狮荔,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤胎撇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后轴合,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體创坞,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年受葛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了题涨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡总滩,死狀恐怖纲堵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闰渔,我是刑警寧澤席函,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站冈涧,受9級(jí)特大地震影響茂附,放射性物質(zhì)發(fā)生泄漏正蛙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一营曼、第九天 我趴在偏房一處隱蔽的房頂上張望乒验。 院中可真熱鬧,春花似錦蒂阱、人聲如沸锻全。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳄厌。三九已至,卻和暖如春妈踊,著一層夾襖步出監(jiān)牢的瞬間了嚎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工廊营, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留新思,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓赘风,卻偏偏與公主長(zhǎng)得像夹囚,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邀窃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容