关于训练YOLO v2模型过程中的经验总结。

数据集:

考虑到自己收集图片并标注,工作量较大,当前主要将包含行人的图片从已有的数据集PASCAL VOC中抽取出来,按照\VOCdevkit\VOC2007\ImageSets\Main中相应类别的txt文件,提取person_train.txt、person_val.txt、person_test.txt、person_trainval.txt中的正样本文件(包括JPEGImages中的图片和Annotations中的xml标注文件);

新建文件夹VOC_person,在其下建立文件夹Annotations、ImageSets、JPEGImages(如下图所示),将提取的图片和标注xml文件分别放置JPEGImages和Annotations中,在ImageSets中建立文件夹Main。

INRIA 行人数据集行人检测滑动窗口python_xml


生成ImageSets\Main里的四个txt文件

通过xml名字(或图片名),生成四个txt文件,即:test.txt train.txt trainval.txt val.txt

txt文件中的内容为:

000002

000005

000028

即图片名字(无后缀),test.txt是测试集,train.txt是训练集,val.txt是验证集,

trainval.txt是训练和验证集.VOC2007中,trainval大概是整个数据集的50%,test也大概是整个数据集的50%;train大概是trainval的50%,val大概是trainval的50%,当数据集较小时,可减少val和test的数量;

生成四个txt文件文件的代码为:

%该代码根据已生成的xml,制作VOC2007数据集中的trainval.txt;train.txt;test.txt和val.txt  
%trainval占总数据集的50%,test占总数据集的50%;train占trainval的50%,val占trainval的50%;  
%上面所占百分比可根据自己的数据集修改,如果数据集比较少,test和val可少一些  
%%  
%注意修改下面四个值  
xmlfilepath='E:\Image Sets\VOC_person\Annotaions';  
txtsavepath='E:\Image Sets\VOC_person\ImageSets\Main\';  
trainval_percent=0.8;%trainval占整个数据集的百分比,剩下部分就是test所占百分比  
train_percent=0.8;%train占trainval的百分比,剩下部分就是val所占百分比  


%%  
xmlfile=dir(xmlfilepath);  
numOfxml=length(xmlfile)-2;%减去.和..  总的数据集大小  


trainval=sort(randperm(numOfxml,floor(numOfxml*trainval_percent)));  
test=sort(setdiff(1:numOfxml,trainval));  


trainvalsize=length(trainval);%trainval的大小  
train=sort(trainval(randperm(trainvalsize,floor(trainvalsize*train_percent))));  
val=sort(setdiff(trainval,train));  


ftrainval=fopen([txtsavepath 'trainval.txt'],'w');  
ftest=fopen([txtsavepath 'test.txt'],'w');  
ftrain=fopen([txtsavepath 'train.txt'],'w');  
fval=fopen([txtsavepath 'val.txt'],'w');  


for i=1:numOfxml  
    if ismember(i,trainval)  
        fprintf(ftrainval,'%s\n',xmlfile(i+2).name(1:end-4));  
        if ismember(i,train)  
            fprintf(ftrain,'%s\n',xmlfile(i+2).name(1:end-4));  
        else  
            fprintf(fval,'%s\n',xmlfile(i+2).name(1:end-4));  
        end  
    else  
        fprintf(ftest,'%s\n',xmlfile(i+2).name(1:end-4));  
    end  
end  
fclose(ftrainval);  
fclose(ftrain);  
fclose(fval);  
fclose(ftest);

将数据集转换成YOLO训练所需标注格式,并生成训练、验证、测试图片路径文件:

(1)将数据集拷贝到darknet-master\data下:如VOC_person数据集

INRIA 行人数据集行人检测滑动窗口python_txt文件_02


打开darknet-master\scripts下的voc_label.py文件,修改代码:

#coding:utf-8
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

#sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]

#classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
sets = ["train","val","test"]
classes = ["person"]#改成自己的类别

#voc_label.py自带的函数,未做修改
def convert(size, box): 
    dw = 1./(size[0])
    dh = 1./(size[1])
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    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)

def convert_annotation(image_id):
   #in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    in_file = open('/home/yi_miao/darknet-master/data/VOC_person/Annotaions/%s.xml'%(image_id))
    out_file = open('/home/yi_miao/darknet-master/data/VOC_person/labels/%s.txt'%(image_id), 'w')
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')  #访问size标签的数据
    w = int(size.find('width').text)  #读取size标签中宽度的数据
    h = int(size.find('height').text) #读取size标签中高度的数据

    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()

for image_set in sets:
    if not os.path.exists('/home/yi_miao/darknet-master/data/VOC_person/labels/'):
        os.makedirs('/home/yi_miao/darknet-master/data/VOC_person/labels/')
    image_ids = open('/home/yi_miao/darknet-master/data/VOC_person/ImageSets/Main/%s.txt'%(image_set)).read().strip().split()
    list_file = open('person_%s.txt'%(image_set), 'w')
    for image_id in image_ids:
        list_file.write('/home/yi_miao/darknet-master/data/VOC_person/JPEGImages/%s.jpg\n'%(image_id))
        convert_annotation(image_id)
    list_file.close()

os.system("cat person_train.txt person_val.txt > train.txt")
#os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")

注意根据自己的实际情况修改数据集的路径名。
(2)终端进入darknet-master\svripts,执行

python voc_label.py

此后可以看到,VOC_person里多了一个labels文件夹(如下图所示),里面有每张图片的标注文件(文件内容形如0 0.488888888889 0.289256198347 0.977777777778 0.429752066116),其中第一个数字表示类别标签(0表示person,也即前面写的classes的顺序),后面四个数为包围框信息,做了转换;

INRIA 行人数据集行人检测滑动窗口python_数据集_03


darknet-master\scripts下多了person_train.txt、person_val.txt和person_test.txt,三个文件(如下),这三个文件是数据集中图片的路径。由于yolo训练只需要一个txt文件,文件中包含所有你想要训练的图片的路径,因此,我们可以用person_train.txt、person_val.txt包含的图片均用来训练,因此执行(voc_label.py中最后两行为相应代码):

cat person_train.txt person_val.txt > train.txt

现在,已经将数据集中的训练集和验证集全都放在一个txt文件中,这些图片用来作为YOLO的训练图片。 将上述txt文件移至VOC_person文件夹下。

INRIA 行人数据集行人检测滑动窗口python_xml_04

修改训练代码:
(1)在darknet-master\data下新建文件personTrain.names文件,内容为:

person

(2)在darknet-master\cfg下新建 personTrain.data文件,内容为:

classes= 1
train  = /home/yi_miao/darknet-master/data/VOC_person/train.txt
valid  = /home/yi_miao/darknet-master/data/VOC_person/person_test.txt
names = data/personTrain.names
backup = /home/yi_miao/darknet-master/backup

(3)修改darknet-master\src\yolo.c文件:

  • 检测目标类别改成自己的数据集类别:
char *voc_names[] = {"person"};
  • Train_yolo函数:
char *train_images = "/home/yi_miao/darknet-master/data/VOC_person/train.txt";
    char *backup_directory = "/home/yi_miao/darknet-master/backup/";

train_images应该指向我们刚得到的train.txt;backup_directory指向的路径是训练过程中生成的weights文件保存的路径(可以在darknet-master下新建文件夹backup然后指向它)。这两个路径按自己系统修改即可;

  • validate_yolo函数:
char *base = "/home/yi_miao/darknet-master/results/comp4_det_test_";
    list *plist = get_paths("/home/yi_miao/darknet-master/data/VOC_person/person_test.txt");
  • validate_yolo_recall函数
char *base = "/home/yi_miao/darknet-master/results/comp4_det_test_";
    list *plist = get_paths("/home/yi_miao/darknet-master/data/VOC_person/person_test.txt");
  • test_yolo函数:
draw_detections(im, l.side*l.side*l.n, thresh, boxes, probs, voc_names, alphabet, 1);

类别数改为1类;
- run_yolo函数:

demo(cfg, weights, thresh, cam_index, filename, voc_names, 1, frame_skip, prefix, .5, 0,0,0,0);

类别数改为1类;

(4)修改darknet-master\src\detector.c文件:

  • validate_detector_recall函数:
list *plist = get_paths("/home/yi_miao/darknet-master/data/VOC_person/person_test.txt");
  • run_detector函数:
int classes = option_find_int(options, "classes", 1);

类别数改为1类;

(5)修改darknet-master\cfg\tiny-yolo.cfg文件:
修改tiny-yolo.cfg文件,其他模型修改类似。
【region】层中 classes 改成1;
【region】层上方第一个【convolution】层,其中的filters值要进行修改,改成(classes+ coords+ 1)* (NUM) ,我的情况中:(1+4+1)* 5=30,所以需要把filters 的值改成30;

[convolutional]
size=1
stride=1
pad=1
#filters=425
filters=30
activation=linear

[region]
anchors = 0.738768,0.874946,  2.42204,2.65704,  4.30971,7.04493,  10.246,4.59428,  12.6868,11.8741
bias_match=1
#classes=80
classes=1
coords=4
num=5
softmax=1
jitter=.2
rescore=1

(6)其他的一些参数可以按自己需求修改,比如学习率、max_batches等。

得到预训练模型文件:
根据不同的model,要对已有的weights进行转换,得到与.cfg文件相匹配的初始权重;

./darknet partial cfg/tiny-yolo.cfg tiny-yolo-voc.weights tiny-yolo.conv.14 14

注意,这里 ./darknet-master partial 转化网络 现有weights的路径 需要生成的weights的路径
YOLO v1和YOLO v2两个版本的weight不通用。

经过以上修改,重新make一下darknet

训练:
在darknet-master下执行:

./darknet detector train cfg/personTrain.data cfg/tiny-yolo.cfg tiny-yolo.conv.14

训练中断:

  • 在保存的最新的权重文件的基础上继续训练,执行:
./darknet detector train cfg/personTrain.data cfg/tiny-yolo.cfg backup/tiny-yolo_8000.weights
  • 也可以执行以下命令:
./darknet partial cfg/tiny-yolo.cfg backup/tiny-yolo_8000.weights tiny-yolo_8000.conv.14 14
./darknet detector train cfg/tiny-yolo.cfg tiny-yolo_8000.conv.14 2>1 | tee person_train_log.txt

保存训练中间参数结果person_train_log.txt,用于可视化调参。

测试和结果:
执行命令:

./darknet detector test cfg/tiny-yolo.cfg tiny-yolo_final.weights data/person.jpg

一般预训练模型都用图像分类的模型,而不是用检测模型训练的,最后没用预训练模型来训练网络,迭代了43000次后的效果如下所示(注:图片来自百度图片):

INRIA 行人数据集行人检测滑动窗口python_xml_05