写在前面的话:
制作数据集和处理数据看似是体力活,但对于机器学习和深度学习应用而言是非常重要的,千万不能掉以轻心,要认真对待,及时检查。如果数据有问题或者没处理好,再好的模型也无济于事。
自从Facebook开源了Detectron目标检测框架后,很多原先用VOC格式数据集(指标注文件)训练目标检测模型的人需要将VOC格式的xml标注文件转换成COCO格式的json标注文件,但Detectron中并未提供VOC格式转json格式的官方代码,虽然cocoapi有提供转换好的COCO json格式的Pascal VOC文件以及VOC转COCO的MatlabAPI,但要想把自己用LabelImg标注得到的VOC格式标注文件转换成COCO格式还是要花一番功夫,我自己在转换的时候就遇到了不少问题,于是把过程记录下来,以做日后参考,也希望对有类似需求的朋友有所帮助。
平台环境:Ubuntu16.04
如果电脑没有安装Matalb,需要先安装matlab,我安装的版本是matlab2014b,主要参考了这篇博客Ubuntu 16.04LTS安装MATLAB2014b简明指南,按照博客中的操作步骤来一般都能安装并激活成功,只是我最后的matlab环境变量设置出了问题,不过不影响使用,直接sudo /usr/local/MATLAB/R2014b/bin/matlab
就能成功启动matlab
主要参考了这篇博客使用自己的数据集(voc2007格式)训练Detectron
从github下载cocoapi,然后matlab新建脚本文件,并将cocoapi下MatlabAPI添加到路径或在matlab中直接将当前路径切换到MatlabAPI路径下。
matlab脚本如下:
mex('CXXFLAGS=\$CXXFLAGS -std=c++11 -Wall','-largeArrayDims',...
'private/gasonMex.cpp','../common/gason.cpp',...
'-I../common/','-outdir','private');
CocoUtils.convertPascalGt( 'D:/datasets', '2007', 'trainval', 'D:/datasets/pascal_trainval2007.json')
CocoUtils.convertPascalGt( 'D:/datasets', '2007', 'test', 'D:/datasets/pascal_test2007.json')
上面脚本里面的 trainval和test对应的是包含相应图片名的txt文件,也就是类似Pascal VOC文件夹Annotations/Main下的文件。
convertPascalGT函数说明如下
% Convert ground truth for PASCAL to COCO format.
%
% USAGE
% CocoUtils.convertPascalGt( dataDir, year, split, annFile )
%
% INPUTS
% dataDir - dir containing VOCdevkit/
% year - dataset year (e.g. '2007')
% split - dataset split (e.g. 'val')
% annFile - annotation file for writing results
在执行上面的matlab文件时,可能会遇到类似“未定义函数或变量’VOCinit’”的错误,这是因为在dataDir的VOCdevkit路径下可能少了VOCcode文件夹,该文件夹里面含有VOCinit.m文件,该文件夹可以从Pascal VOC的VOCdevkit文件夹中拷贝过来,但要让convertPascalGT函数能识别出自己数据集的目标检测类别时需要修改VOCinit.m文件中的VOCopts.classes变量,把里面的Pascal VOC类别换成自定义的类别标签。如下:
1. VOCopts.classes={...
2. '你的标签1'
3. '你的标签2'
4. '你的标签3'
5. '你的标签4'};
转换结束后,务必要查看一下生成的json文件里面的内容是否与预期的相符,我在检查的时候就发现里面的images字段的id都是”-nan”,对比原始Pascal VOC数据集的xml和自己数据集的xml文件后才发现自己数据集的xml文件中filename字段包含了非数字字符,也就是说我自己数据集的图片名并不是完全用数字编号的,而Pascal VOC数据集的图片均是用数字编号的,于是只得回过头来把自己数据集的所有xml标注文件都进行修改(参见第3部分)。修改完并检查无误后,把生成的json文件标签按照如下的文件夹结构放入到Annotations下,即可用于后续Detectron的模型训练。
VOC //主文件夹可以起其它名字
|_ JPEGImages
| |_ 1-name>.jpg
| |_ ...
| |_ .jpg
|_ Annotations
| |_ pascal_trainval.json
| |_ ...
|_ VOCdevkit
后续的用Detectron模型训练的过程可以参考我之前博客: Detectron研读和实践三:用faster_rcnn_R-50-FPN训练PASCAL VOC数据集 1.2节之后的内容,要注意的一点是如果想使用Pascal VOC的测评标准对在自己数据集上训练的模型进行测评的话,Detectron的dataset_catalog.py文件中的验证数据集名字要命名成和’voc_2007_val’或’voc_2012_val’一样的名字,否则测评器evaluator会识别不了。
补充上一部分,我对xml文件的修改主要包括修改filename和folder字段、删除path字段,在修改xml文件的同时也把对应的图片名改掉了,批量修改xml建议使用xml.etree.ElementTree — The ElementTree XML API,另外部分参考了博客修改别人标注好的数据集xml文件,使用别人的数据集训练自己的网络,下面是我的修改代码以及修改前后的xml标注文件:
import cv2
from xml.etree.ElementTree import ElementTree,Element
def modify_xml(data_root_path):
"""Modify folder,filename and delete path node of xml files"""
annots_path = os.path.join(data_root_path, 'Annotations')
imgs_path = os.path.join(data_root_path, 'JPEGImages')
num_per_class = {'transformer':0, 'insulator':0, 'switch':0, 'fuse':0}
for i,annot in enumerate(os.listdir(annots_path)):
if annot.split('.')[-1] == 'xml':
xml_path = os.path.join(annots_path, annot)
tree = ElementTree()
tree.parse(xml_path)
# modify folder node
folder = tree.find("folder")
folder.text = "VOC2007"
# modify filename node
filename = tree.find("filename")
new_name = '0'*(5-len(str(i+1)))+str(i+1)
filename.text = new_name +'.jpg'
# delete path node
root = tree.getroot()
path = root.find("path")
root.remove(path)
# count number of each class
for object in root.findall('object'):
name = object.find('name')
if name.text not in num_per_class.keys():
print annot
num_per_class[name.text] += 1
print num_per_class
# save xml file
new_xml_path = os.path.join(data_root_path, new_name+'.xml')
# 注意写xml文件时要去掉xml开头的版本信息(xml_declaration=False),
# 否则用MatlabAPI转格式时会出错
tree.write(new_xml_path, encoding="utf-8",xml_declaration=False)
# modify img name
print str(i+1)+":modifying "+annot.split('.')[0]+'.jpg'
img_path = os.path.join(imgs_path, annot.split('.')[0]+'.jpg')
img = cv2.imread(img_path)
new_img_path = os.path.join(data_root_path, new_name+'.jpg')
cv2.imwrite(new_img_path, img)
print 'finished'
修改前的xml标注文件:
<annotation>
<folder>newfolder>
<filename>flir_20180321T104306.jpgfilename>
<path>/usr/elecEquipment/new/flir_20180321T104306.jpgpath>
<source>
<database>Unknowndatabase>
source>
<size>
<width>480width>
<height>640height>
<depth>3depth>
size>
<segmented>0segmented>
<object>
<name>transformername>
<pose>Unspecifiedpose>
<truncated>0truncated>
<difficult>0difficult>
<bndbox>
<xmin>161xmin>
<ymin>222ymin>
<xmax>348xmax>
<ymax>370ymax>
bndbox>
object>
<object>
<name>insulatorname>
<pose>Unspecifiedpose>
<truncated>0truncated>
<difficult>0difficult>
<bndbox>
<xmin>276xmin>
<ymin>171ymin>
<xmax>365xmax>
<ymax>248ymax>
bndbox>
object>
annotation>
修改后的xml标注文件
<annotation>
<folder>VOC2007folder>
<filename>00001.jpgfilename>
<source>
<database>Unknowndatabase>
source>
<size>
<width>480width>
<height>640height>
<depth>3depth>
size>
<segmented>0segmented>
<object>
<name>transformername>
<pose>Unspecifiedpose>
<truncated>0truncated>
<difficult>0difficult>
<bndbox>
<xmin>161xmin>
<ymin>222ymin>
<xmax>348xmax>
<ymax>370ymax>
bndbox>
object>
<object>
<name>insulatorname>
<pose>Unspecifiedpose>
<truncated>0truncated>
<difficult>0difficult>
<bndbox>
<xmin>276xmin>
<ymin>171ymin>
<xmax>365xmax>
<ymax>248ymax>
bndbox>
object>
annotation>