在RCNN、Fast-RCNN、Faster-RCNN等一系列深度学习用于目标检测(Object Detection)的众多开源实现里,基本上都是基于pascal_voc的数据集进行处理的,给出了使用该数据集进行训练和测试的完整代码。
诚然,我们可以基于这些开源项目来进行定制,并在自己的数据集跑起来。但这样需要修改大量代码,稍有不慎可能带来很多注意不到的错误。而从另一个方面入手,如果将我们的数据集按照pascal_voc的格式进行转换,然后进行训练,则会简单地多,只需要修改很少量的代码即可。不过这样一来,我们的任务则集中在了将数据集转换为pascal_voc的格式上。
1.pascal_voc数据集介绍
在将我们的数据集转换为pascal_voc的数据格式时,需要注意一下数据放置的路径和文件夹层级结构,原始的pascal_voc结构格式大致如下:
VOCdevkit2007
├── results
│ └── VOC2007
│ └── Main
├── VOC2007
│ ├── Annotations
│ ├── ImageSets
│ │ └── Main
│ └── JPEGImages
└── VOCcode
该数据集表示的是pascal_voc中voc_2007_*的数据集,其中VOCcode中是一些用于数据处理的MATLAB函数文件;
VOC2007放置数据集中的各文件,主要包括JPEGImages中是图片,为jpg格式的文件,如果是其他格式,则需要对代码稍作修改;Annotation是中是标注数据,为xml格式的文件,为下面介绍的结构,如果是其他格式或者标注类型,则需要对代码作相应修改。
JPEGImages和Annotation是这两个文件夹中的各数据一一对应,均是6位数据的文件名;而ImageSets文件夹下面Main文件中则是一系列如train.txt、trainval.txt、val.txt或者test.txt的文件,这些文件实现了对数据集的划分,即将数据集划分为训练集、验证集、测试集等等。文件内容为相应的文件名,但不包含后缀。我们可以据这几个文件来选择数据集的名字,可以设置为如voc_2007_train、voc_2007_trainval或者voc_2007_test等等。
2.pascal_voc数据集的xml标注文件
对Images进行的操作十分简单,无需赘言,其着重需要注意的地方是将Annotations转换为pascal_voc的xml格式。这是pascal_voc的一个xml格式的annotation文件样例:
<annotation>
<folder>VOC2007</folder>
<filename>000002.jpg</filename>
<size>
<width>335</width>
<height>500</height>
<depth>3</depth>
</size>
<object>
<name>cat</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>139</xmin>
<ymin>200</ymin>
<xmax>207</xmax>
<ymax>301</ymax>
</bndbox>
</object>
</annotation>
这里,需要注意的信息主要有:<folder>、<filename>、<size>,这些属于某张图片的基本信息;而后在<object>里记录的则是标注信息,主要有<name>和<bndbox>两个,name是标注的物体的class name,bndbox是该物体的bounding box信息。
我们只需要按照这个格式,并以其为模板,将自己的数据集转换过来即可。需要特别注意的是,有些图片上可能被标注的物体不只有一个。
转换程序的核心代码如下:
- tree = ET.parse(template_file)
- root = tree.getroot()
- # filename
- root.find('filename').text = image_file
- # size
- sz = root.find('size')
- im = cv2.imread(image_dir + image_file)
- sz.find('height').text = str(im.shape[0])
- sz.find('width').text = str(im.shape[1])
- sz.find('depth').text = str(im.shape[2])
- # object
- obj_ori = root.find('object')
- root.remove(obj_ori)
- for al in anno_lines:
- bb_info = al.split()
- x_1 = int(bb_info[1])
- y_1 = int(bb_info[2])
- x_2 = int(bb_info[3])
- y_2 = int(bb_info[4])
- obj = copy.deepcopy(obj_ori)
- obj.find('name').text = bb_info[0].decode('utf-8')
- bb = obj.find('bndbox')
- bb.find('xmin').text = str(x_1)
- bb.find('ymin').text = str(y_1)
- bb.find('xmax').text = str(x_2)
- bb.find('ymax').text = str(y_2)
- root.append(obj)
- xml_file = image_file.replace('jpg', 'xml')
- tree.write(target_dir + xml_file, encoding='utf-8', xml_declaration=True)
首先读入模板xml文件,然后对filename和size进行处理;接着从模板文件中取出object块,以其为模板,根据标注对象的数量来建立一个或多个object,并将其append到xml模板文件中,最后将处理完毕的文件写入到硬盘上。需要注意的是bb_info[0].decode('utf-8')这句,这是因为class name是汉字,而且使用的是Python2,所以需要进行一下处理。
详细的代码请参阅GitHub上的文件。
3.根据数据集修改代码
虽然GitHub上开源的各Faster-RCNN的Repositories已经考虑的相当周到,很多可以直接拿来使用,但如果在自己的数据集上进行训练,还是需要对其进行稍作改动的。而如果自己的数据与pascal_voc的差异较大的话,可能还需要作更多一些的修改。修改一般集中在一下几个方面
3.1 修改数据集
如前述,首先把自己的数据集构建成pascal_voc的数据集格式;但这还不够,有时候可能我们并不像原始的pascal_voc那样,训练验证测试三个数据集都很完备。如果我们只有训练集和验证集,怎么办呢?我们可以将其看为训练集和测试集。训练集使用形如“voc_2007_train”的名字,验证集(或所谓测试集)使用“voc_2007_test”的名字。其对应于1中所述的Main文件夹下的train.txt和test.txt两个文件。然后在repository的lib/datasets文件夹下的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))
此处,可以看到对应于voc_2007_train和voc_2007_test的分别是函数pascal_voc(‘train’, ‘2007’)、pascal_voc(‘test’, ‘2007’)。这是使用pascal_voc类的构造函数。在构造函数里,我们需要进行下一步的修改。
3.2 修改classes
在pascal_voc类的构造函数里,我们可以看到有这样一行代码:
- 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')
其用以构建数据集的classes。但如果我们使用自己的数据集,显然需要对此进行针对性的修改。
3.3 其他修改
在这个构建函数里,可以看到,还可以对关于数据集的其他许多地方进行修改,包括数据集的名称、数据集的路径以及图片的后缀,等等。而如果你不嫌麻烦,或者想直接使用自己的数据集的原始标注而不想将标注文件转换为pascal_voc的xml文件的话,也可以修改这个pascal_voc类的成员函数——gt_roidb,进而修改它调用的私有成员函数_load_pascal_annotation,修改annotations的加载。
3.4 如果classes使用了汉字
如果classes使用了汉字,而你恰好使用的还是Python2,则在_load_pascal_annotaion中加载Annotation时,可能需要注意汉字的影响。即该函数中的这句代码:
- cls = self._class_to_ind[obj.find('name').text.lower().strip()]
如果不进行修改,其可能会提示KeyError,这是编码方式导致的。我将其修改为了:
- cls = self._class_to_ind[obj.find('name').text.strip().encode('utf-8')]
当然,你可能需要根据自己的特殊情况对其进行修改。
此外,如果classes使用了汉字,可能还需要注意其他地方,即所有对classes进行了操作的地方,或者进行比较的地方,不然极可能得不到正确的结果。如:
- R = [obj for obj in recs[imagename] if obj['name'] == classname]
需要修改为:
- R = [obj for obj in recs[imagename] if obj['name'] == classname.decode('utf-8')]