参考)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,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,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,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的结构如下
这里面用到的文件夹是
Annotations、ImageSets和JPEGImages。其中文件夹
Annotation中主要存放xml文件,每一个xml对应一张图像,并且每个xml中存放的是标记的各个目标的位置和类别信息,命名通常与对应的原始图像一样;而ImageSets我们只需要用到Main文件夹,这里面存放的是一些文本文件,通常为train.txt、test.txt等,该文本文件里面的内容是需要用来训练或测试的图像的名字(无后缀无路径);JPEGImages文件夹中放我们已按统一规则命名好的原始图像。
因此,首先
3.在ImageSets文件夹中,新建三个空文件夹Layout、Main、Segmentation,然后把写了训练或测试的图像的名字的文本拷到Main文件夹下,按目的命名,我这里所
有图像用来训练,故而Main文件夹下只有train.txt文件。上面代码运行后会在图片文件夹内生成该文件,把它拷进去即可。
2 用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','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()
这里包含了类别和对应归一化后的位置(i guess,如有错请指正)。同时在scripts\下应该也生成了train_2007.txt这个文件,里面包含了所有训练样本的绝对路径。
2.配置文件修改
做好了上述准备,就可以根据不同的网络设置(cfg文件)来训练了。在文件夹cfg中有很多cfg文件,应该跟caffe中的prototxt文件是一个意思。这里以tiny-yolo-voc.cfg为例,该网络是yolo-voc的简版,相对速度会快些。主要修改参数如下
copy
- .
- .
- .
- [convolutional]
- size=1
- stride=1
- pad=1
- filters=30//修改最后一层卷积层核参数个数,计算公式是依旧自己数据的类别数filter=num×(classes+coords+1)=5×(1+4+1)=30
- activation=linear
- [region]
- anchors=1.08,1.19,3.42,4.41,6.63,11.38,9.42,5.11,16.62,10.52
- bias_match=1
- classes=1//类别数,本例为1类
- coords=4
- num=5
- softmax=1
- jitter=.2
- rescore=1
- object_scale=5
- noobject_scale=1
- class_scale=1
- coord_scale=1
- absolute=1
- thresh=.6
- random=1