理论知识
darknet安装
在darknet安装之前,需要预先装好cuda、cudnn和opencv环境。也可以不用装GPU版本直接编译运行使用cpu版本,但是,为了体现更好的处理效率,推荐使用GPU。
安装方法直接按照官网教程就可以,链接如下:
车型识别
车型识别目标: 要精确识别给定车辆图片的车型信息
零碎知识点
召回率和准确率<https://www.zhihu.com/question/19645541/answer/91694636>
数据集准备与处理
车型数据集共有1500张车辆照片,主要为汽车、公共汽车、卡车三种类型,拍摄时尽可能从不同角度、不同场景、不同环境下采集图片,初次之外,每种类型车型的图片量尽可能相差不大。
- 数据标记
注意:标注图片时,要时常查看标注的数据是否已经保存,并且要非常注意要勾选标注图片的类型
-
xml文件
[外链图片转存失败(img-FuNiXWdz-1568511457921)(/home/gavin/NoteBook/学习汇报/DeepinScreenshot_select-area_20190516185547.png)]
注意:一定要生成指定的XML格式文件,此次主要是根据pascal xml数据制作数据标签。
-
生成标签
VOCdevkit
└── VOC2018
├── Annotations #存放标记生成的XML文件
├── ImageSets #存放训练集和验证集的照片信息(照片的名称)
├── JPEGImages #存放车型数据集
└── labels # 每张样本的标签labels文件格式:
0 0.5467489919354839 0.4299395161290323 0.8933971774193548 0.5191532258064516
- object-class:是指对象的索引,从0开始,具体代表哪个对象去obj.names配置文件中按索引查,初次之外,每个txt文件可以有多个boundbox的信息,表示圈定的图片不是一个单一类。
- x,y:是一个坐标,需要注意的是它可不是对象左上角的坐标,而对象中心的坐标
- width,height:是指对象的宽高
voc_label.py 解析
import xml.etree.ElementTree as ET import pickle import os from os import listdir, getcwd from os.path import join sets=[('2018', 'train'), ('2018', 'val')] classes = ["car", "bus", "truck"] #设置分类车型 def convert(size, box): #此函数主要生成labels文件中归一化的值 dw = 1./(size[0]) #用于归一化 dh = 1./(size[1]) x = (box[0] + box[1])/2.0 #找出对象的中心点坐标 y = (box[2] + box[3])/2.0 w = box[1] - box[0] # 得到真是的boundbox的长宽信息 h = box[3] - box[2] x = x*dw #归一化 w = w*dw y = y*dh h = h*dh return (x,y,w,h) def convert_annotation(year, image_id):#解析XMl文件 in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id)) out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w') tree=ET.parse(in_file) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) for obj in root.iter('object'): difficult = obj.find('difficult').text cls = obj.find('name').text #查找是不是我们自己的类 if cls not in classes or int(difficult)==1:#跳过 continue cls_id = classes.index(cls)#得到类别标签 xmlbox = obj.find('bndbox') b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text)) bb = convert((w,h), b) out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') wd = getcwd()#获取当前路径 #1.创建labels文件夹 for year, image_set in sets: #此处第一个循环训练集 第二个循环验证集 if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)): os.makedirs('VOCdevkit/VOC%s/labels/'%(year)) #2.得到图片的id image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split() #3.创建训练集和验证集文件夹 list_file = open('%s_%s.txt'%(year, image_set), 'w') #4.创建绝对路径下的图片文件路径 for image_id in image_ids: list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id)) #5.xml 转 label convert_annotation(year, image_id) list_file.close() os.system("cat 2018_train.txt 2018_val.txt > train.txt")#扩充训练集数据量
-
数据预处理
voc.data
classes= 3 #分类类型 train = /home/gavin/Machine/darknet/scripts/train.txt #训练集 valid = /home/gavin/Machine/darknet/scripts/2018_val.txt #验证集 names = data/voc.names #类别信息 backup = backup #存放训练权重
-
yolov3-tiny.cfg
考虑的笔记本硬件的不足,选择了yolov3-tiny版网络模型,缺点:会丧失一定的准确度
[net] # Testing #batch=1 #subdivisions=1 Training batch=2 subdivisions=1 width=416 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 = 500200 policy=steps steps=400000,450000 scales=.1,.1 ...... [convolutional] size=1 stride=1 pad=1 filters=24 #classes*8 activation=linear [yolo] mask = 3,4,5 anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 classes=3#classes num=6 jitter=.3 ignore_thresh = .7 truth_thresh = 1 random=1 [route] layers = -4 [convolutional] batch_normalize=1 filters=128 size=1 stride=1 pad=1 activation=leaky [upsample] stride=2 [route] layers = -1, 8 [convolutional] batch_normalize=1 filters=256 size=3 stride=1 pad=1 activation=leaky [convolutional] size=1 stride=1 pad=1 filters=24 activation=linear [yolo] mask = 0,1,2 anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 classes=3 num=6 jitter=.3 ignore_thresh = .7 truth_thresh = 1 random=1
网络模型
训练模型
-
开始训练
darknet : sudo ./darknet detector train cfg/voc.data cfg/yolov3-tiny.cfg
本次训练总共7万次,已经达到了不错的效果
-
日志文件解析
Region 16 Avg IOU: 0.100322, Class: 0.425682, Obj: 0.461557, No Obj: 0.488897, .5R: 0.000000, .75R: 0.000000, count: 4 Region 23 Avg IOU: -nan, Class: -nan, Obj: -nan, No Obj: 0.534580, .5R: -nan, .75R: -nan, count: 0 2: 675.922241, 673.001831 avg, 0.000000 rate, 0.177072 seconds, 8 images Loaded: 0.315449 seconds Region 16 Avg IOU: 0.142092, Class: 0.463787, Obj: 0.288284, No Obj: 0.489365, .5R: 0.000000, .75R: 0.000000, count: 4 Region 23 Avg IOU: -nan, Class: -nan, Obj: -nan, No Obj: 0.534735, .5R: -nan, .75R: -nan, count: 0 3: 673.221313, 673.023804 avg, 0.000000 rate, 0.173940 seconds, 12 images Loaded: 0.325054 seconds Region 16 Avg IOU: 0.293639, Class: 0.411060, Obj: 0.413394, No Obj: 0.489323, .5R: 0.000000, .75R: 0.000000, count: 4
其中每行的参数意义如下:
Avg IOU:当前迭代中,预测的box与标注的box的平均交并比,越大越好,期望数值为1;
Class: 标注物体的分类准确率,越大越好,期望数值为1;
obj: 越大越好,期望数值为1;
No obj: 越小越好;
.5R: 以IOU=0.5为阈值时候的recall; recall = 检出的正样本/实际的正样本
0.75R: 以IOU=0.75为阈值时候的recall;
count:正样本数目。
注:存在nan值说明该子批次没有预测到正样本,在训练开始时候有出现是正常现象特别的每一个批次结束之后会输出一下结果:
3: 673.221313, 673.023804 avg, 0.000000 rate, 0.173940 seconds, 12 images
第几批次,总损失,平均损失,当前学习率,当前批次训练时间,目前为止参与训练的图片总数 -
检测图片
3.1 批量化测试效果
darknet: ./darknet detector valid cfg/voc.data cfg/yolov3-tiny.cfg backup/yolov3-tiny_70000.weights
注意 : 此时应该修改yolov3-tiny.cfg文件中的batch和subminibatch为1
测试结果会存放在results文件夹下:如下
comp4_det_test_bus.txt
comp4_det_test_car.txt
comp4_det_test_truck.txt
yolo_valid.txtbus.txt
yolo_valid.txtcar.txt
yolo_valid.txttruck.txt当打开其中comp4_det_test_bus.txt:
1039 0.008945 1075.546509 642.225098 1427.953003 852.698364
0822 0.005433 578.461548 576.750000 2930.228027 1478.208252
0860 0.987352 133.620972 325.751465 3403.745117 2544.819336
图片名称 该类置信度 坐标信息3.2 单张测试效果
darknet: sudo ./darknet detector test cfg/voc.data cfg/yolov3-tiny.cfg backup/yolov3-tiny_70000.weights
汽车检测效果:
准确率:
卡车检测效果:
准确率:
公交车检测效果:
准确率:
-
对于一些复杂场景下的检测效果
覆盖车辆下的检测效果
检测结果:未识别
只有部分车身检测效果
检测结果:未识别
多车辆下的检测效果
检测结果:此时的car 的准确率只有57%,并且对其它车辆不能够进行检测
黑夜下的检测效果
检测结果:未识别
结论:由于本次采集的数据未能够采集到特殊条件下的车辆照片,尤其是黑夜和不同角度下的车辆数据,而只是简单的采集了大多数正面和光线条件比较好的情况下的车辆照片,造成了对于复杂环境下未能检测的效果。后期需要如果能够加大数据集的囊括性,便能够达到很好的检测效果。
数据分析
-
提取数据
import inspect import os import random import sys def extract_log(log_file,new_log_file,key_word): with open(log_file, 'r') as f: with open(new_log_file, 'w') as train_log: #f = open(log_file) #train_log = open(new_log_file, 'w') for line in f: # 去除多gpu的同步log if 'Syncing' in line: continue # 去除除零错误的log if 'nan' in line: continue if key_word in line: train_log.write(line) f.close() train_log.close() extract_log('yolov3.log','train_log_loss.txt','images') extract_log('yolov3.log','train_log_iou.txt','IOU')
-
绘制数据图
import pandas as pd import numpy as np import matplotlib.pyplot as plt #%matplotlib inline lines =79755 #改为自己生成的train_log_loss.txt中的行数 result = pd.read_csv('train_log_loss.txt', skiprows=[x for x in range(lines) if ((x%10!=9) |(x<1000))] ,error_bad_lines=False, names=['loss', 'avg', 'rate', 'seconds', 'images']) result.head() result['loss']=result['loss'].str.split(' ').str.get(1) result['avg']=result['avg'].str.split(' ').str.get(1) result['rate']=result['rate'].str.split(' ').str.get(1) result['seconds']=result['seconds'].str.split(' ').str.get(1) result['images']=result['images'].str.split(' ').str.get(1) result.head() result.tail() # print(result.head()) # print(result.tail()) # print(result.dtypes) print(result['loss']) print(result['avg']) print(result['rate']) print(result['seconds']) print(result['images']) result['loss']=pd.to_numeric(result['loss']) result['avg']=pd.to_numeric(result['avg']) result['rate']=pd.to_numeric(result['rate']) result['seconds']=pd.to_numeric(result['seconds']) result['images']=pd.to_numeric(result['images']) result.dtypes fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.plot(result['avg'].values,label='avg_loss') # ax.plot(result['loss'].values,label='loss') ax.legend(loc='best') #图列自适应位置 ax.set_title('The loss curves') ax.set_xlabel('batches') fig.savefig('avg_loss') # fig.savefig('loss')
2.1 loss 图:
2.2 IOU图:
2.3 Ap:
classes | car | bus | truck |
---|---|---|---|
Ap | 0.33 | 0.29 | 0.32 |
mAP = 0.313
Recall:
2.3.1 预备工作
- 修改文件
修改darknet根文件下的detector.c
list *plist = get_paths("data/voc.2007.test"); char **paths = (char **)list_to_array(plist); //修改为如下 list *plist = get_paths("scripts/train.txt"); char **paths = (char **)list_to_array(plist);
- 如果出现nan% 修改
//在在detector.c 542行 for(k = 0; k <l.w*l.h*l.n; ++k) //修改为 for(k = 0; k < nboxes; ++k)
- 测试命令
./darknet detector recall <data_cfg> <test_cfg> <weights>
2.3.2 显示结果
数据:
Number Correct Total Rps/Img IOU Recall
2940 2933 2940 RPs/Img: 1.42 IOU: 85.75% Recall:99.76%
2941 2934 2941 RPs/Img: 1.42 IOU: 85.75% Recall:99.76%
2942 2935 2942 RPs/Img: 1.42 IOU: 85.75% Recall:99.76%
2943 2936 2943 RPs/Img: 1.42 IOU: 85.75% Recall:99.76%
2944 2937 2944 RPs/Img: 1.42 IOU: 85.76% Recall:99.76%
2945 2938 2945 RPs/Img: 1.42 IOU: 85.76% Recall:99.76%
2946 2939 2946 RPs/Img: 1.42 IOU: 85.76% Recall:99.76%
2947 2940 2947 RPs/Img: 1.42 IOU: 85.76% Recall:99.76%
分析:
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的值。
总结
本次,从darknet的安装到yolo模型的实战,成功的实现了从理论到实战的转换,虽然此次检测结果未能对一些复杂环境下的车型进行识别,但是,还是能够实现对大多数条件比较好的车型进行识别,并且准确率比较高。这次实际操作也能更加对yolov3的论文中的一些知识点进行深刻理解。初次之外,通过这次的练习,也学会了一些其他知识技能,比如图片数据的标记与处理、opencv的使用等等。或许这一次的项目实战在整体性完整性上有许多未能考虑点,有许多知识点暂时还未能触及到或尚未真正理解明白,这就需要我接下来认真的重新看有关方面的知识来进行加强。
总之,这次理论与实践相的融合,让我学习到了很多知识。