一、前言
本文主要使用yolo v2 训练自己的车牌图片数据,并能够框出测试图片中存在的车牌区域,也即车牌检测。本文参考了博文http://m.blog.csdn.net/qq_34484472/article/details/73135354和http://blog.csdn.net/zhuiqiuk/article/details/72722227
二、准备工作
首先需要下载正确配置好darknet, 使用./darknet detect cfg/yolo.cfg yolo.weights data/dog.jpg 命令可得检测结果。本文主要是为了检测车牌区域,在darknet下新建一文件夹plate_imgae,其中存放train和val、pic三个文件夹,train文件夹下为1296张训练图片,val文件夹下为196张验证图片,pic为236张待检测的图片,图片格式均为jpg格式,训练模型主要用到train和val这两个文件夹。
三、制作标签文件
yolo v2使用VOC格式的数据集,故这里首先需要对训练集及验证集的图片进行标注,每张图片均可得到相应的xml文件,然后再将xml文件转化为txt标签文件。参考这里http://blog.csdn.net/jesse_mx/article/details/53606897 安装标注工具LabelImg,安装完成后,./labelImg.py打开标注工具,先对train文件夹内的图片作标注,在plate_image文件夹下新建train_xml文件夹,用于存放生成的xml文件。所生成的xml文件如下所示:
同样的方法,也在在plate_image文件夹下新建val_xml文件夹,保存验证集的xml标注文件。这样就有VOC格式标记的XML文件,接下来需要将xml文件转换为txt文件,使用python转换,将train_xml中的xml文件,转换为txt保存于文件夹train_txt下;同理,将val_xml中的xml文件转换后保存于val_txt文件夹下
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
xml_label_Dir = 'train_xml' #需转换的xml路径
txt_label_Dir = 'train_txt/' #转换得的txt文件保存路径
def convert(size, box): #归一化操作
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]
h = box[3] - box[2]
x = x*dw
w = w*dw
y = y*dh
h = h*dh
return (x,y,w,h)
if not os.path.exists(txt_label_Dir):
os.makedirs(txt_label_Dir)
for rootDir,dirs,files in os.walk(xml_label_Dir):
for file in files:
file_name = file.split('.')[0]
out_file = open(txt_label_Dir+'%s.txt'%(file_name),'w')
in_file = open("%s/%s"%(rootDir,file))
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'):
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("0" + " " + " ".join([str(a) for a in bb]) + '\n') #only one class,index 0
out_file.close()
接着还需要获得训练集图片的绝对路径train_imgae_path.txt和验证集的绝对路径val_image_path.txt,同样使用以下python代码
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
TrainDir = '/home/jyang/darknet/plate_image/train'
out_file = open('train_image_path.txt','w')
for root,dirs,files in os.walk(TrainDir):
for file in files:
out_file.write('%s/%s\n'%(root,file))
out_file.close()
YOLO是直接通过替换原图片绝对路径的后缀名来找到对应标记文件的。比如原图片的绝对路径为/home/ubuntu/data/train/1.jpg。则YOLO将会直接认为其对应的标记文件路径为/home/ubuntu/data/train/1.txt。所以,我们将之前生成的txt文件都放到相应的图片路径下,即将train_txt下txt文件放到train文件夹下,将val_txt下txt文件放到val文件夹下
看看plate_image下都有啥。
四、修改配置文件
(1)创建names文件
在YOLO主目录的data文件夹下,创建一个.names文件,文件名任意,我的是myClass.names,在该文件中写入所有类别的名称,每一类占一行,我只检测车牌,故只在第一行写licence.
(2)修改data文件
修改darknet主目录下的 cfg/voc.data文件,修改如下:
classes= 1 #只有一个类别
train = /home/jyang/darknet/plate_image/train_image_path.txt #训练集的绝对路径
valid = /home/jyang/darknet/plate_image/val_image_path.txt #验证集的绝对路径
names = data/myClass.names
backup = /home/jyang/darknet/plate_image/backup #训练得的模型保存路径
(3)修改cfg文件
修改darknet主目录下的cfg/yolo-voc.cfg。
1.[region]层中classes改为1
2.[region]层上方的[convolution]层中,filters的数量改成(classes+coords+1)*NUM。我这里改成了(1+4+1)*5=30.
五、 修改src/yolo.c文件
1.第13行改成 char *voc_names[] = {"licence"}; //如果你是一类的话,就改成这样,如果你有多类,自己根据需求改。
2.第322行draw_detections函数中,最后一个参数由20改成你的类别数,我这里是1
3.第354行demo函数中,倒数第三个参数由20改成你的类别数,我这里是1
4.第17行改成 char *train_images = "<你的路径>/train_imgae_path";
5.第18行改成 char *backup_directory = "你的路径/backup/";
6.第121行改成 char *base = "results/comp4_det_test_"
7.第123行改成 list *plist = get_paths("<你的路径>/val_image_path.txt");
8.第209行改成 char *base = "results/comp4_det_test_"
9.第210行改成 list *plist = get_paths("<你的路径>/val_image_path.txt");
10.将src/yolo_kernels.cu文件中,第62行draw_detections函数最后一个参数由20改成你的类别数,我这里是1.
11.scripts/voc_label.py 文件中 ,位置第9行改成:classes=[“licence”],因为我只有一类
12.将src/detector.c文件中,第372行改成 list *plist = get_paths("<你的路径>/val_image_path.txt");,第542行option_find_int函数的最后一个参数由20改成1.
六、重新编译darknet yolo
进入darknet主目录,make clean后 make -j8
七、下载预训练文件cfg/darknet19_448.conv.23
为了加快训练速度,下载官方提供的预训练模型,保存至cfg下。下载地址为
http://pjreddie.com/media/files/darknet19_448.conv.23
八、训练
在darknet文件夹路径下运行命令:
./darknet detector train cfg/voc.data cfg/yolo-voc.cfg cfg/darknet19_448.conv.23
系统默认会迭代45000次batch,如果需要修改训练次数,进入cfg/yolo_voc.cfg修改max_batches的值。
九、训练过程输出log
参考自这篇文章http://blog.csdn.net/renhanchi/article/details/71077830?locationNum=13&fps=1,以下摘自这篇文章
Region Avg IOU: 这个是预测出的bbox和实际标注的bbox的交集 除以 他们的并集。显然,这个数值越大,说明预测的结果越好
Avg Recall: 这个表示平均召回率, 意思是 检测出物体的个数 除以 标注的所有物体个数。
count: 标注的所有物体的个数。 如果 count = 6, recall = 0.66667, 就是表示一共有6个物体(可能包含不同类别,这个不管类别),然后我预测出来了4个,所以Recall 就是 4 除以 6 = 0.66667
有一行跟上面不一样的,最开始的是iteration次数,然后是train loss,然后是avg train loss, 然后是学习率, 然后是一batch的处理时间, 然后是已经一共处理了多少张图片。 重点关注 train loss 和avg train loss,这两个值应该是随着iteration增加而逐渐降低的。如果loss增大到几百那就是训练发散了,如果loss在一段时间不变,就需要降低learning rate或者改变batch来加强学习效果。当然也可能是训练已经充分。这个需要自己判断
十、评估训练得的模型
训练了3天,得到了迭代30000次的权值,在plate_image/backup/下生成了yolo-voc_30000.weights,当然还有其他迭代次数的权值,使用以下语句可以测试单张图片
./darknet detector test cfg/voc.data cfg/yolo-voc.cfg plate_image/backup/yolo-voc_30000.weights plate_image/pic/099999.jpg
显示的检测结果为
训练的时候只是用到了训练集,评估检测性能才会使用到验证集。使用以下指令评估
root@computer:/home/jyang/darknet# ./darknet detector recall cfg/voc.data cfg/yolo-voc.cfg plate_image/backup/yolo-voc_30000.weights
根据参考的第2篇博文,使用该指令的时候,会调用src/detector.c里的validate_detector_recall函数,将其中的阈值从0.001修改为0.25,防止过多框被识别出,将432行代码修改为如下:
fprintf(stderr, "Id:%5d Correct:%5d Total%5d\tRPs/Img: %.2f\tIOU: %.2f\tRecall:%.2f%%\tPrecision:%.2f%%\n", i,
correct, total, (float)proposals/(i+1), avg_iou*100/total, 100.*correct/total ,100.*correct/proposals);
重新make之后,运行结果为:
可见196张图片,准确率有97%以上。
结语
本文只是使用了yolo v2 检测了车牌,后续对框下来的车牌图片作识别,请看《yolo v2之车牌检测后续识别字符(一)》