最近跑faster rcnn需要训练自己的数据集,那么首先要跑通一个faster rcnn模型,比如先跑通公共数据集,证明模型是没问题的。第二步就是要自己制作数据集用于训练和测试。制作自己的目标检测数据集有两种方法,第一种是自己写,第二种是把自己的数据集改成和公共数据集一样的结构。当然第二种方法更简单。会了第二种方法,再自己重头开始写数据集的时候就不会那么难受。
系统ubuntu16。假设开始之前你已经有了一个可以跑通的faster rcnn模型,需要用自己的数据集进行训练。
除去语义分割的部分,主要用到VOC2007文件夹下的Annotations文件夹、ImageSets文件件、JPEGImages文件夹。用到的目标检测voc格式大致如下:
PASCAL_VOC07
└──VOCdevkit
├ local
├ results
├ VOC2007
│ ├── Annotations
│ ├── ImageSets
│ │ └── Main
│ └── JPEGImages
└ VOCcode
Annotations文件夹存放的是xml文件,它们是一些标注信息。比如VOC数据集中000001图片的标注信息在<annotation></annotation>中,<filename></filename>中间存放的文件名,图片的长、宽、通道存放在<size></size>中,<segmented>0</segmented>表示这张图片不用来做语义分割,每一个<object></object>表示一个物体,其中<name></name>表示物体名称,<pose></pose>是拍摄角度,<truncated>1</truncated>表示物体被截断,<difficult>0</difficult>表示检测难度为简单。<></>
<annotation>
<folder>VOC2007</folder>
<filename>000001.jpg</filename>
<source>
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
<flickrid>341012865</flickrid>
</source>
<owner>
<flickrid>Fried Camels</flickrid>
<name>Jinky the Fruit Bat</name>
</owner>
<size>
<width>353</width>
<height>500</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>dog</name>
<pose>Left</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>48</xmin>
<ymin>240</ymin>
<xmax>195</xmax>
<ymax>371</ymax>
</bndbox>
</object>
<object>
<name>person</name>
<pose>Left</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>8</xmin>
<ymin>12</ymin>
<xmax>352</xmax>
<ymax>498</ymax>
</bndbox>
</object>
</annotation>
最简单办法就是把自己的数据集格式改成voc的格式,最重要的,也是需要改的地方主要是<filename></filename>、<size></size>以及<object></object>中的<name></name>和<bndbox></bndbox>。这些修改可以使用标注工具label me完成。label me可以选择保存为voc的xml格式。
ImageSets文件夹中只使用到Main文件夹。Main文件夹中包含一些txt文件,这些txt以A_test、A_train、A_trainval命名。表示test/train/trainval有哪些图片,test/train/trainval的图片是否有A类。比如在A_test.txt中,000001 -1表示000001 图片属于test,-1表示000001 图片中没有A类。
000001 -1
000002 -1
000003 -1
……
可以写个python小程序把已有的xml信息做成符合A_test.txt、A_train.txt、A_trainval.txt、B_test.txt、B_train.txt、B_trainval.txt的格式。假设已经制作好了voc格式的标注信息,并且放在path下,简单假设只有A、B、C3个类。首先把它们存到不同的list中。
import os
import xml.etree.cElementTree as ET
A , B , C = [] ,[] , []
for file in os.listdir(path):
tree = ET.parse(os.path.join(path , file))
root = tree.getroot()
for obj in root.findall('object'):
name = obj.find('name').text
if name == 'A':
file = root.find('path').text
file = file.split("\\")
file = file[-1][:-4]
A.append(file)
elif name == 'B':
file = root.find('path').text
file = file.split("\\")
file = file[-1][:-4]
B.append(file)
elif name == 'C':
file = root.find('path').text
file = file.split("\\")
file = file[-1][:-4]
C.append(file)
剩下的是随机将数据集分成train和test,还有一些txt的操作,这些操作和你的数据集有关,代码需要根据自己的数据集来写。上面完成了A_test.txt、A_train.txt、A_trainval.txt的制作。
JPEGImages文件夹中存放的都是图片文件,没什么好说的,把自己的图片放在JPEGImages文件夹中就行。
现在你的voc数据集就制作完成了。
制作完数据集还需要相应的修改代码。如果你的数据集少,没有办法分成train、val、test三部分,可以只分成train和test两部分,factory.py中有一段代码
# Set up voc_<year>_<split>
for year in ['2007', '2012']:
for split in ['train', 'val', 'trainval', 'test']:
name = 'voc_{}_{}'.format(year, split)
__sets[name] = (lambda split=split, year=year: pascal_voc(split, year))
pascal_voc(split, year)生成了pascal_voc子类,在pascal_voc.py文件中根据实际情况需要修改pascal_voc构造函数的路径以及类别。
class pascal_voc(imdb):
def __init__(self, image_set, year, devkit_path=None):
imdb.__init__(self, 'voc_' + year + '_' + image_set)
self._year = year
self._image_set = image_set
self._devkit_path = self._get_default_path() if devkit_path is None else devkit_path
self._data_path = os.path.join(self._devkit_path, 'VOC' + self._year)
self._classes = ('__background__', # always index 0
'aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair',
'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor')
#balabala....
pascalvoc(‘train’, ‘2007’)表示生成pascal voc2007格式的训练数据集,pascalvoc(‘test’, ‘2007’)表示生成pascal voc2007格式的测试数据集。同时,如果你的数据集只有训练和测试集,ImageSets/Main中的txt文件就不再是一个类有3个txt,而是只有A_train.txt和A_test.txt。所以制作txt的代码也需要修改。
上面基本是跑通自己的目标检测数据集需要修改的地方,如果已经有一个跑通了的目标检测网络,就可以开始训练自己的数据集了。
下面记录一下关于数据集代码的调用关系,实在是太复杂了。
从开始的imdb, roidb, ratio_list, ratio_index = combined_roidb(imdb_name)
在get_roidb函数中,首先用到datasets/factory.py的get_imdb函数,get_imdb函数返回相应的pascalvoc(‘xxx’, ‘yyyy’)的pascalvoc类(imdb基类)。接着get_training_roidb函数从image database获得roi database
然后filter_roidb函数去除没有bounding box的图片,接着rank_roidb_ratio函数按照roi的长宽比排序。
所以返回的imdb, roidb, ratio_list, ratio_index表示image database、roi database、长宽比、长宽比索引。
之后就是写一个torch.utils.data.Dataset子类,将roi database包装成pytorch的Dataset的抽象类。还要写个torch.utils.data.sampler.Sampler基础采样器的子类,控制数据集的采样方式,注意的是如果数据集大小不能被batch_size整除的时候,余下部分的处理。最后,有了上面两个子类,就可以使用torch.utils.data.DataLoader加载数据了。总结就是重写了classtorch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0,……)的dataset和sampler。
剩下的就是送入网络训练。