閑話:標(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;
}