YOLO-V3 实战(暗网)
一. 准备工作
1)实验环境:
darknet 是由 C 和 CUDA 开发的,不需要配置其他深度学习的框架(如,tensorflow、caffe 等),支持 CPU 和 GPU 运算,而且安装过程非常简单。本文使用的 CUDA 的版本如下所示:
CUDA:9.0
CUDNN:7.0
2)下载 github 源码:
git clone https://github.com/pjreddie/darknet.git
3)配置darknet编译环境:
Ⅰ. 编译一直在darknet文件夹下; Ⅱ. 每次修改 Makefile 文件后需重新 make 一下才能生效; Ⅲ. 默认的 Makefile 是使用 CPU; |
修改Makefile编译环境配置文件:
GPU=1 # 是否打开GPU CUDNN=1 # 是否打开cudnn OPENCV=0# 是否打开opencv OPENMP=0 DEBUG=1 # 是否进行debug
ARCH= -gencode arch=compute_61,code=compute_61# 根据GPU计算能力选择对应数值(GTX 1080Ti:61、Tesla K80:37)官网查看:https://developer.nvidia.com/cuda-gpus
...
NVCC=/usr/local/cuda-9.0/bin/nvcc #修改nvcc路径
...
ifeq ($(GPU), 1) #修改cuda路径--黄色部分,若不需要更改删除黄色部分即可
COMMON+= -DGPU -I/usr/local/cuda-9.0/include
CFLAGS+= -DGPU
LDFLAGS+= -L/usr/local/cuda-9.0/lib64 -lcuda -lcudart -lcublas -lcurand
endif
ifeq ($(CUDNN), 1)#修改cudnn路径--黄色部分,若不需要更改删除黄色部分即可
COMMON+= -DCUDNN -I/usr/local/cuda-9.0/include
CFLAGS+= -DCUDNN
LDFLAGS+= -L/usr/local/cuda-9.0/lib64 -lcudnn
endif
重新编译
make clean make
执行./darknet就可以执行编译
4) 实验环境测试
下载预训练模型权重yolov3.weights
下载地址:https://pjreddie.com/media/files/yolov3.weights
基于yolov3.weights模型权重的测试
测试单张图片(下面两个指令相同)
./darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights data/dog.jpg
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
测试多张图片
./darknet detect cfg/yolov3.cfg yolov3.weights
~根据提示输入图片路径
输出:保存./darknet目录下的predict.png
5) ./darknet编译格式
./darknet detector test <data_cfg> <models_cfg> <weights> <test_file> [-thresh] [-out] ./darknet detector train <data_cfg> <models_cfg> <weights> [-thresh] [-gpu] [-gpus] [-clear] ./darknet detector valid <data_cfg> <models_cfg> <weights> [-out] [-thresh] ./darknet detector recall <data_cfg> <models_cfg> <weights> [-thresh]
'<>'必选项,’[ ]‘可选项
data_cfg:数据配置文件,eg:cfg/voc.data
models_cfg:模型配置文件,eg:cfg/yolov3-voc.cfg
weights:权重配置文件,eg:weights/yolov3.weights
test_file:测试文件,eg:*/*/*/test.txt
-thresh:显示被检测物体中confidence大于等于 [-thresh] 的bounding-box,默认0.005
-out:输出文件名称,默认路径为results文件夹下,eg:-out "" //输出class_num个文件,文件名为class_name.txt;若不选择此选项,则默认输出文件名为comp4_det_test_"class_name".txt
-i/-gpu:指定单个gpu,默认为0,eg:-gpu 2
-gpus:指定多个gpu,默认为0,eg:-gpus 0,1,2
二. 实验步骤
基于darknet在VOC格式的数据集上训练yolov3 |
1)下载 Pascal VOC 2007 和 2012 的数据集
wget https://pjreddie.com/media/files/VOCtrainval_11-May-2012.tar wget https://pjreddie.com/media/files/VOCtrainval_06-Nov-2007.tar wget https://pjreddie.com/media/files/VOCtest_06-Nov-2007.tar tar xf VOCtrainval_11-May-2012.tar tar xf VOCtrainval_06-Nov-2007.tar tar xf VOCtest_06-Nov-2007.tar
注:若非voc格式的数据集需先生成voc格式的数据集,数据集的目录严格按照voc数据集的目录结构
2)生成 VOC 数据集的标签
darknet 需要一个“.txt”格式的标签文件,每行表示一张图像的信息,包括(x, y, w, h)格式为:<object-class> <x> <y> <width> <height>
darknet 官网提供了一个针对 VOC 数据集,处理标签的脚本,darknet/scripts文件夹下的voc_label.py文件,若无执行如下命令下载文件:
wget https://pjreddie.com/media/files/voc_label.py # 获取脚本
修改 voc_label.py(4处)
①sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')] #替换为自己的数据集 ②classes = ["head", "eye", "nose"] #修改为自己的类别 ③in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id)) #将数据集放于当前目录下 ④os.system("cat 2007_train.txt 2007_val.txt > train.txt") #修改为自己的数据集用作训练
修改完成后执行:
python voc_label.py# 执行脚本,获得所需的“.txt”文件
生成的xml文件在 VOCdevkit/VOC%s/labels 目录下
标签处理完成后,在voc_label.py所在目录下生成如下几个文件:
2007_text.txt、2007_train.txt、2007_val.txt、2012_text.txt、2012_train.txt、train.txt、train_all.txt
3)修改配置文件
Ⅰ. 数据配置文件——cfg/voc.data
classes= 20 # 类别总数 train = /home/xieqi/project/train.txt # 训练数据所在的位置 valid = /home/xieqi/project/2007_test.txt # 测试数据所在的位置 names = data/voc.names # 修改见voc.names backup = backup # 输出的权重信息保存的文件夹
eval =
results =
Ⅱ. 模型配置文件——cfg/yolov3-voc.cfg
batch=64 # 一批训练样本的样本数量,每batch个样本更新一次参数 subdivisions=32 # 它会让你的每一个batch不是一下子都丢到网络里。而是分成subdivision对应数字的份数,一份一份的跑完后,在一起打包算作完成一次iteration width=416 # 只可以设置成32的倍数 height=416 # 只可以设置成32的倍数 channels=3 # 若为灰度图,则chennels=1,另外还需修改/scr/data.c文件中的load_data_detection函数;若为RGB则 channels=3 ,无需修改/scr/data.c文件 momentum=0.9 # 最优化方法的动量参数,这个值影响着梯度下降到最优值得速度 decay=0.0005 # 权重衰减正则项,防止过拟合 angle=0 # 通过旋转角度来生成更多训练样本 saturation = 1.5 # 通过调整饱和度来生成更多训练样本 exposure = 1.5 # 通过调整曝光量来生成更多训练样本 hue=.1 # 通过调整色调来生成更多训练样本 learning_rate=0.001 # 学习率, 刚开始训练时, 以 0.01 ~ 0.001 为宜, 一定轮数过后,逐渐减缓。 burn_in=1000 # 在迭代次数小于burn_in时,其学习率的更新有一种方式,大于burn_in时,才采用policy的更新方式 max_batches = 50200 # 训练步数 policy=steps # 学习率调整的策略 steps=40000,45000 # 开始衰减的步数 scales=.1,.1 # 在第40000和第45000次迭代时,学习率衰减10倍 ... [convolutional]——YOLO层前一层卷积层 ... filters=24 # 每一个[yolo]层前的最后一个卷积层中的 filters=num(yolo层个数)*(classes+5) ... [yolo] mask = 6,7,8 anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326#如果想修改默认anchors数值,使用k-means即可; classes=3 # 修改为自己的类别数 num=9 # 每个grid cell预测几个box,和anchors的数量一致。调大num后训练时Obj趋近0的话可以尝试调大object_scale jitter=.3 # 利用数据抖动产生更多数据, jitter是crop的参数, jitter=.3,就是在0~0.3中进行crop ignore_thresh = .5 # 决定是否需要计算IOU误差的参数,大于thresh,IOU误差不会夹在cost function中 truth_thresh = 1 random=1 # 如果为1,每次迭代图片大小随机从320到608,步长为32,如果为0,每次训练大小与输入大小一致 ...
Ⅲ. 标签配置文件——data/voc.names
head #自己需要探测的类别,一行一个 eye nose
Ⅳ. 修改数据格式——scr/data.c
data load_data_detection(int n, char **paths, int m, int w, int h, int boxes, int classes, float jitter, float hue, float saturation, float exposure) { char **random_paths = get_random_paths(paths, n, m); int i; data d = {0}; d.shallow = 0; d.X.rows = n; d.X.vals = calloc(d.X.rows, sizeof(float*)); d.X.cols = h*w; //灰阶图 //d.X.cols = h*w*3; //RGB图 ...
4)下载预训练的参数(卷积权重)
“darknet53.conv.74”是使用 Imagenet 数据集进行预训练:
wget https://pjreddie.com/media/files/darknet53.conv.74 # 下载预训练的网络模型参数
获取其他模型类似 darknet53.conv.74 的预训练权重
Ⅰ. 下载官方预训练模型的权重
https://pjreddie.com/darknet/yolo///COCO数据集训练的
https://pjreddie.com/darknet/imagenet///Imagenet数据集训练的
eg: wget https://pjreddie.com/media/files/yolov3-tiny.weights
Ⅱ. 转换为类似darknet.conv.74的预训练权重
https://github.com/AlexeyAB/darknet/blob/57e878b4f9512cf9995ff6b5cd6e0d7dc1da9eaf/build/darknet/x64/partial.cmd#L24
eg: ./darknet partial cfg/yolov3-tiny yolov3-tiny.weights yolov-tiny.conv.15 15
5)训练模型:
Ⅰ. 单GPU训练: ./darknet detector train <data_cfg> <train_cfg> <weights> -gpu <gpu_id>
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -i 1
Ⅱ. 多GPU训练: ./darknet detector train <data_cfg> <model_cfg> <weights> -gpus <gpu_list>
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -gpus 0,1,2,3
Ⅲ. 从checkpoint继续训练
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc.backup -gpus 0,1,2,3
Ⅳ. CPU训练:
./darknet detector train <data_cfg> <model_cfg> <weights> -nogpu
Ⅴ. 生成loss-iter曲线
在执行训练命令的时候加一下管道,tee一下log:
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg | tee result/log/training.log
将下面的python代码保存为drawcurve.py。并执行
python drawcurve.py training.log 0
~drawcurve.py
import argparse import sys import matplotlib.pyplot as plt def main(argv): parser = argparse.ArgumentParser() parser.add_argument("log_file", help = "path to log file" ) parser.add_argument( "option", help = "0 -> loss vs iter" ) args = parser.parse_args() f = open(args.log_file) lines = [line.rstrip("\n") for line in f.readlines()] # skip the first 3 lines lines = lines[3:] numbers = {'1','2','3','4','5','6','7','8','9','0'} iters = [] loss = [] for line in lines: if line[0] in numbers: args = line.split(" ") if len(args) >3: iters.append(int(args[0][:-1])) loss.append(float(args[2])) plt.plot(iters,loss) plt.xlabel('iters') plt.ylabel('loss') plt.grid() plt.show() if __name__ == "__main__": main(sys.argv)
6)训练过程:
训练的log格式如下:
Loaded: 4.533954 seconds Region Avg IOU: 0.262313, Class: 1.000000, Obj: 0.542580, No Obj: 0.514735, Avg Recall: 0.162162, count: 37 Region Avg IOU: 0.175988, Class: 1.000000, Obj: 0.499655, No Obj: 0.517558, Avg Recall: 0.070423, count: 71 Region Avg IOU: 0.200012, Class: 1.000000, Obj: 0.483404, No Obj: 0.514622, Avg Recall: 0.075758, count: 66 Region Avg IOU: 0.279284, Class: 1.000000, Obj: 0.447059, No Obj: 0.515849, Avg Recall: 0.134615, count: 52 1: 629.763611, 629.763611 avg, 0.001000 rate, 6.098687 seconds, 64 images Loaded: 2.957771 seconds Region Avg IOU: 0.145857, Class: 1.000000, Obj: 0.051285, No Obj: 0.031538, Avg Recall: 0.069767, count: 43 Region Avg IOU: 0.257284, Class: 1.000000, Obj: 0.048616, No Obj: 0.027511, Avg Recall: 0.078947, count: 38 Region Avg IOU: 0.174994, Class: 1.000000, Obj: 0.030197, No Obj: 0.029943, Avg Recall: 0.088889, count: 45 Region Avg IOU: 0.196278, Class: 1.000000, Obj: 0.076030, No Obj: 0.030472, Avg Recall: 0.087719, count: 57 2: 84.804230, 575.267700 avg, 0.001000 rate, 5.959159 seconds, 128 images
iter 总损失 平均损失 学习率 花费时间 参与训练的图片总数
Region | cfg文件中yolo-layer的索引 |
Avg IOU | 当前迭代中,预测的box与标注的box的平均交并比,越大越好,期望数值为1 |
Class | 标注物体的分类准确率,越大越好,期望数值为1 |
obj | 越大越好,期望数值为1 |
No obj | 越小越好,但不为零 |
.5R | 以IOU=0.5为阈值时候的recall; recall = 检出的正样本/实际的正样本 |
.75R | 以IOU=0.75为阈值时候的recall |
count | 正样本数目 |
Region 82 Avg IOU: 0.798032, Class: 0.559781, Obj: 0.515851, No Obj: 0.006533, .5R: 1.000000, .75R: 1.000000, count: 2 Region 94 Avg IOU: 0.725307, Class: 0.830518, Obj: 0.506567, No Obj: 0.000680, .5R: 1.000000, .75R: 0.750000, count: 4 Region 106 Avg IOU: 0.579333, Class: 0.322556, Obj: 0.020537, No Obj: 0.000070, .5R: 1.000000, .75R: 0.000000, count: 2
以上输出显示了所有训练图片的一个批次(batch),批次大小的划分根据我们在 .cfg 文件中设置的subdivisions参数。
在我使用的 .cfg 文件中 batch = 64 ,subdivision = 16,所以在训练输出中,训练迭代包含了16组,每组又包含了4张图片,跟设定的batch和subdivision的值一致。
但是此处有16*3条信息,每组包含三条信息,分别是:Region 82、Region 94、Region 106。
三个尺度上预测不同大小的框:
82卷积层 为最大的预测尺度,使用较大的mask,但是可以预测出较小的物体;
94卷积层 为中间的预测尺度,使用中等的mask;
106卷积层为最小的预测尺度,使用较小的mask,可以预测出较大的物体
每个batch都会有这样一个输出:
2706: 1.350835, 1.386559 avg, 0.001000 rate, 3.323842 seconds, 173184 images
batch 总损失 平均损失 学习率 花费时间 参与训练的图片总数 = 2706 * 64
三. 模型评价
1)更改配置文件:
Ⅰ. 模型参数(cfg/yoloc3.cfg):
batch=1
subdivisions=1
注:测评模型时batch、subdivisions必须为1 |
Ⅱ. 更改为批处理(example/detector.c):
①. 在detector.c中增加头文件:
#include <unistd.h> /* Many POSIX functions (but not all, by a large margin) */ #include <fcntl.h> /* open(), creat() - and fcntl() */
②. 在前面添加GetFilename(char *fullname)函数
#include <unistd.h> #include <fcntl.h> #include "darknet.h" #include <sys/stat.h> #include <stdio.h> #include <time.h> #include <sys/types.h> static int coco_ids[] = {1,2,3,4,5,6,7,8,9,10,11,13,14,15,16,17,18,19,20,21,22,23,24,25,27,28,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,
67,70,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90};
//产生的文件名与原文件名相同(去除路径和格式) char *GetFilename(char *fullname) { int from,to,i; char *newstr,*temp; if(fullname!=NULL){ //if not find dot if((temp=strchr(fullname,'.'))==NULL){ newstr = fullname; } else { from = strlen(fullname) - strlen(temp); to = (temp-fullname); //the first dot's index for (i=from; i<=to; i--){ if (fullname[i]=='.') break;//find the last dot } newstr = (char*)malloc(i+1); strncpy(newstr,fullname,i); *(newstr+i)=0; } } static char name[50] = {""}; char *q = strrchr(newstr,'/') + 1; strncpy(name,q,40); return name; }
③. 用下面代码替换detector.c文件的void test_detector函数(注意有3处要改成自己的路径)
void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen) { list *options = read_data_cfg(datacfg); char *name_list = option_find_str(options, "names", "data/names.list"); char **names = get_labels(name_list); image **alphabet = load_alphabet(); network *net = load_network(cfgfile, weightfile, 0); set_batch_network(net, 1); srand(2222222); double time; char buff[256]; char *input = buff; float nms=.45; int i=0; while(1){ if(filename){ strncpy(input, filename, 256); image im = load_image_color(input,0,0); 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; 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); free_detections(dets, nboxes); if(outfile) { save_image(im, outfile); } else{ save_image(im, "predictions"); #ifdef OPENCV cvNamedWindow("predictions", CV_WINDOW_NORMAL); if(fullscreen){ cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN); } show_image(im, "predictions"); cvWaitKey(0); cvDestroyAllWindows(); #endif } free_image(im); free_image(sized); if (filename) break; } else { printf("Enter Image Path: "); fflush(stdout); input = fgets(input, 256, stdin); if(!input) return; strtok(input, "\n"); list *plist = get_paths(input); char **paths = (char **)list_to_array(plist); printf("Start Testing!\n"); int m = plist->size; if(access("/home/xieqi/darknet/data/out_img",0)==-1)//修改成自己的路径 { if (mkdir("/home/xieqi/darknet/data/out_img",0777))//修改成自己的路径 { printf("creat file bag failed!!!"); } } for(i = 0; i < m; ++i){ char *path = paths[i]; image im = load_image_color(path,0,0); 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("Try Very Hard:"); printf("%s: Predicted in %f seconds.\n", path, what_time_is_it_now()-time); int nboxes = 0; 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); free_detections(dets, nboxes); if(outfile){ save_image(im, outfile); } else{ char b[2048]; sprintf(b,"/home/xieqi/darknet/data/out_img/%s",GetFilename(path));//修改成自己的路径 save_image(im, b); printf("save %s successfully!\n",GetFilename(path)); #ifdef OPENCV cvNamedWindow("predictions", CV_WINDOW_NORMAL); if(fullscreen){ cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN); } show_image(im, "predictions"); cvWaitKey(0); cvDestroyAllWindows(); #endif } free_image(im); free_image(sized); if (filename) break; } } } }
④. validate_detector_recall
函数定义和调用改为:
void validate_detector_recall(char *datacfg, char *cfgfile, char *weightfile) validate_detector_recall(datacfg, cfg, weights);
⑤.validate_detector_recall
内的plist
和paths
的如下初始化代码:
list *plist = get_paths("data/voc.2007.test"); char **paths = (char **)list_to_array(plist);
修改为:
list *options = read_data_cfg(datacfg); char *valid_images = option_find_str(options, "valid", "data/train.list"); list *plist = get_paths(valid_images); char **paths = (char **)list_to_array(plist);
Ⅲ. 在darknet下重新make
make clean make
2). 测试单张图片
Ⅰ. ./darknet detector test <data_cfg> <models_cfg> <weights> <image_path> # 本次测试无opencv支持
Ⅱ. <models_cfg>文件中batch和subdivisions两项必须为1;
Ⅲ. 测试时还可以用-thresh
和-hier
选项指定对应参数;
Ⅳ. 结果都保存在./data/out_img 文件夹下
./darknet detector test cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights Eminem.jpg # 测试单张图片
3). 生成预测结果
Ⅰ. 批量测试——输出文本检测结果
./darknet detector valid cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights -i 1 -out ""
① ./darknet detector valid <data_cfg> <models_cfg> <weights>;
② 结果生成在<data_cfg>的指定的目录下以<out_file>开头的若干文件中,若<data_cfg>没有指定results,那么默认为<darknet_root>/results;
③ <models_cfg>文件中batch和subdivisions两项必须为1;
④ 若-out 未指定字符串,则在results文件夹下生成comp4_det_test_[类名].txt文件并保存测试结果;
⑤ 本次实验在results文件夹下生成 [类名].txt 文件;
Ⅱ. 批量测试——输出图片检测结果
./darknet detector test cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights -i 2 #enter
Enter Image Path: data/voc/2007_test.txt
① ./darknet detector test <data_cfg> <models_cfg> <weights>;
② <models_cfg>文件中batch和subdivisions两项必须为1;
③ 本次实验结果,在<darknet_root>/data/out_img文件夹下(detector.c/test_detector函数更改的路径),文件名以原图片名前6位命名(detector.c/GetFilename函数定义的位数);
4). 计算recall(执行这个命令需要修改detector.c文件,修改信息请参考“detector.c修改”)
Ⅰ. ./darknet detector recall <data_cfg> <test_cfg> <weights>;
Ⅱ. <test_cfg>文件中batch和subdivisions两项必须为1;
Ⅲ. 输出在stderr里,重定向时请注意;
Ⅳ. RPs/Img、IOU、Recall都是到当前测试图片的均值;
Ⅴ. detector.c中对目录处理有错误,可以参照validate_detector对validate_detector_recall最开始几行的处理进行修改;
./darknet detector valid cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_20000.weights
最后得到的log如下:
306 746 783 RPs/Img: 21.45 IOU: 75.59% Recall:95.27% 307 748 785 RPs/Img: 21.43 IOU: 75.62% Recall:95.29% 308 750 787 RPs/Img: 21.42 IOU: 75.59% Recall:95.30% 309 752 789 RPs/Img: 21.43 IOU: 75.62% Recall:95.31% 310 754 791 RPs/Img: 21.44 IOU: 75.63% Recall:95.32%
No. Correct total
输出的具体格式为:
Number | 处理的第几张图 |
Correct |
正确的识别出了多少bbox。这个值算出来的步骤是这样的,丢进网络一张图片,网络会预测出很多bbox,每个bbox都有其置信概率,概率大于threshold的bbox与实际的bbox,也就是labels中txt的内容计算IOU, 找出IOU最大的bbox,如果这个最大值大于预设的IOU的threshold,那么correct加一 |
Total | 实际有多少个bbox |
Rps/img | 平均每个图片会预测出来多少个bbox |
IOU | 预测出的bbox和实际标注的bbox的交集 除以 他们的并集。显然,这个数值越大,说明预测的结果越好 |
Recall | 召回率, 检测出物体的个数 除以 标注的所有物体个数。通过代码我们也能看出来就是Correct除以Total的值 |
error:
本次实验出现 IOU:inf% —— 交并比 数值爆炸
打印bbox详细信息(修改detector.c/validate_detector_recall)
for (j = 0; j < num_labels; ++j) { ++total; box t = {truth[j].x, truth[j].y, truth[j].w, truth[j].h}; printf("truth x=%f, y=%f, w=%f,h=%f\n",truth[j].x,truth[j].y,truth[j].w,truth[j].h);//添加代码 float best_iou = 0; for(k = 0; k < l.w*l.h*l.n; ++k){ float iou = box_iou(dets[k].bbox, t); if(dets[k].objectness > thresh && iou > best_iou){ printf("predict=%f x=%f, y=%f, w=%f,h=%f\n",dets[k].objectness,dets[k].bbox.x,dets[k].bbox.y,dets[k].bbox.w,dets[k].bbox.h);//添加代码 best_iou = iou; } }
由显示结果可以看出,预测框的中心坐标x和y出现-nan
解决方法:修改函数
void validate_detector_recall(char *datacfg, char *cfgfile, char *weightfile) { ... //layer l = net->layers[net->n-1]; //注释掉 ... for(k = 0; k < l.w*l.h*l.n; ++k){ //改为for(k = 0; k < nboxes; ++k){ ...
5). 计算AP、mAP
先使用 3)-Ⅰ计算出验证集结果,再使用py-faster-rcnn下的voc_eval.py计算AP、mAP
Ⅰ. 下载voc_eval.py到 darknet 的根目录
voc_eval.py下载地址:https://github.com/rbgirshick/py-faster-rcnn/blob/master/lib/datasets/voc_eval.py
根据标签修改voc_eval.py的内容
.xml文件结构中 ①若无pose 22 #obj_struct['pose'] = obj.find('pose').text # 注释掉 ②若无truncated 23 #obj_struct['truncated'] = int(obj.find('truncated').text) # 注释掉 ③若无difficult 24 #obj_struct['difficult'] = int(obj.find('difficult').text) # 注释掉 137 #if use_diff: # 注释掉 138 #difficult = np.array([False for x in R]).astype(np.bool) #注释掉 139 #else: # 注释掉 140 #difficult = np.array([x['difficult'] for x in R]).astype(np.bool) # 注释掉 142 npos = npos +len(R) # 更改npos = npos + sum(~difficult) 145 #'difficult': difficult, # 注释掉 197 #if not R['difficult'][jmax]: #注释掉
Ⅱ. 计算单类AP
①. 在darknet根目录下新建computer_mAP.py
#!/home/xieqi/anaconda2/envs/py2.7/bin python2.7 # -*- coding:utf-8 -*- from voc_eval import voc_eval print(voc_eval('/home/xieqi/darknet/results/{}.txt', '/home/xieqi/project/traffic_object_detection/voc_data/Annotations/{}.xml',
'/home/xieqi/project/traffic_object_detection/voc_data/ImageSets/Main/test.txt', 'vehicle', '/home/xieqi/project/traffic_object_detection/result/')) # "第一个参数为detector valid 按类别分类后的txt路径" # "第二个参数为验证集对应的xml标签路径" # "第三个为验证集txt文本路径,内容必须是无路径无后缀的图片名" # "第四个为待验证的类别名" # "第五个为pkl文件保存的路径"
②. 用python2执行computer_mAP.py
① 重复执行,检测其他类别需要删除生成的annots.pkl文件或改变computer_mAP.py中pkl文件保存的路径
② 输出两个array(),分别为rec和prec,最后一个数字为单类AP。
Ⅲ. 计算总mAP
在darknet根目录下新建computer_all_mAP.py
#!/home/xieqi/anaconda2/envs/py2.7/bin python2.7 # -*- coding:utf-8 -*- from voc_eval import voc_eval import os current_path = os.getcwd() results_path = current_path+"/results" sub_files = os.listdir(results_path) mAP = [] for i in range(len(sub_files)): class_name = sub_files[i].split(".txt")[0] rec, prec, ap = voc_eval('/home/xieqi/darknet/results/{}.txt',
'