将用LabelImg标注得到的VOC格式数据集标签(xml文件)转换成COCO格式(json文件)

写在前面的话
制作数据集和处理数据看似是体力活,但对于机器学习和深度学习应用而言是非常重要的,千万不能掉以轻心,要认真对待,及时检查。如果数据有问题或者没处理好,再好的模型也无济于事。


自从Facebook开源了Detectron目标检测框架后,很多原先用VOC格式数据集(指标注文件)训练目标检测模型的人需要将VOC格式的xml标注文件转换成COCO格式的json标注文件,但Detectron中并未提供VOC格式转json格式的官方代码,虽然cocoapi有提供转换好的COCO json格式的Pascal VOC文件以及VOC转COCO的MatlabAPI,但要想把自己用LabelImg标注得到的VOC格式标注文件转换成COCO格式还是要花一番功夫,我自己在转换的时候就遇到了不少问题,于是把过程记录下来,以做日后参考,也希望对有类似需求的朋友有所帮助。

平台环境:Ubuntu16.04

1 安装Matlab

如果电脑没有安装Matalb,需要先安装matlab,我安装的版本是matlab2014b,主要参考了这篇博客Ubuntu 16.04LTS安装MATLAB2014b简明指南,按照博客中的操作步骤来一般都能安装并激活成功,只是我最后的matlab环境变量设置出了问题,不过不影响使用,直接sudo /usr/local/MATLAB/R2014b/bin/matlab 就能成功启动matlab

2 调用cocoapi的MatlabAPI将VOC数据集标签转换为COCO数据集标签

主要参考了这篇博客使用自己的数据集(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<year> //主文件夹可以起其它名字
|_ JPEGImages
|  |_ <im-1-name>.jpg
|  |_ ...
|  |_ <im-N-name>.jpg
|_ Annotations
|  |_ pascal_trainval<year>.json
|  |_ ...
|_ VOCdevkit<year>

后续的用Detectron模型训练的过程可以参考我之前博客: Detectron研读和实践三:用faster_rcnn_R-50-FPN训练PASCAL VOC数据集 1.2节之后的内容,要注意的一点是如果想使用Pascal VOC的测评标准对在自己数据集上训练的模型进行测评的话,Detectron的dataset_catalog.py文件中的验证数据集名字要命名成和’voc_2007_val’或’voc_2012_val’一样的名字,否则测评器evaluator会识别不了。

3 批量修改Pascal VOC格式的xml标注文件

补充上一部分,我对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>new</folder>
    <filename>flir_20180321T104306.jpg</filename>
    <path>/usr/elecEquipment/new/flir_20180321T104306.jpg</path>
    <source>
        <database>Unknown</database>
    </source>
    <size>
        <width>480</width>
        <height>640</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>transformer</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>161</xmin>
            <ymin>222</ymin>
            <xmax>348</xmax>
            <ymax>370</ymax>
        </bndbox>
    </object>
    <object>
        <name>insulator</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>276</xmin>
            <ymin>171</ymin>
            <xmax>365</xmax>
            <ymax>248</ymax>
        </bndbox>
    </object>
</annotation>

修改后的xml标注文件

<annotation>
    <folder>VOC2007</folder>
    <filename>00001.jpg</filename>
    <source>
        <database>Unknown</database>
    </source>
    <size>
        <width>480</width>
        <height>640</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>transformer</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>161</xmin>
            <ymin>222</ymin>
            <xmax>348</xmax>
            <ymax>370</ymax>
        </bndbox>
    </object>
    <object>
        <name>insulator</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>276</xmin>
            <ymin>171</ymin>
            <xmax>365</xmax>
            <ymax>248</ymax>
        </bndbox>
    </object>
</annotation>
发布了92 篇原创文章 · 获赞 127 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/Blateyang/article/details/80655802