zoukankan      html  css  js  c++  java
  • YOLO模型训练自己数据-VOC格式数据集制作-ubuntu c++文件夹内图片批量读取与重命名

    参考)YOLOv2训练自己的数据集(voc格式)进行实验,基本上是正确的,但其初始给出的代码并非是在linux下可以运行的,因此参考部分博客写了下面的程序,可以实现对文件夹内图片的批量读取以及更改名称符合VOC数据集习惯。另原文有部分小错误,本文已经修改,但后文属于转载,版权属原作者所有,本文仅为记录和交流用。如下文所示。

    1 准备数据

    首先准备好自己的数据集,最好固定格式,此处以VOC为例,采用jpg格式的图像,在名字上最好使用像VOC一样类似000001.jpg、000002.jpg这样。可使用下面示例代码

    #include <dirent.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <opencv2/opencv.hpp>
    
    #define img_num 2000
    char img_file[img_num][1000];
    
    int list_dir_name(char* dirname, int tabs)
    {
        DIR* dp;
        struct dirent* dirp;
        struct stat st;
        char tab[tabs + 1];
    		char img_count=0;
    
        /* open dirent directory */
        if((dp = opendir(dirname)) == NULL)
        {
            perror("opendir");
            return -1;
        }
    
    
    
        /* fill tab array with tabs */
        memset(tab, '	', tabs);
        tab[tabs] = 0;
    
        /**
         * read all files in this dir
         **/
        while((dirp = readdir(dp)) != NULL)
    
        {
            char fullname[255];
            memset(fullname, 0, sizeof(fullname));
    
            /* ignore hidden files */
            if(dirp->d_name[0] == '.')
                continue;
    
            /* display file name */
            //printf("img_name:%s
    ", dirp->d_name);
    
            strncpy(fullname, dirname, sizeof(fullname));
            strncat(fullname, dirp->d_name, sizeof(fullname));
    	    strcat(img_file[img_count++], fullname);				
    	    printf("Image %3d path:%s
    ",img_count-1,img_file[img_count-1]);//fullname=dir+file name,the absolute path of the image file        
    
    				/* get dirent status */
            if(stat(fullname, &st) == -1)
            {
                perror("stat");
                fputs(fullname, stderr);
                return -1;
            }
            /* if dirent is a directory, call itself */
            if(S_ISDIR(st.st_mode) && list_dir_name(fullname, tabs + 1) == -1)
                return -1;
        }
        return img_count;
    
    }
    
    int main(int argc, char* argv[])
    {
    	char* dir="/home/robot/Downloads/mark_recognition/car_img/simple_3class/";
            printf("%s
    ", dir);
    
            char sum=list_dir_name(dir, 1);
    	printf("Img total num:%d
    ",sum);
    
    	int i;
    	char order[1000];
    
    	char txt_path[1000];
    	char* txt_name="train.txt";
    	memset(txt_path, 0, sizeof(txt_path));
    	strcat(txt_path,dir);
    	strcat(txt_path,txt_name);
    	FILE *fp = fopen(txt_path, "w");
    
    	for (i = 0; i<sum; ++i)
    	{
    		char img_path[1000];
    		memset(img_path, 0, sizeof(img_path));
    
    		printf("Source %s
    ", img_file[i]);
    		IplImage *pSrc = cvLoadImage(img_file[i]);
    		sprintf(order, "%05d.jpg", i);
    		strcat(img_path,dir);
    		strcat(img_path,order);
    		cvSaveImage(img_path, pSrc);
    		fprintf(fp, "%05d
    ", i);
    		
    		printf("Save as%s
    ", img_path);
    		cvReleaseImage(&pSrc);
    	}
    	fclose(fp);
    
    	
    
    	return 0;
    }
    
    
    
    
    
    
    
    

    准备好了自己的图像后,需要按VOC数据集的结构放置图像文件。VOC的结构如下
    [plain] view plain copy
    1. --VOC  
    2.     --Annotations  
    3.     --ImageSets  
    4.       --Main  
    5.       --Layout  
    6.       --Segmentation  
    7.     --JPEGImages  
    8.     --SegmentationClass  
    9.     --SegmentationObject  
          这里面用到的文件夹是Annotations、ImageSets和JPEGImages。其中文件夹Annotation中主要存放xml文件,每一个xml对应一张图像,并且每个xml中存放的是标记的各个目标的位置和类别信息,命名通常与对应的原始图像一样;而ImageSets我们只需要用到Main文件夹,这里面存放的是一些文本文件,通常为train.txt、test.txt等,该文本文件里面的内容是需要用来训练或测试的图像的名字(无后缀无路径);JPEGImages文件夹中放我们已按统一规则命名好的原始图像。
          因此,首先
          1.新建文件夹VOC2007(通常命名为这个,也可以用其他命名,但一定是名字+年份,例如MYDATA2016,无论叫什么后面都需要改相关代码匹配这里,本例中以VOC2007为例)
          2.在VOC2007文件夹下新建三个文件夹Annotations、ImageSets和JPEGImages,并把准备好的自己的原始图像放在JPEGImages文件夹下
          3.在ImageSets文件夹中,新建三个空文件夹Layout、Main、Segmentation,然后把写了训练或测试的图像的名字的文本拷到Main文件夹下,按目的命名,我这里所有图像用来训练,故而Main文件夹下只有train.txt文件。上面代码运行后会在图片文件夹内生成该文件,把它拷进去即可。

    2 标记图像目标区域

           因为做的是目标检测,所以接下来需要标记原始图像中的目标区域。相关方法和工具有很多,这里需用labelImg,相关用法也有说明,基本就是框住目标区域然后双击类别,标记完整张图像后点击保存即可。操作界面如下:

    通常save之后会将标记的信息保存在xml文件,其名字通常与对应的原始图像一样。最后生成的画风是这样的

    其中每个xml文件是这样的画风
    [html] view plain copy
    1. <?xml version="1.0" ?>  
    2. <annotation>  
    3.     <folder>JPEGImages</folder>  
    4.     <filename>00000</filename>  
    5.     <path>/home/kinglch/VOC2007/JPEGImages/00000.jpg</path>  
    6.     <source>  
    7.         <database>Unknown</database>  
    8.     </source>  
    9.     <size>  
    10.         <width>704</width>  
    11.         <height>576</height>  
    12.         <depth>3</depth>  
    13.     </size>  
    14.     <segmented>0</segmented>  
    15.     <object>  
    16.         <name>person</name>  
    17.         <pose>Unspecified</pose>  
    18.         <truncated>0</truncated>  
    19.         <difficult>0</difficult>  
    20.         <bndbox>  
    21.             <xmin>73</xmin>  
    22.             <ymin>139</ymin>  
    23.             <xmax>142</xmax>  
    24.             <ymax>247</ymax>  
    25.         </bndbox>  
    26.     </object>  
    27.     <object>  
    28.         <name>person</name>  
    29.         <pose>Unspecified</pose>  
    30.         <truncated>0</truncated>  
    31.         <difficult>0</difficult>  
    32.         <bndbox>  
    33.             <xmin>180</xmin>  
    34.             <ymin>65</ymin>  
    35.             <xmax>209</xmax>  
    36.             <ymax>151</ymax>  
    37.         </bndbox>  
    38.     </object>  
    39.     <object>  
    40.         <name>person</name>  
    41.         <pose>Unspecified</pose>  
    42.         <truncated>0</truncated>  
    43.         <difficult>0</difficult>  
    44.         <bndbox>  
    45.             <xmin>152</xmin>  
    46.             <ymin>70</ymin>  
    47.             <xmax>181</xmax>  
    48.             <ymax>144</ymax>  
    49.         </bndbox>  
    50.     </object>  
    51. </annotation>  
    注意filename中文件的文件名名没有后缀,因此需要统一加上后缀。只需一段命令即可
    [plain] view plain copy
    1. find -name '*.xml' |xargs perl -pi -e 's|</filename>|.jpg</filename>|g'  
    有时候在Windows下用该工具label图像,可能会出现size那里的width和height都为0,如果在label之前已经归一化了图像大小那么就可以用下面两行命令来修改这个0值
    同理修改宽:
    [plain] view plain copy
    1. find -name '*.xml' |xargs perl -pi -e 's|0</width>|448</width>|g'  
    同理修改高:
    [plain] view plain copy
    1. find -name '*.xml' |xargs perl -pi -e 's|0</height>|448</height>|g'  
    在对应目录下执行即可,这样就可以把后缀添上。这样就做按照VOC做好了我们的数据集,接下来就是放到算法中去训练跑起来。

    3 用YOLOv2训练

    1).生成相关文件

        按darknet的说明编译好后,接下来在darknet-master/scripts文件夹中新建文件夹VOCdevkit,然后将整个VOC2007文件夹都拷到VOCdevkit文件夹下。
        然后,需要利用scripts文件夹中的voc_label.py文件生成一系列训练文件和label,具体操作如下:
        首先需要修改voc_label.py中的代码,这里主要修改数据集名,以及类别信息,我的是VOC2007,并且所有样本用来训练,没有val或test,并且只检测人,故只有一类目标,因此按如下设置
    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=[('2007', 'train')]  
    classes = [ "person"]  
      
      
    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)  
      
    def convert_annotation(year, image_id):  
        in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))  #(如果使用的不是VOC而是自设置数据集名字,则这里需要修改)  
        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]) + '
    ')  
      
    wd = getcwd()  
      
    for year, image_set in sets:  
        if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):  
            os.makedirs('VOCdevkit/VOC%s/labels/'%(year))  
        image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()  
        list_file = open('%s_%s.txt'%(year, image_set), 'w')  
        for image_id in image_ids:  
            list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg
    '%(wd, year, image_id))  
            convert_annotation(year, image_id)  
        list_file.close()

    修改好后在该目录下运行命令:python voc_label.py,之后则在文件夹scriptsVOCdevkitVOC2007下生成了文件夹lable,该文件夹下的画风是这样的

    这里包含了类别和对应归一化后的位置(i guess,如有错请指正)。同时在scripts下应该也生成了train_2007.txt这个文件,里面包含了所有训练样本的绝对路径。

    2).配置文件修改

          做好了上述准备,就可以根据不同的网络设置(cfg文件)来训练了。在文件夹cfg中有很多cfg文件,应该跟caffe中的prototxt文件是一个意思。这里以tiny-yolo-voc.cfg为例,该网络是yolo-voc的简版,相对速度会快些。主要修改参数如下

    [plain] view plain copy
    1. .  
    2. .  
    3. .  
    4. [convolutional]  
    5. size=1  
    6. stride=1  
    7. pad=1  
    8. filters=30  //修改最后一层卷积层核参数个数,计算公式是依旧自己数据的类别数filter=num×(classes + coords + 1)=5×(1+4+1)=30  
    9. activation=linear  
    10.   
    11. [region]  
    12. anchors = 1.08,1.19,  3.42,4.41,  6.63,11.38,  9.42,5.11,  16.62,10.52  
    13. bias_match=1  
    14. classes=1  //类别数,本例为1类  
    15. coords=4  
    16. num=5  
    17. softmax=1  
    18. jitter=.2  
    19. rescore=1  
    20.   
    21. object_scale=5  
    22. noobject_scale=1  
    23. class_scale=1  
    24. coord_scale=1  
    25.   
    26. absolute=1  
    27. thresh = .6  
    28. random=1  
    另外也可根据需要修改learning_rate、max_batches等参数。这里歪个楼吐槽一下其他网络配置,一开始是想用tiny.cfg来训练的官网作者说它够小也够快,但是它的网络配置最后几层是这样的画风:

    [html] view plain copy
    1. [convolutional]  
    2. filters=1000  
    3. size=1  
    4. stride=1  
    5. pad=1  
    6. activation=linear  
    7.   
    8. [avgpool]  
    9.   
    10. [softmax]  
    11. groups=1  
    12.   
    13. [cost]  
    14. type=sse  
    这里没有类别数,完全不知道怎么修改,强行把最后一层卷积层卷积核个数修改又跑不通会出错,如有大神知道还望赐教。

         修改好了cfg文件之后,就需要修改两个文件,首先是data文件下的voc.names。打开voc.names文件可以看到有20类的名称,本例中只有一类,检测人,因此将原来所有内容清空,仅写上person并保存, 备注:若此处为多个类的训练,请同voc_label.py 中顺序一致

          接着需要修改cfg文件夹中的voc.data文件。也是按自己需求修改,我的修改之后是这样的画风:

    [plain] view plain copy
    1. classes= 1  //类别数  
    2. train  = /home/kinglch/darknet-master/scripts/2007_train.txt  //训练样本的绝对路径文件,也就是上文2.1中最后生成的  
    3. //valid  = /home/pjreddie/data/voc/2007_test.txt  //本例未用到  
    4. names = data/voc.names  //上一步修改的voc.names文件  
    5. backup = /home/kinglch/darknet-master/results/  //指示训练后生成的权重放在哪  
    修改后按原名保存最好,接下来就可以训练了。

    ps:yolo v1中这些细节是直接在源代码的yolo.c中修改的,源代码如下

    比如这里的类别,训练样本的路径文件和模型保存路径均在此指定,修改后从新编译。而yolov2似乎摈弃了这种做法,所以训练的命令也与v1版本的不一样。

    3).运行训练and 测试

          上面完成了就可以命令训练了,可以在官网上找到一些预训练的模型作为参数初始值,也可以直接训练,训练命令为

    ./darknet detector train ./cfg/voc.data cfg/tiny-yolo-voc.cfg
    
    
       测试命令:

    ./darknet detector test cfg/voc.data cfg/tiny-yolo-voc.cfg result/yolo-voc_400.weights testImage/738780.jpg
    

         或者

    ./darknet detector test cfg/voc.data cfg/tiny-yolo-voc.cfg results/tiny-yolo-voc_final.weights 0000.jpg

  • 相关阅读:
    uboot如何检测XC2440是从Nand或Nor启动
    uboot中的快捷菜单的制作说明
    Android存储数据方式
    SharedPreferences详解
    (转载)Android理解:显式和隐式Intent
    使用 Fresco加载图片
    script "text/template"
    网页动画插件---Super Scrollorama , TweenMax 和skrollr
    Android控件之HorizontalScrollView 去掉滚动条
    android编程取消标题栏方法(appcompat_v7、Theme.NoTitleBar)
  • 原文地址:https://www.cnblogs.com/siahekai/p/11000795.html
Copyright © 2011-2022 走看看