最近在做工业产品缺陷检测相关的项目,调研了目前目标检测方向的一些深度神经网络方法,最终确定使用yolov3。
首先到 https://github.com/AlexeyAB/darknet 克隆最新版本的代码。这里面有VS2015的工程,如果安装有vc140的编译工具集,则使用vs2017和vs2019都可以。
注:如果使用高于vs2015的版本,则一定要选择不升级平台工具集,使用默认的vc140。
Vs解决方案共有4个,每个解决方案下面各一个工程。看名字就知道每个工程的作用。
如果要在命令行中使用yolo的网络,选择不带_dll的,如果要在别的程序中调用yolo,选择带dll的。根据是否有gpu选择带不带_no_gpu。
打开后编译配置选择release和x64,配置opencv和cuda(如果选择了gpu版本)的路径。Opencv目前还不支持4.x.x版本。
使用cfg文件夹的yolov3-voc.cfg配置文件,里面有关键的几行需要修改
测试时
batch=1
subdivisions=1
二者必须都是1。
训练时
batch=64
subdivisions=32
batch必须是64,subdivisions可以是1,2,4,8,16,32,64。数字越大,需要的gpu显存就越少。
[yolo]
classes = ?
classes设为要分类的目标种类,voc数据集是20,一共有3处需要修改。
然后把这个[yolo]组的上一组[convolutional]分组的
filters=?
设为(Classes+5)*3,这里需要写计算出的值,不能写表达式。
voc.data和voc.names。这两个文件在build\darknet\x64\data文件夹下。
下载预训练权重文件darknet53.conv.74放到build\darknet\x64目录中
下载voc数据集VOCtrainval_11-May-2012.tar,VOCtrainval_06-Nov-2007.tar,VOCtest_06-Nov-2007.tar,并放到build\darknet\x64\data\voc\VOCdevkit\目录中。
下载python文件voc_label.py 到build\darknet\x64\data\voc目录。
以上需要的文件都放到百度网盘中
链接:https://pan.baidu.com/s/18ZKVJtrs6pUtaq--Ma2sQQ
提取码:xtlp
所有数据准备就绪,并且darknet的非dll版本编译成功,就可以开始模型的训练和测试。
为了方便,需要写几个脚本
训练的脚本 darknet_train_voc.cmd
darknet.exe detector train data/voc.data cfg/yolov3-voc.cfg darknet53.conv.74
pause
恢复训练的脚本 darknet_train_voc_resume.cmd
darknet.exe detector train data/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_last.weights -map
-map参数代表在可视化的图形中显示在验证集上的准确度。
测试的脚本 darknet_test_voc.cmd
darknet.exe detector test data/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_14000.weights test/person.jpg
pause
记得测试前修改yolov3-voc.cfg文件中的
batch=1
subdivisions=1
都为1。
生成训练用的所有图片路径的txt,使用脚本dir.cmd
dir /s /b /oe "E:\WorkSpace\darknet\build\darknet\x64\mydataset\img">"E:\WorkSpace\darknet\build\darknet\x64\mydataset\train.txt"
需要有和每张图片同名的txt文件保存图片中的目标区域和类别。
规则是
类别 目标框中心x相对坐标 目标框中心x相对坐标 目标框相对宽度 目标框相对高度
相对的意思是把图片的总宽度和总长度看做1,等比例缩放的意思。比如如下的形式:
0 0.366406 0.470833 0.028125 0.038889
0 0.403906 0.625694 0.017188 0.031944
这种格式文件的生成有个标注工具可以使用
https://github.com/AlexeyAB/Yolo_mark
为方便执行标注文件,写一个脚本yolo_mark.cmd
yolo_mark.exe E:/WorkSpace-Python/DataAugmentationForObjectDetection/mydataset/img/aug mydataset/train.txt mydataset/myobj.names
pause
参数分别是:需要输入图像的路径;图像的文件名txt,每行一个;目标类别名称文件。
https://github.com/Paperspace/DataAugmentationForObjectDetection
由于使用的坐标系和标注结果定义不一样,需要进行转换,转换的python程序为:
from data_aug.data_aug import *
from data_aug.bbox_util import *
import numpy as np
import cv2
import matplotlib.pyplot as plt
import pickle as pkl
from glob import glob
image_path = './mydataset/img'
file_names=glob(image_path+"/*bmp")
for file_name in file_names:
name = os.path.split(file_name)[1]
path = os.path.split(file_name)[0]
img = cv2.imread(file_name)
bboxesraw = np.loadtxt(path + "/" + name[:-4] + ".txt")
if np.ndim(bboxesraw) == 1:
bboxes = np.expand_dims(bboxesraw, axis=0)
else:
bboxes = bboxesraw
# 对bboxes进行操作
bboxesnew = bboxes.copy()
bboxesnew[:,4] = bboxes[:,0]
bboxesnew[:,0] = (bboxes[:,1] - bboxes[:,3]/2) * img.shape[1]
bboxesnew[:,1] = (bboxes[:,2] - bboxes[:,4]/2) * img.shape[0]
bboxesnew[:,2] = (bboxes[:,1] + bboxes[:,3]/2) * img.shape[1]
bboxesnew[:,3] = (bboxes[:,2] + bboxes[:,4]/2)* img.shape[0]
bboxes = bboxesnew
for i in range(5):
if i == 0: # 翻转
img_, bboxes_ = RandomHorizontalFlip(1)(img.copy(), bboxes.copy())
elif i == 1: #
img_, bboxes_ = RandomScale(0.3, diff = True)(img.copy(), bboxes.copy())
elif i == 2: #
img_, bboxes_ = RandomTranslate(0.3, diff = True)(img.copy(), bboxes.copy())
elif i == 3: #
img_, bboxes_ = RandomRotate(20)(img.copy(), bboxes.copy())
elif i == 4: #
img_, bboxes_ = RandomShear(0.2)(img.copy(), bboxes.copy())
# elif i == 5: #
# img_, bboxes_ = RandomHSV(100, 100, 100)(img.copy(), bboxes.copy())
# 把bboxes改回去
bboxesnew_ = bboxes_.copy()
bboxesnew_[:,0] = bboxes_[:,4]
bboxesnew_[:,1] = (bboxes_[:,2] / img.shape[1] + bboxes_[:,0] / img.shape[1])/2
bboxesnew_[:,2] = (bboxes_[:,3] / img.shape[0] + bboxes_[:,1] / img.shape[0])/2
bboxesnew_[:,3] = (bboxes_[:,2] / img.shape[1] - bboxes_[:,0] / img.shape[1])
bboxesnew_[:,4] = (bboxes_[:,3] / img.shape[0] - bboxes_[:,1] / img.shape[0])
# 保存图像和bboxes
cv2.imwrite(path + "/aug/" + name[:-4] + "-aug" + str(i) +".bmp",img_)
np.savetxt(path + "/aug/" + name[:-4] + "-aug" + str(i) +".txt",bboxesnew_, fmt = ['%i','%10.6f','%10.6f','%10.6f','%10.6f'])
print(path + "/aug/" + name[:-4] + "-aug" + str(i))
根据要识别的类数量,更改配置文件
classes = ?
filters=?
总共有3处。
写一个脚本darknet_train_myobject.cmd
darknet.exe detector train mydataset/myobj.data mydataset/yolov3.cfg darknet53.conv.74
写一个测试脚本darknet_test_myobject.cmd
darknet.exe detector test mydataset/myobj.data mydataset/yolov3.cfg backup/yolov3_last.weights myobjtest/6.bmp
首先编译带_dll的工程,编译配置为debug/release,x64。
在c++程序中,使用如下方式调用,识别单张图片
#include "include/yolo_v2_class.hpp"
void draw_boxes(cv::Mat mat_img, std::vector result_vec, std::vector obj_names,
int current_det_fps = -1, int current_cap_fps = -1)
{
int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };
for (auto& i : result_vec) {
cv::Scalar color = obj_id_to_color(i.obj_id);
cv::rectangle(mat_img, cv::Rect(i.x, i.y, i.w, i.h), color, 2);
if (obj_names.size() > i.obj_id) {
std::string obj_name = obj_names[i.obj_id];
if (i.track_id > 0) obj_name += " - " + std::to_string(i.track_id);
cv::Size const text_size = getTextSize(obj_name, cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, 2, 0);
int max_width = (text_size.width > i.w + 2) ? text_size.width : (i.w + 2);
max_width = std::max(max_width, (int)i.w + 2);
//max_width = std::max(max_width, 283);
std::string coords_3d;
if (!std::isnan(i.z_3d)) {
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << "x:" << i.x_3d << "m y:" << i.y_3d << "m z:" << i.z_3d << "m ";
coords_3d = ss.str();
cv::Size const text_size_3d = getTextSize(ss.str(), cv::FONT_HERSHEY_COMPLEX_SMALL, 0.8, 1, 0);
int const max_width_3d = (text_size_3d.width > i.w + 2) ? text_size_3d.width : (i.w + 2);
if (max_width_3d > max_width) max_width = max_width_3d;
}
cv::rectangle(mat_img, cv::Point2f(std::max((int)i.x - 1, 0), std::max((int)i.y - 35, 0)),
cv::Point2f(std::min((int)i.x + max_width, mat_img.cols - 1), std::min((int)i.y, mat_img.rows - 1)),
color, CV_FILLED, 8, 0);
putText(mat_img, obj_name, cv::Point2f(i.x, i.y - 16), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(0, 0, 0), 2);
if (!coords_3d.empty()) putText(mat_img, coords_3d, cv::Point2f(i.x, i.y - 1), cv::FONT_HERSHEY_COMPLEX_SMALL, 0.8, cv::Scalar(0, 0, 0), 1);
}
}
if (current_det_fps >= 0 && current_cap_fps >= 0) {
std::string fps_str = "FPS detection: " + std::to_string(current_det_fps) + " FPS capture: " + std::to_string(current_cap_fps);
putText(mat_img, fps_str, cv::Point2f(10, 20), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(50, 255, 0), 2);
}
}
#endif // OPENCV
void show_console_result(std::vector const result_vec, std::vector const obj_names, int frame_id = -1) {
if (frame_id >= 0) std::cout << " Frame: " << frame_id << std::endl;
for (auto& i : result_vec) {
if (obj_names.size() > i.obj_id) std::cout << obj_names[i.obj_id] << " - ";
std::cout << "obj_id = " << i.obj_id << ", x = " << i.x << ", y = " << i.y
<< ", w = " << i.w << ", h = " << i.h
<< std::setprecision(3) << ", prob = " << i.prob << std::endl;
}
}
std::string names_file = "E:/WorkSpace-git/QEL_IMUCS_Project/Yolov3/mydataset/myobj.names";
std::string cfg_file = "E:/WorkSpace-git/QEL_IMUCS_Project/Yolov3/mydataset/yolov3.cfg";
std::string weights_file = "E:/WorkSpace-git/QEL_IMUCS_Project/Yolov3/mydataset/yolov3_last.weights";
std::string filename = “1.bmp”;
Detector detector(cfg_file, weights_file);
auto obj_names = objects_names_from_file(names_file);
cv::Mat mat_img = cv::imread(filename);
std::vector result_vec = detector.detect(mat_img);
draw_boxes(mat_img, result_vec, obj_names);
cv::imshow("window name", mat_img);
show_console_result(result_vec, obj_names);
cv::waitKey(0);
要包含yolo_cpp_dll.lib,把yolo_cpp_dll.dll加入的环境变量。
注意:如果dll是使用release配置编译的,那么在调用dll的工程中如果使用debug的配置,则会有cfg_file不能识别的bug。