參考)YOLOv2訓練自己的數據集(voc格式)進行實驗,基本上是正確的,但其初始給出的代碼並非是在linux下可以運行的,因此參考部分博客寫了下面的程序,可以實現對文件夾內圖片的批量讀取以及更改名稱符合VOC數據集習慣。另原文有部分小錯誤,本文已經修改,但後文屬於轉載,版權屬原作者所有,本文僅爲記錄和交流用。如下文所示。
1 準備數據
#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;
}
2 標記圖像目標區域
通常save之後會將標記的信息保存在xml文件,其名字通常與對應的原始圖像一樣。最後生成的畫風是這樣的
有時候在Windows下用該工具label圖像,可能會出現size那裏的width和height都爲0,如果在label之前已經歸一化了圖像大小那麼就可以用下面兩行命令來修改這個0值
同理修改寬: 同理修改高: 在對應目錄下執行即可,這樣就可以把後綴添上。這樣就做按照VOC做好了我們的數據集,接下來就是放到算法中去訓練跑起來。
3 用YOLOv2訓練
1).生成相關文件
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()
2).配置文件修改
做好了上述準備,就可以根據不同的網絡設置(cfg文件)來訓練了。在文件夾cfg中有很多cfg文件,應該跟caffe中的prototxt文件是一個意思。這裏以tiny-yolo-voc.cfg爲例,該網絡是yolo-voc的簡版,相對速度會快些。主要修改參數如下
另外也可根據需要修改learning_rate、max_batches等參數。這裏歪個樓吐槽一下其他網絡配置,一開始是想用tiny.cfg來訓練的官網作者說它夠小也夠快,但是它的網絡配置最後幾層是這樣的畫風:
這裏沒有類別數,完全不知道怎麼修改,強行把最後一層卷積層卷積核個數修改又跑不通會出錯,如有大神知道還望賜教。
修改好了cfg文件之後,就需要修改兩個文件,首先是data文件下的voc.names。打開voc.names文件可以看到有20類的名稱,本例中只有一類,檢測人,因此將原來所有內容清空,僅寫上person並保存, 備註:若此處爲多個類的訓練,請同voc_label.py 中順序一致。
接着需要修改cfg文件夾中的voc.data文件。也是按自己需求修改,我的修改之後是這樣的畫風:
修改後按原名保存最好,接下來就可以訓練了。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