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, '\t', 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\n", 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\n",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\n", dir);

        char sum=list_dir_name(dir, 1);
	printf("Img total num:%d\n",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\n", 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\n", i);
		
		printf("Save as%s\n", 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]) + '\n')  
  
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\n'%(wd, year, image_id))  
        convert_annotation(year, image_id)  
    list_file.close()

修改好後在該目錄下運行命令:python voc_label.py,之後則在文件夾scripts\VOCdevkit\VOC2007下生成了文件夾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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章