• 【计算机视觉】【神经网络与深度学习】YOLO v2 detection训练自己的数据


    转自:http://blog.csdn.net/hysteric314/article/details/54097845

    说明

    这篇文章是训练YOLO v2过程中的经验总结,我使用YOLO v2训练一组自己的数据,训练后的model,在阈值为.25的情况下,Recall值是95.54%,Precision 是97.27%。 
    需要注意的是,这一训练过程可能只对我自己的训练集有效,因为我是根据我这一训练集的特征来对YOLO代码进行修改,可能对你的数据集并不适用,所以仅供参考。

    我的数据集

    1,用于训练的数据集一共1003张图片和1003个与图片对应的标记信息(xml格式)。 
    2,图片的格式是 jpg,分辨率都是384*288,图片的命名从0000.jpg到1002.jpg,与VOC数据集的命名方式差不多。 
    3,标记信息的格式xml,命名从0000.xml到1002.xml,标记内容格式与VOC中标记信息的格式类似,某一xml中具体内容如下:

    <annotation>
        <folder>Image</folder>
        <filename>0000</filename>
        <source>
            <database>Pedestrian_ultrared</database>
        </source>
        <size>
            <width>384</width>
            <height>288</height>
        </size>
        <object>
            <name>n00000001</name>
            <bndbox>
                <xmin>232</xmin>
                <xmax>248</xmax>
                <ymin>161</ymin>
                <ymax>203</ymax>
            </bndbox>
        </object>
    </annotation>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4,一共1003组信息,选择900组用于训练(Train),103组用于验证(Validation)。 
    5,这个数据集只包含一个类别:人。当然也可以说是两个类别:人、背景。

    我要训练的数据集的有以上这些特点,它数据的格式和VOC训练数据集的格式很相似,而YOLO v2默认是能够训练VOC数据集的。为了让YOLO v2能像训练VOC数据集一样训练我自己的数据集,我对代码进行以下三个方面的修改: 
    一:修改分类的个数:在代码中,默认VOC数据集是20类,而我要改成1类。 
    二:准备txt文档:VOC训练数据集中会自带几个txt文档,用来指明文件名或者路径地址,而如果你使用你自己的数据可能就需要自己生成这些文档。 
    三:修改代码中路径信息,把代码中VOC训练数据集的路径改成自己训练数据集的路径。

    把20类改成1类

    1. cfg/voc.data文件中:

      • classes 改成1。
      • names=data/pasacal.names。
      • pasacal.names这一个文件要存在于darknet目录下的data文件夹里,没有的话可以自己在那个目录下创建一个pasacal.txt,加上内容之后,修改文件后缀名变成pasacal.names即可,当然名字和路径都可以自己定义。这个文件中的行数要和类数一致,每一行都是一个类别的名字。比如我的这一文件中就只有一行数据:“person”。这个文件在测试你训练的model的时候会用到,系统会在图片上画出bounding box,bounding box上面的文字,也就是这个框中物体的名字,应该就来自这个文件。
    2. cfg/yolo_voc.cfg文件中 :

      • 【region】层中 classes 改成1。
      • 【region】层上方第一个【convolution】层,其中的filters值要进行修改,改成(classes+ coords+ 1)* (NUM) ,我的情况中:(1+4+1)* 5=30,我把filters 的值改成了30。
      • 修改filters的建议来源自(https://groups.google.com/forum/#!topic/darknet/B4rSpOo84yg),我修改了之后一切正常。
    3. src/yolo.c 文件中 :

      • 位置大约第14行左右改成:char *voc_names={“n00000001”},原来里面有20类的名字,我改成了唯一1类的名字。
      • 位置大约第328行左右,修改draw_detection这个函数最后一个参数:20改成1。这个函数用于把系统检测出的框给画出来,并把画完框的图片传回第一个参数im中,用于保存和显示。
      • 位置大约第361行左右,demo函数中,倒数第三个参数我把20改成了1,虽然不知道有没有用,反正对结果没什么影响。
    4. src/yolo_kernels.cu 文件中 :

      • 位置第62行,draw_detection这个函数最后一个参数20改成1。
    5. scripts/voc_label.py 文件中 :

      • 位置第9行改成:classes=[“n00000001”],因为我只有一类。

    准备txt文档

    一共需要准备四个txt格式文档:train.txt与val.txt,infrared_train.txt与infrared_val.txt:

    train.txt与val.txt 
    在生成infrared_train.txt与infrared_val.txt这两个文件时,会分别用到这两个文档。文档里包含了用于训练/验证的图片的名称,里面的数据组成很简单,每行都是一个图片的名称,并不包含图片的后缀(.jpg),比如文档中: 
    第一行是: 0000 
    第二行是: 0001 
    …..

    生成脚本:creat_list.py:

    #这个小脚本是用来打开图片文件所在文件夹,把前900个用于训练的图片的名称保存在tain.txt,后103个用于验证的图片保存在val.txt
    import os
    from os import listdir, getcwd
    from os.path import join
    if __name__ == '__main__':
        source_folder='/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/'#地址是所有图片的保存地点
        dest='/home/yolo_v2_tinydarknet/darknet/infrared/train.txt' #保存train.txt的地址
        dest2='/home/yolo_v2_tinydarknet/darknet/infrared/val.txt'  #保存val.txt的地址
        file_list=os.listdir(source_folder)       #赋值图片所在文件夹的文件列表
        train_file=open(dest,'a')                 #打开文件
        val_file=open(dest2,'a')                  #打开文件
        for file_obj in file_list:                #访问文件列表中的每一个文件
            file_path=os.path.join(source_folder,file_obj) 
            #file_path保存每一个文件的完整路径
            file_name,file_extend=os.path.splitext(file_obj)
            #file_name 保存文件的名字,file_extend保存文件扩展名
            file_num=int(file_name) 
            #把每一个文件命str转换为 数字 int型 每一文件名字都是由四位数字组成的  如 0201 代表 201     高位补零  
            if(file_num<900):                     #保留900个文件用于训练
                #print file_num
                train_file.write(file_name+'
    ')  #用于训练前900个的图片路径保存在train.txt里面,结尾加回车换行
            else :
                val_file.write(file_name+'
    ')    #其余的文件保存在val.txt里面
        train_file.close()#关闭文件
        val_file.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    infrared_train.txt与infrared_val.txt 
    这个文档用于告诉训练系统哪些图片是用来进行训练,哪些是用于验证的。 
    文档里包含了所有用于训练/验证的图片的完整路径,每一行都是一个图片的完整路径,例如 
    第一行是: /home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0000.jpg 
    第二行是 :/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0001.jpg 
    …..

    生成脚本:voc_label_change.py

    #此脚本修改自voc_label.py。修改的原因是:我的训练集跟voc有些不同。
    #由于数据集中包括用于训练的数据和用于验证的数据,所以此脚本可能需要分别对这两种数据各运行一次,对两种数据只需要简单地注释掉相应语句即可
    #这个脚本需要train.txt ,这个文件是我用脚本creat_list.py生成的,保存了用于训练的图片的名字id,保存了用于训练的图片的名字
    #这个脚本需要val.txt文件,这个文件是我用脚本creat_list.py生成的,保存了用于验证的图片的名字id,保存了用于验证的图片的名字
    #这个脚本还需要xml格式的标签文件,我的训练集xml文件的格式与voc2007的类似,xml文件的名称与对应的用于训练的图片的名称相同
    #这个脚本会生成 indrared_train.txt文件 ,用于保存每一用于训练的图片的完整的路径,随后会被voc.data yolo.c使用
    #这个脚本会生成 indrared_val.txt文件 ,用于保存每一用于验证的图片的完整的路径,随后会被voc.data yolo.c使用
    #这个脚本还会生成 txt格式的yolo可识别的标签文件,转换自每一个用于训练或验证的图片对应的xml文件,txt格式的文件名称与对应的xml文件名相同,但是内容不同,扩展名不同
    #这个脚本 需要与图片对应的xml文件所在的地址,需要,转换后生成的txt的完整保存路径
    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')] #按照自己的文件格式改的,不需要判断是那个voc数据包
    classes = ["n00000001"]#因为我的数据集只有一个类别
    def convert(size, box):#voc_label.py 自带的函数,没有修改
        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)
    def convert_annotation(image_id):
        #in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
        in_file = open('/home/yolo_v2_tinydarknet/darknet/infrared/labels/dout_original/%s.xml'%(image_id))#与图片对应的xml文件所在的地址
        out_file = open('/home/yolo_v2_tinydarknet/darknet/infrared/labels/%s.txt'%(image_id),'w') #与此xml对应的转换后的txt,这个txt的保存完整路径
        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   #由于自己的文件里面没有diffcult这一个标签,所以就屏蔽之
            cls = obj.find('name').text
            if cls not in classes :#or int(difficult) == 1:
                continue
            cls_id = classes.index(cls)
            xmlbox = obj.find('bndbox')   #访问boundbox标签的数据并进行处理,都按yolo自带的代码来,没有改动
            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]) + '
    ')
    
    #image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()  #之前代码是按照sets里面的字符来访问保存有图片名字的train或者val的txt文件
    image_ids = open('/home/yolo_v2_tinydarknet/darknet/infrared/train.txt').read().strip().split()  #如果是训练集数据打开这一行,注释下一行
    #image_ids = open('/home/yolo_v2_tinydarknet/darknet/infrared/val.txt').read().strip().split()  #如果是验证数据集数据打开这一行,注释上一行
    #list_file = open('%s_%s.txt'%(year, image_set), 'w')
    list_file = open('infrared_train.txt', 'w')     #把结果写入到indrared_train.txt文件中,如果是训练集数据打开这一行,注释下一行
    #list_file = open('infrared_val.txt', 'w')     #把结果写入到indrared_train.txt文件中,如果是验证数据集数据打开这一行,注释上一行
    for image_id in image_ids:
        #list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg
    '%(wd, year, image_id))
        list_file.write('/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/%s.jpg
    '%(image_id))  #把每一用于训练或验证的图片的完整的路径写入到infrared_train.txt中  这个文件会被voc.data yolo.c调用
        convert_annotation(image_id)   #把图片的名称id传给函数,用于把此图片对应的xml中的数据转换成yolo要求的txt格式
    list_file.close() #关闭文件
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    修改路径

    1. cfg/voc.data文件中:

      • train = /home/yolo_v2_tinydarknet/darknet/infrared/infrared_train.txt //infrared_train.txt的完整路径
      • valid = /home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt //infrared_val.txt的完整路径
      • backup = /home/yolo_v2_tinydarknet/darknet/backup/ /* 这个路径是YOLO用于备份的,在训练过程中YOLO会不断地对产生的weights文件进行备份,darknet目录下就自带一个backup文件夹,这个路径指向那里。*/
    2. src/yolo.c 文件中:

      • train_yolo函数中:

        char *train_images =" /home/yolo_v2_tinydarknet/darknet/infrared/infrared_train.txt";//infrared_train.txt的完整路径
        char *backup_directory = "/home/yolo_v2_tinydarknet/darknet/backup/";//可以修改为自己的路径
        • 1
        • 2
        • 1
        • 2
      • validate_yolo函数中:

        char *base = "/home/yolo_v2_tinydarknet/darknet/results/comp4_det_test_";//可以修改自己的路径,好像是用于保存测试结果
        list *plist=get_paths("/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt");//infrared_val.txt的完整路径
        • 1
        • 2
        • 1
        • 2
      • validate_yolo_recall函数中:

        char *base = "/home/yolo_v2_tinydarknet/darknet/results/comp4_det_test_";//可以修改自己的路径
        list *plist = get_paths("/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt");//infrared_val.txt的完整路径
        • 1
        • 2
        • 1
        • 2
    3. src/detector.c 文件中: 
      • 位置第375行改成:list *plist = get_paths(“/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt”);//改成infrared_val.txt的完整路径
      • 需要注意的是,这个文件里的validate_detector_recall函数是用来计算输出recall值的,后面会说。

    对了,可能你注意到,输入到系统的infrared_train.txt或者infrared_val.txt都只是图片的完整路径,你也知道,要进行训练的话,除了需要图片还需要标记信息,然而标记信息仅仅用我的脚本voc_label_change.py从xml转换成了YOLO可识别的txt格式,但是它们的完整路径并没有输入进系统,那么系统该怎么找到它们呢?

    因为在训练集中,一个图片文件和这一图片文件对应标记文件,他们俩除了后缀名之外其余的名称是一样的,所以src/yolo.c中有以下语句:

              find_replace(path, "dout", "labels", labelpath);
              find_replace(labelpath, "JPEGImages", "labels", labelpath);
              find_replace(labelpath, ".jpg", ".txt", labelpath);
              find_replace(labelpath, ".JPEG", ".txt", labelpath);
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    函数会找到路径中的图片后缀名.jpg,自动替换成.txt。比如: 
    /home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0000.jpg 
    自动替换后变成了 
    /home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0000.txt 
    所以在使用voc_label_new.py转换生成txt格式的标记信息之后,只需要把这些txt格式的标记文件复制到图片所在的目录下即可。系统根据替换后的路径地址来读取对应标记文件。

    开始训练!

    通过复杂地修改和准备,终于可以开始训练了 
    我是按照YOLO的官方指南来的,首先下载一个预训练的model(当然你也可以自己生成),放到darkent/目录下。 
    下载地址 (76 MB):http://pjreddie.com/media/files/darknet19_448.conv.23 
    然后运行指令:./darknet detector train cfg/voc.data cfg/yolo_voc.cfg darknet19_448.conv.23 
    就可以开始训练了,系统默认会迭代45000次,我花了一周时间才训练完。 
    当然迭代次数是可以修改的,应该是在cfg/yolo_voc.cfg修改max_batches的值就行。

    评估性能

    经过漫长的训练过程,model终于训练好了,为了评估性能,可以使用以下指令 
    ./darknet detector recall cfg/voc.data cfg/yolo_voc.cfg backup/yolo_voc_final.weights

    需要注意的是,在使用这个指令之前,我先修改一下src/detector.c 这一函数

    • 位置第375行改成:list *plist = get_paths(“/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt”);//改成infrared_val.txt的完整路径
    • 运行上面的指令会调用validate_detector_recall函数,这个函数中有个参数thresh(阈值),默认的值是.001,这个默认值设的很小,会让系统识别出更多的框来,导致proposals值激增,还会让recall值变高,达到98.5%。最终我改成了 .25。
    • 上面的函数只会显示出recall值,没有precision值,precision的值计算方法是:识别为正确的个数/画了多少个框,所以我修改了代码。我把第447行显示结果的代码修改为 :
    fprintf(stderr, "ID:%5d Correct:%5d Total:%5d	RPs/Img: %.2f	IOU: %.2f%%	Recall:%.2f%%	", i, correct, total, (float)proposals/(i+1), avg_iou*100/total, 100.*correct/total);
    fprintf(stderr, "proposals:%5d	Precision:%.2f%%
    ",proposals,100.*correct/(float)proposals); 
    • 1
    • 2
    • 1
    • 2

    我运行后显示的结果是: 
    ….. 
    ID: 101 Correct: 106 Total: 111 RPs/Img: 1.07 IOU: 82.00% Recall:95.50% proposals: 109 Precision:97.25% 
    ID: 102 Correct: 107 Total: 112 RPs/Img: 1.07 IOU: 82.11% Recall:95.54% proposals: 110 Precision:97.27%

    结果中的参数,我的理解是:

    Correct :可以理解为正确地画了多少个框,遍历每张图片的Ground Truth,网络会预测出很多的框,对每一Groud Truth框与所有预测出的框计算IoU,在所有IoU中找一个最大值,如果最大值超过一个预设的阈值,则correct加一。

    Total:一共有多少个Groud Truth框。

    Rps/img:p 代表proposals, r 代表region。 意思就是平均下来每个图片会有预测出多少个框。预测框的决定条件是,预测某一类的概率大于阈值。在validation_yolo_recall函数中,默认的这一个阈值是0.001,这一阈值设置的比较低,这就会导致会预测出很多个框,但是这样做是可以提升recall的值,一般yolo用于画框的默认值是.25,使用这个阈值会让画出来的框比较准确。而validation_yolo_recall使用的阈值改成。25的时候,Rps/img 值会降低,recall的值会降低,所以validation_yolo_recall默认使用一个较低的阈值,有可能作者的目的就是为了提高recall值,想在某种程度上体现网络的识别精度比较高。

    IoU、Recall、Precision:解释起来比较麻烦,请看我的博客有详细说明: 
    http://blog.csdn.net/hysteric314/article/details/54093734

    要说的

    1,这是我的经验总结,可能对你并没有意义,仅供参考。 
    2,如果你发现了错误,欢迎留言指正,多谢! 
    3,接下来一段时间可能要比较忙了,马上就要中期了,不过如果有新进展的话还会继续更新博客。 
    4,具体的YOLOv2的安装,教程很多,在这就不赘述了,可以参考他们的官网:http://pjreddie.com/darknet/yolo/ 
    5,如果对YOLO感兴趣,可以跳墙去访问这个论坛,YOLO作者会亲自答疑:https://groups.google.com/forum/#!forum/darknet


  • 相关阅读:
    Java高级特性 第11节 JUnit 3.x和JUnit 4.x测试框架
    Java高级特性 第10节 IDEA和Eclipse整合JUnit测试框架
    Java高级特性 第9节 Socket机制
    Java面向对象和高级特性 项目实战(一)
    Java高级特性 第8节 网络编程技术
    Java高级特性 第7节 多线程
    二十一、字符串类的创建
    二十二、经典问题解析二
    二十一、C++中的临时对象
    二十、对象的销毁
  • 原文地址:https://www.cnblogs.com/huty/p/8517182.html
Copyright © 2020-2023  润新知