将自己的数据集转换成pascal_voc格式

在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文件样例:


VOC2007
000002.jpg

335
500
3


cat
Unspecified
0
0

139
200
207
301


这里,需要注意的信息主要有:,这些属于某张图片的基本信息;而后在里记录的则是标注信息,主要有两个,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_
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’)]

参考资料

关于Faster-RCNN的模型训练时的数据的解释请参阅:faster-rcnn 之训练数据是如何准备的 xml文件的转换的有关内容请参阅这篇高质量的博客:使用自己的数据集训练 R-CNN