我要先声明一下,系统是Ubuntu18.04,我的机器已经装好了opencv4.1.1 和 cuda10.0 的,过程可参考我另一篇博客,这部分不再说明
IDE使用qtcreator
Github链接
官网链接
这个不用多讲,直接git clone,网不好不管
git clone https://github.com/pjreddie/darknet
还要下载预训练权重darknet53.conv.74
这个先放一边,一会拿它组成训练文件夹
下载完成后进入darknet文件夹
为了方便修改代码,使用VSCode打开darknet所在的文件夹
找到Makefile文件,打开,前5行是配置的参数,OPENCV、GPU、CUDNN全部打开
GPU=0
CUDNN=0
OPENCV=0
OPENMP=0
DEBUG=0
修改完就是这样的
GPU=1
CUDNN=1
OPENCV=1
OPENMP=0
DEBUG=0
关于后面的显卡计算能力配置这个参考别的博客,我不修改没有遇到问题
ARCH= -gencode arch=compute_30,code=sm_30 \
-gencode arch=compute_35,code=sm_35 \
-gencode arch=compute_50,code=[sm_50,compute_50] \
-gencode arch=compute_52,code=[sm_52,compute_52]
# -gencode arch=compute_20,code=[sm_20,sm_21] \ This one is deprecated?
# This is what I use, uncomment if you know your arch and want to specify
# ARCH= -gencode arch=compute_52,code=compute_52
找到第42行,修改pkg-congif的包名称,opencv4的名称就是opencv4,两处都改
ifeq ($(OPENCV), 1)
COMMON+= -DOPENCV
CFLAGS+= -DOPENCV
LDFLAGS+= `pkg-config --libs opencv` -lstdc++
COMMON+= `pkg-config --cflags opencv`
endif
这样,其他的不用动
ifeq ($(OPENCV), 1)
COMMON+= -DOPENCV
CFLAGS+= -DOPENCV
LDFLAGS+= `pkg-config --libs opencv4` -lstdc++
COMMON+= `pkg-config --cflags opencv4`
endif
下面紧接着是cuda的库,检查一下是否和自己的路径相符,我的默认就好
ifeq ($(GPU), 1)
COMMON+= -DGPU -I/usr/local/cuda/include/
CFLAGS+= -DGPU
LDFLAGS+= -L/usr/local/cuda/lib64 -lcuda -lcudart -lcublas -lcurand
endif
到此Makefile配置完成
按快捷键Ctrl+shift+P打开VSCode的命令,搜索C/C++,打开编辑配置(UI),不用UI的随便
/usr/local/include/opencv4/**
让它自己找就行了/usr/local/cuda/include
里面,我的添加完如下${workspaceFolder}/**
/usr/local/cuda/include/
/usr/local/include/opencv4/**
OPENCV
CUDNN
GPU
do{
make;
if(编译出错){
修改;
continue;
}
else break;
}while(1); //这就是要干的
其实很简单按Ctrl+shift+`打开VSCode的终端,执行make
然后报错
./src/image_opencv.cpp:12:1: error: ‘IplImage’ does not name a type; did you mean ‘image’?
IplImage *image_to_ipl(image im)
^~~~~~~~
这是因为他原来写的是opencv3的结构和命名,4的有些变动
按住Ctrl,鼠标点击./src/image_opencv.cpp:12:1直接转到这个位置,这是我用VSCode的一大原因
在最上面添加一个头文件
#include "opencv2/imgproc/imgproc_c.h"
这个错误就解决了
然后是
./src/image_opencv.cpp:76:20: error: ‘CV_CAP_PROP_FRAME_WIDTH’ was not declared in this scope
if(w) cap->set(CV_CAP_PROP_FRAME_WIDTH, w);
这个就是命名变化的,此类的把前面的CV_去掉就行,这个错误都在image_opencv.cpp里面,这个文件以后有用,请记住它,改完错误就能完成编译。
先建一个文件夹,名字任意,不要带中文,你懂的然后以下都是在这里面操作
darknet YoloV3的数据集格式是文本文档,结构简单
一个JPEGImages文件夹,里面放图片,都是JPG格式的,不是的话可以用opencv自己写程序自动转换
另一个labels文件夹,存放标记文件也是txt格式是文本文档
标记文件具体的格式是,文件名和对应的JPG文件名保证相同
2 0.341624 0.582733 0.00729167 0.0175926
1 0.433048 0.624979 0.0286458 0.062963
4 0.265383 0.666638 0.0776042 0.125926
#ID 空格 box中心点X坐标 空格 box中心点Y坐标 空格 目标宽度W 空格 目标高度H
#坐标都是归一化的0-1,相对于画面的
#一个图多个目标直接换行再写就行
然后两个文件夹一定放在一起,darknet似乎是直接自动找相对于图片文件的../labels/xxxxxx.txt
来找标记文件的
再建一个backup文件夹,用来存放训练权重文件的备份
weigits文件夹,用来存放训练完成要保留的权重文件,这个自愿
ImageSets文件夹,用来存图片集在里面再建两个文本文档,就是图片集了
一个名叫train.txt就是用来训练的图片集,里面存JPG图片的路径,推荐绝对路径,一行一个路径
另一个val.txt用来验证格式相同
出ImageSets文件夹,
新建一个voc.names文本文档里面写你的目标的标签,例如
car
people
bicycle
bird
boat
一行一个我已5个目标为例
新建一个voc.data文本文档里面有5行
classes= 你的目标数量
train = /...../你的训练集文件夹/ImageSets/train.txt
valid = /...../你的训练集文件夹/ImageSets/val.txt
names = /...../你的训练集文件夹/voc.names
backup = /...../你的训练集文件夹/backup
还有一个最重要的cfg文件,从cfg/yolov3-voc.cfg
里面复制一个,到你的文件夹里,可以重命名,后缀保留(其实应该没关系)然后修改
前几行[net]下面
[net]
# Testing
batch=1 #batch是多少张图片迭代一次训练,调大能快速收敛,但是这个超级吃显存
subdivisions=1 #subdivisions是分多少步去计算一个batch的图片
#最终同时在显存里的就是batch/subdivisions,可以达到既能收敛又能省显存的目的
# Training
# batch=64
# subdivisions=16
width=416 #width和height还有channels是网络入口的大小,通道一般不变,宽高在速度和精度之间衡量
height=416
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1
learning_rate=0.001 #学习率,不变就好
burn_in=1000
max_batches = 50200 #最大迭代次数
policy=steps #下面三行都是学习率衰减策略,如果这项不动的话就
steps=40000,45000 #是在迭代40000次时学习率乘0.1,45000次时再乘0.1
scales=.1,.1 #要根据你的最大迭代次数改,可以逗号分隔再往后填
然后在下面找[yolo]项,一共有3个,在608行,692行,776行
拿一个为例,其他三个一样
[convolutional]
size=1
stride=1
pad=1
filters=10 #这个一定要改,和目标数量有关,就是3*(目标数量+5),否则在构建网络的时候报错
activation=linear
[yolo]
mask = 0,1,2
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
classes=20 #类的数量
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=1 #多尺度训练,能让大图片准,小图片快,代价就是显存
#如果输入的图片大小固定应该没啥用,可以关掉
再加上一开始下载的预训练权重,这样就配置完成了
最终的文件夹结构
你的训练集文件夹
╞backup
╞ImageSets
║ └train.txt val.txt
╞JPEGImages
║ └你的图片
╟labels
║ └你的标签
╟weigits
╟voc.names
╟voc.data
╟yolov3-voc.cfg
└darknet53.conv.74
关于训练集标签和图片集的生成,我想大家都是撸代码的,偷懒办法不可能想不出来吧
本来能直接开始训练了,但是本人比较懒,嫌命令太长,然后就有了这个
DARKNET_PATH=你的darknet路径
LOCAT=${PWD} #如果是放在你的训练集文件夹下面就能自动获取当前目录
classes=5
TRAIN_PATH=${LOCAT}/ImageSets/train.txt
VALID_PATH=${LOCAT}/ImageSets/val.txt
NAMES_PATH=${LOCAT}/voc.names
BACKUP_PATH=${LOCAT}/backup
DATA_PATH=${LOCAT}/voc.data
CFG_PATH=${LOCAT}/yolov3-voc.cfg
MODEL_PATH=${LOCAT}/darknet53.conv.74
cat>${DATA_PATH}<<EOF
classes= ${classes}
train = ${TRAIN_PATH}
valid = ${VALID_PATH}
names = ${NAMES_PATH}
backup = ${BACKUP_PATH}
EOF
cd ${DARKNET_PATH}
if [ "$1" = "train" ];then
if [ "$2" = "" ];then
./darknet detector train ${DATA_PATH} ${CFG_PATH} darknet53.conv.74 -gpus 0
else
if [ "$2" = "goon" ];then
WEIGHT=${BACKUP_PATH}/yolov3-voc.backup
./darknet detector train ${DATA_PATH} ${CFG_PATH} ${WEIGHT} -gpus 0
else
./darknet detector train ${DATA_PATH} ${CFG_PATH} $2 -gpus 0
fi
fi
fi
if [ "$1" = "test" ];then
WEIGHT=${BACKUP_PATH}/yolov3-voc.backup
if [ "$3" = "" ];then
./darknet detector test ${DATA_PATH} ${CFG_PATH} ${WEIGHT} -gpus 0 $2
else
./darknet detector test ${DATA_PATH} ${CFG_PATH} $3 $2 -gpus 0
fi
fi
前面的都简单,使用方法就是
./xxxx.sh train
是训练(默认使用darknet53.conv.74训练)
./xxxx.sh train goon
是继续上一次权重训练(默认使用backup/yolov3-voc.backup训练)
./xxxx.sh train 另一个权重文件的路径
就是在另一个权重文件基础上训练
./xxxx.sh test JPG文件路径
就是使用那个图片测试
./xxxx.sh test JPG文件路径 权重文件的路径
就是使用那个图片和权重文件测试
把这个脚本也放进你的训练集文件夹里就行了
配置好上一步的前几行变量就可以./xxxx.sh train
了
想要预览效果可以./xxxx.sh test JPG文件路径
首先将darknet目录下include/darknet.h和libdarknet.so复制到其他的安全的地方(比如在/usr/local下面新建一个文件夹)然后把那个共享库加到系统里
cd /etc/ld.so.conf.d/
sudo vim darknet.conf #里面写入你的共享库所在文件夹的路径
sudo ldconfig #刷新共享库路径
新建一个qtcreator的项目(拿qtcreator举例,操作都可以在其他平台上复现)
在pro文件里添加opencv(这个就不详细说了)
添加darknet.h的引用目录,添加cuda的引用目录,前面提到了一般在/usr/local/cuda/include
里面
添加darknet和cuda的库路径,添加OPENCV那几个在VSCode里面添加过的宏
完事就是这样的
#darknet依赖cuda,需要添加cuda头文件路径和库
INCLUDEPATH += /usr/local/darknet /usr/local/cuda/include
LIBS += -L/usr/local/darknet/lib -L/usr/local/cuda/lib64 -lcuda -lcudart -lcublas -lcurand -lcudnn -ldarknet
DEFINES += GPU OPENCV CUDNN #darknet内部需要的全局宏,否则功能打不开
回到VSCode里,读一下darknet的源码
找到examples/darknet.c,里面包含main函数,可以顺着找到函数,调用肯定是和测试差不多的,找到识别detector命令的地方
} else if (0 == strcmp(argv[1], "detector")){
run_detector(argc, argv);
就是这个函数,到定义
if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen);
else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear);
能找到test命令和train命令,到定义test_detector
void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
{
/*
printf("进入test_detector\n"
"datacfg = %s\n" //路径配置文件
"cfgfile = %s\n" //网络配置文件
"weightfile = %s\n" //权重文件
"filename = %s\n" //测试图片
"thresh = %f\n" //最低可能性阈值,默认0.5
"hier_thresh = %f\n"//? = 0.5
"outfile = %s\n" //输出图像路径
"fullscreen = %d\n" //全屏显示选项
, datacfg, cfgfile, weightfile, filename, thresh, hier_thresh, outfile, fullscreen);*/
list *options = read_data_cfg(datacfg); //读取.data路径文件$
char *name_list = option_find_str(options, "names", "data/names.list"); //获取标签名列表文件路径$
/*printf("name_list = %s\n", name_list);*/
char **names = get_labels(name_list); //解析签名列表文件到字符串数组$
image **alphabet = load_alphabet(); //加载标签图片
network *net = load_network(cfgfile, weightfile, 0); //从配置文件和权重加载神经网络$
set_batch_network(net, 1); //启动网络(CUDNN支持)$
srand(2222222);
double time;
char buff[256];
char *input = buff;
float nms=.45;
while(1){
if(filename){ //如果测试图片文件名非空,拷贝文件名作为输入
strncpy(input, filename, 256);
} else { //否则询问测试图片文件路径
printf("Enter Image Path: ");
fflush(stdout);
input = fgets(input, 256, stdin);
if(!input) return;
strtok(input, "\n");
}
image im = load_image_color(input,0,0); //加载测试图片(load_image_cv函数加检查)
image sized = letterbox_image(im, net->w, net->h); //缩放图像至网络入口大小$
//image sized = resize_image(im, net->w, net->h);
//image sized2 = resize_max(im, net->w);
//image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
//resize_network(net, sized.w, sized.h);
layer l = net->layers[net->n-1];
float *X = sized.data; //获取图像数组指针
time=what_time_is_it_now(); //获取当前时间
network_predict(net, X); //网络推理$
printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
int nboxes = 0;
//获得bandbox列表$
detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
//printf("%d\n", nboxes);
//if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes); //画bandbox
free_detections(dets, nboxes); //释放bandbox列表
if(outfile){
save_image(im, outfile); //如果outfile非空,按outfile路径保存输出图像
}
else{
save_image(im, "predictions");
#ifdef OPENCV
make_window("predictions", 512, 512, 0);
show_image(im, "predictions", 0);
#endif
}
free_image(im);
free_image(sized);
if (filename) break;
}
}
这就是最好的例程了,其中$结尾的是必须的步骤
如果想要使用opencv读取图像,找到src/image_opencv.cpp
里面的mat_to_image是将opencv的Mat转换为Yolo接受的格式是函数,ipl_to_image和rgbgr_image就在旁边,需要啥就复制出来
image mat_to_image(Mat m)
{
IplImage ipl = m;
image im = ipl_to_image(&ipl);
rgbgr_image(im);
return im;
}
关于detection结构体说明,有些我也没看懂,但是这些够用
typedef struct
{
//中心点坐标和bandbox宽高(归一化的)
float x, y, w, h;
} box;
typedef struct detection
{
boxbbox; //bandbox
int classes;
float *prob; //数组,各个目标的可能性
float *mask;
float objectness;
int sort_class;
} detection;
最后送上例程一份
#include
#include
#include
#include
using namespace std;
using namespace cv;
#define datacfg "/home/yao/Documents/yoloV3/darknet/VOCdevkit/VOC2007/voc.data"
#define cfgfile "/home/yao/Documents/yoloV3/darknet/VOCdevkit/VOC2007/yolov3-voc.cfg"
#define weightfile "/home/yao/Documents/yoloV3/darknet/VOCdevkit/VOC2007/weigits/640_480_3K_5.23.weights"
#define videosrc "/home/yao/Videos/0.mp4"
#define SavePAYH "/home/yao/Videos/640_480_3K_5.23.avi"
#define PicturePATH "/home/yao/Downloads/qq-files/1790931015/file_recv/BL}A~RL7L3Y0({BI0[}JGRT.png"
#define SaveVideo 1
#define USE_Picture 0
void drawBox(Mat& Src, const Scalar& color, box Box, const char* name, float prob);
Scalar get_Scalar(int index, int classes);
image cvmat_to_image(cv::Mat m);
int main()
{
list *options = read_data_cfg(datacfg); //读取路径文件
char *name_list = option_find_str(options, "names", NULL); //获取标签名列表文件路径
char **names = get_labels(name_list); //解析签名列表文件到字符串数组
int classes = option_find_int(options, "classes", 2); //获得总标签数量
network *net = load_network(cfgfile, weightfile, 0); //从配置文件和权重加载神经网络
set_batch_network(net, 1); //启动网络(CUDNN支持)
#if (USE_Picture == 0)
VideoCapture CAP(videosrc);
//用摄像头就设置一下
// CAP.open("/dev/video0", CAP_V4L2);
// CAP.set(CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
// CAP.set(CAP_PROP_FRAME_HEIGHT, 480);
// CAP.set(CAP_PROP_FRAME_WIDTH, 640);
// CAP.set(CAP_PROP_AUTO_WB, 1);
// CAP.set(CAP_PROP_AUTO_EXPOSURE, 0);
// CAP.set(CAP_PROP_EXPOSURE, 121);
// CAP.set(CAP_PROP_FPS, 120);
#endif
#if(SaveVideo == 1)
VideoWriter Writer(SavePAYH, VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(1920, 1080));
if(!Writer.isOpened())
{
cout << "Writer Error" << endl;
exit(-1);
}
#endif
Mat Src, Dst;
int nboxes = 0;
namedWindow("Video", WINDOW_NORMAL);
resizeWindow("Video", 1280, 720);
#if (USE_Picture == 0)
double time1 = 0;
double time2 = 0;
char buff[100] = { 0 };
if(CAP.isOpened())
{
while(CAP.read(Src))
{
time1 = (double)getCPUTickCount();
#else
Src = imread(PicturePATH);
#endif
resize(Src, Dst, Size(net->w,net->h)); //缩放至网络入口大小
image im = cvmat_to_image(Dst); //将图像转换为Yolo格式
network_predict(net, im.data); //使用图像数据网络推理
detection *dets = get_network_boxes(net, im.w, im.h, 0.5, 0.5, 0, 1, &nboxes); //获得bandbox列表
cout << nboxes << endl;
for(int i = 0; i < nboxes; ++i)
{
//cout << i << ": objectness=" << dets[i].objectness << endl;
float probmax = dets[i].prob[0];
int index = 0;
for(int j = 0; j < dets->classes; ++j)
{
if(probmax < dets[i].prob[j]) //循环找出可能性最大的序号
{
probmax = dets[i].prob[j];
index = j;
}
//cout << "names=" << names[j] << " prob=" << dets[i].prob[j] << endl;
}
if(probmax < 0.5) //可能性小于一定值时抛弃
{
cout << "throw" << endl;
continue;
}
drawBox(Src, get_Scalar(index, classes), dets[i].bbox, names[index], probmax);
}
#if (USE_Picture == 0)
#if(SaveVideo == 1)
time2 = (double)getCPUTickCount();
double usetime = (time2 - time1) / getTickFrequency();
sprintf(buff, "Time = %4.3lfms FPS = %2.2lf BoxConut = %d", usetime * 1000.0, 1.0 / usetime, nboxes);
cout << buff << endl;
putText(Src, buff, Point(25, 100), FONT_ITALIC, 1, Scalar(0, 255, 0), 2);
putText(Src, weightfile, Point(25, 50), FONT_ITALIC, 1, Scalar(0, 255, 0), 2);
imshow("Video", Src);
Writer.write(Src);
#endif
waitKey(1);
#else
imshow("Video", Src);
waitKey(100000);
#endif
free_image(im); //free!!!!!!!纯C编写的darknet不会自动释放内存
free_detections(dets, nboxes); //free!!!!!!!纯C编写的darknet不会自动释放内存
#if (USE_Picture == 0)
}
}
else
{
cout << "CAP Error" << endl;
}
#endif
free_network(net); //free!!!!!!!纯C编写的darknet不会自动释放内存
free_ptrs((void**)names, classes); //free!!!!!!!纯C编写的darknet不会自动释放内存
free_list(options); //free!!!!!!!纯C编写的darknet不会自动释放内存
cout << "Done!" << endl;
}
//画出bandbox及其标签和可能性
void drawBox(Mat& Src, const Scalar& color, box Box, const char* name, float prob)
{
int left = (Box.x-Box.w/2.)*Src.cols;
int right = (Box.x+Box.w/2.)*Src.cols;
int top = (Box.y-Box.h/2.)*Src.rows;
int bot = (Box.y+Box.h/2.)*Src.rows;
if(left < 0) left = 0;
if(right > Src.cols-1) right = Src.cols-1;
if(top < 0) top = 0;
if(bot > Src.rows-1) bot = Src.rows-1;
Point p1(left, top);
Point p2(right, bot);
putText(Src, name + to_string(prob * 100) + "%", p1 - Point(0, 3), FONT_HERSHEY_PLAIN, 1, color);
rectangle(Src, p1, p2, color, 2);
}
float get_color(int c, int x, int max)
{
float colors[6][3] = { {1,0,1}, {0,0,1},{0,1,1},{0,1,0},{1,1,0},{1,0,0} };
float ratio = ((float)x / max) * 5;
int i = floor(ratio);
int j = ceil(ratio);
ratio -= i;
float r = (1-ratio) * colors[i][c] + ratio*colors[j][c];
return r;
}
Scalar get_Scalar(int index, int classes)
{
int offset = index * 123457 % classes;
int red = get_color(0, offset, classes) * 255;
int green = get_color(1, offset, classes) * 255;
int blue = get_color(2, offset, classes) * 255;
return Scalar(blue, green, red);
}
image cvipl_to_image(IplImage *src)
{
int h = src->height;
int w = src->width;
int c = src->nChannels;
image im = make_image(w, h, c);
unsigned char *data = (unsigned char *)src->imageData;
int step = src->widthStep;
int i, j, k;
for (i = 0; i < h; ++i)
{
for (k = 0; k < c; ++k)
{
for (j = 0; j < w; ++j)
{
im.data[k * w * h + i * w + j] = data[i * step + j * c + k] / 255.;
}
}
}
return im;
}
image cvmat_to_image(cv::Mat m)
{
IplImage ipl = m;
image im = cvipl_to_image(&ipl);
rgbgr_image(im);
return im;
}
都到这份上了别再想着白嫖我了