darknet是一个C语言实现的深度学习框架,几乎不依赖任何库,安装编译都很方便,训练好的模型可以直接在opencv上部署,堪称业界良心。这篇博客主要包含目标检测数据标注和预处理、yolo_v3代码编译、模型训练、在opencv上部署,都是简要的笔记。
〇、参考链接
这些博客写的比较详细,博主也是参照这些博客一步步走下来的,在这些博客基础上总结扩充的:
一、数据标注
使用labelimg,有python版本,也有打包好的二进制文件(window/linux)直接用:下载地址 本站暂存的windows版本

我们使用Pascal标注格式,将标注文件xml和图像存放在一起,放到同一目录下。标注的时候可以在软件里设置自动保存(View->AutoSaving),就不需要一直弹窗确认了。下面是软件的快捷键,可以提高标注效率。
| Ctrl + u | Load all of the images from a directory |
| Ctrl + r | Change the default annotation target dir |
| Ctrl + s | Save |
| Ctrl + d | Copy the current label and rect box |
| Space | Flag the current image as verified |
| w | Create a rect box |
| d | Next image |
| a | Previous image |
| del | Delete the selected rect box |
| Ctrl++ | Zoom in |
| Ctrl– | Zoom out |
| ↑→↓← | Keyboard arrows to move selected rect box |
二、数据增强
标注好图像之后,为了取得好的训练效果,常常需要对数据集进行扩充,在变化图像的时候谁带也把标注文件一起处理好,这样同源的图像就不用重复标注了。
有一些Python的脚本比较好用,博主根据自己需要写了一个matlab的(这里没哟该标注框的位置,如果你用的变换有目标位置发生改变的,要换算一下矩形框位置进行修改)
clear
FOLDER = 'D:/data/drone/';
EXT = '.jpg';
output_path = '1';
mkdir(output_path);
files = dir([FOLDER '*' EXT]);
% calculate transform position
for i = 1:length(files)
file = files(i);
img = imread(fullfile(file.folder, file.name));
% read xml
xmlfile = fullfile(file.folder, [file.name(1:end-4) '.xml']);
fp = fopen(xmlfile, 'r');
A = fscanf(fp,'%c');
fclose(fp);
%output_path = file.folder;
% transform image
parfor trans = 1:10
img_new_name = fullfile(output_path, ...
[file.name(1:end-4) '.' sprintf('%02d',trans) '.JPG']);
xml_new_name = fullfile(output_path, ...
[file.name(1:end-4) '.' sprintf('%02d',trans) '.xml']);
% transform image
img_new = data_enhance(img, trans);
imwrite(img_new, img_new_name);
% trnasform xml
A_new = strrep(A, file.name, ...
[file.name(1:end-4) '.' sprintf('%02d',trans) '.JPG']);
fp = fopen(xml_new_name, 'w');
fprintf(fp, '%s', A_new);
fclose(fp);
end
end
里面用到的图像变换函数(后面几个变换会改变标注框的位置,如果要用后面的变换,上面那个文件中还需要修改矩形的位置):
function [img] = data_enhance(img_src,type)
img = img_src;
if type == 0
img = img_src;
elseif type == 1
img = imsharpen(img_src);
elseif type == 2
img = imnoise(img_src, 'gaussian');
elseif type == 3
img = imnoise(img_src, 'poisson');
elseif type == 4
img = img_src * 0.8;
elseif type == 5
img = img_src * 0.9;
elseif type == 6
img = img_src * 1.1;
elseif type == 7
img = img_src * 1.2;
elseif type == 8
img = imguidedfilter(img_src);
elseif type == 9
img = imgaussfilt(img_src,2);
elseif type == 10
img = rgb2gray(img_src);
img = cat(3, img, img, img);
elseif type == 31
img = rot90(img_src,180);
elseif type == 32
img = fliplr(img_src);
elseif type == 33
img = fliplr(img_src);
img = rot90(img,180);
elseif type == 41
img = imrotate(img_src,2);
elseif type == 42
img = imratate(img_src,3);
elseif type == 43
img = imresize(img_src, 0.8);
elseif type == 44
img = imresize(img_src, 1.2);
end
img = uint8(img);
end
三、数据处理
我们已经有了训练需要的数据,但是它与darknet规定的格式不同,darknet并不认识它,这一步就是我们在训练之前需要把数据和标注的格式处理成darknet需要的格式。
先新建一个VOCdevkit目录,在其下新建一个VOC2007目录,再在VOC2007下新建Annotations、ImageSets、JPEGImages三个目录,再在ImageSets下面新建Main目录
最终目录结构如下(先不用管里面的txt文件,txt文件都是脚本生成的):

然后我们把标注好的图像放到JPEGImages目录下,然后把标注好的xml文件放到Annotations目录下,把test.py脚本放到VOC2007目录下,把voc_label.py脚本。
test.py脚本的作用是生成训练检测文件列表。test.py最开头的参数可以调整用于训练和验证的图像比例。
import os
import random
trainval_percent = 0.1
train_percent = 0.9
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
total_xml = os.listdir(xmlfilepath)
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
ftrainval = open('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')
for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftest.write(name)
else:
fval.write(name)
else:
ftrain.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
voc_label.py脚本的作用是按照上一步生成的文件列表,将标注也转化成darknet认识的txt格式,也就是目录最外层的txt,这几个txt是真正最后传递给darknet的。该脚本使用有两点要注意:
下面的脚本里默认图片是jpg格式,如果你的图片是其它格式,那么需要在脚本中修改
修改目录 Sets 和类别 classes,脚本下载链接:voc_label.py
wget https://pjreddie.com/media/files/voc_label.py

首先运行test.py脚本,再运行voc_label.py脚本,看到对应txt文件生成,就大功告成了
四、darknet代码编译
1. 下载代码
git clone https://github.com/pjreddie/darknet
2. 编译代码
如果是linux,直接修改MakeFile,是使用GPU还是CPU选择下就可以了,纯c写的,没啥依赖,有装opencv的话添加下路径,没有opencv也可以运行。
如果是windows,需要读github首页的readme,来确认自己的环境,cuda版本不一样的改下配置文件,该装cudnn的装cudnn,该重新cmake的重新cmake,该把cuda.procs放到指定位置就按照readme给的环境来配置就好,一般多折腾会儿都没什么问题。
3. 下载模型
也是在官网已经有了预训练好的模型,这里再贴两个模型链接,都提前下下来:
待会儿测试的时候会跑一个识别,用到的预训练模型:
wget https://pjreddie.com/media/files/yolov3.weights
预训练的时候可能会用到darknet53网络的权重:
wget https://pjreddie.com/media/files/darknet53.conv.74
这两个文件都放到和darknet.exe同一个目录下。
4. 运行测试demo
测试下效果:
darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

五、训练模型
1.修改cfg/voc.data
这里指定了训练时使用的数据,我们选择之前脚本生成的txt就好

2.修改data/voc.names和coco.names
修改成自己标注时候的类别就可以

3.修改参数文件cfg/yolov3-voc.cfg
ctrl+f搜 yolo, 总共会搜出3个含有yolo的地方。
每个地方都必须要改2处, filters:3*(5+len(classes));因为输出是矩形4个location+1个置信度+1个类别
其中:classes: len(classes) = 1,这里以单个类dog为例
filters = 18
classes = 1
可修改:random = 1:原来是1,显存小改为0。(是否要多尺度输出。)

参数文件开头的地方可以选训练的batchsize,要注意,训练时要改batchsize,测试时要强制成1,。博主使用的显卡是8G现存,每次只能算4张或者5张图,所以选了64/16=4张来算,你需要根据自己显卡的情况进行配置,免得显存爆掉。

4.训练模型
我们在先前下载的darknet53权重基础上微调
darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74
博主使用大概700张图片,训练到200次的时候结果已经可以看了,一晚上大概跑了3000轮,默认没100次迭代后模型备份到backup目录下

六、应用部署
opencv3.4.2之后dnn模块就支持yolov3了,因为ipp和openmp并行,opencv对cpu的利用效率很高,测试过opencv的实现比darknet本来的实现要快九倍,博主也测了下,也差不多是这么快,所以把模型通过opencv部署可以有比较快的运行速度,当然,如果你需要更快的速度,能配置一块儿比较好的显卡就另当别论了。
| OS | Framework | CPU/GPU | Time(ms)/Frame |
|---|---|---|---|
| Linux 16.04 | Darknet | 12x Intel Core i7-6850K CPU @ 3.60GHz | 9370 |
| Linux 16.04 | Darknet + OpenMP | 12x Intel Core i7-6850K CPU @ 3.60GHz | 1942 |
| Linux 16.04 | OpenCV [CPU] | 12x Intel Core i7-6850K CPU @ 3.60GHz | 220 |
| Linux 16.04 | Darknet | NVIDIA GeForce 1080 Ti GPU | 23 |
| macOS | DarkNet | 2.5 GHz Intel Core i7 CPU | 7260 |
| macOS | OpenCV [CPU] | 2.5 GHz Intel Core i7 CPU | 400 |
直接上博主的代码吧,只有一个cpp文件,也可以在github直接下载下来用:
P-Chao / yolov3-opencv
load yolov3 and detection by opencv dnn module
#include<opencv2/opencv.hpp>
float confThreshold = 0.5f;
float nmsThreshold = 0.4f;
int inpWidth = 416;
int inpHeight = 416;
std::vector<std::string> classes;
std::vector<cv::String> getOutputsNames(const cv::dnn::Net& net) {
static std::vector<cv::String> names;
if (names.empty()) {
std::vector<int> outLayers =
net.getUnconnectedOutLayers();
std::vector<cv::String> layersNames =
net.getLayerNames();
names.resize(outLayers.size());
for (size_t i = 0; i < outLayers.size(); ++i)
names[i] = layersNames[outLayers[i]-1];
}
return names;
}
// Draw the predicted bounding box
void drawPred(int classId, float conf, int left, int top, int right, int bottom, cv::Mat & frame) {
//Draw a rectangle displaying the bounding box
cv::rectangle(frame, cv::Point(left, top), cv::Point(right, bottom), cv::Scalar(0, 0, 255));
//Get the label for the class name and its confidence
std::string label = cv::format("%.2f", conf);
if (!classes.empty()) {
CV_Assert(classId < (int)classes.size());
label = classes[classId] + ":" + label;
}
//Display the label at the top of the bounding box
int baseLine;
cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
top = std::max(top, labelSize.height);
putText(frame, label, cv::Point(left, top), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255));
}
void postprocess(cv::Mat& frame, const std::vector<cv::Mat>& outs) {
std::vector<int> classIds;
std::vector<float> confidences;
std::vector<cv::Rect> boxes;
for (size_t i = 0; i < outs.size(); ++i) {
float* data = (float*)outs[i].data;
for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols) {
cv::Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
cv::Point classIdPoint;
double confidence;
cv::minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
if (confidence > confThreshold) {
int centerX = (int)(data[0] * frame.cols);
int centerY = (int)(data[1] * frame.rows);
int width = (int)(data[2] * frame.cols);
int height = (int)(data[3] * frame.rows);
int left = centerX - width / 2;
int top = centerY - height / 2;
classIds.push_back(classIdPoint.x);
confidences.push_back((float)confidence);
boxes.push_back(cv::Rect(left, top, width, height));
}
}
}
// Perform non maximum suppression to eliminate redundant overlapping boxes with
// lower confidences
std::vector<int> indices;
cv::dnn::NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
for (size_t i = 0; i < indices.size(); ++i) {
int idx = indices[i];
cv::Rect box = boxes[idx];
drawPred(classIds[idx], confidences[idx], box.x, box.y,
box.x + box.width, box.y + box.height, frame);
}
return;
}
int detect(cv::Mat& image) {
// Load names of classes
std::string classesFile = "coco.names";
std::string line;
std::ifstream ifs(classesFile.c_str());
while (std::getline(ifs, line))
classes.push_back(line);
std::string modelConfiguration = "yolov3.cfg";
std::string modelWeights = "yolov3.weights";
// Load the network
cv::dnn::Net net = cv::dnn::readNetFromDarknet(
modelConfiguration, modelWeights);
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
if (image.empty()) {
std::cout << "input image empty !!!" << std::endl;
return 1;
}
cv::Mat blob;
cv::dnn::blobFromImage(image, blob, 1/255.0,
cv::Size(inpWidth, inpHeight), cv::Scalar(0,0,0), true, false);
net.setInput(blob);
std::vector<cv::Mat> outs;
net.forward(outs, getOutputsNames(net));
// Remove the bounding boxes with low confidence
postprocess(image, outs);
// Profile
std::vector<double> layersTimes;
double freq = cv::getTickFrequency() / 1000;
double t = net.getPerfProfile(layersTimes) / freq;
std::string label = cv::format("Inference time for a frame: %.2f ms", t);
cv::putText(image, label, cv::Point(0, 15),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255));
cv::Mat detectedFrame;
image.convertTo(detectedFrame, CV_8U);
cv::imwrite("predict.png",detectedFrame);
return 0;
}
int main(int argc, char * argv[]) {
cv::Mat image = cv::imread("D:/workspace/cvyolo/data/dark.jpg");
cv::imshow("image", image);
detect(image);
cv::waitKey();
return 0;
}
OK,希望能有帮助。
你好!
请问博主有尝试用libtorch 部署yolov3 吗?
我想知道怎么用libtorch部署yolov3
谢谢!
没有用libtorch部署过 0.0 ~
请问您实现部署了吗,我最近也想实现