最近在项目中使用TensorFlow训练目标检测模型,在制作自己的数据集时使用了labelimg软件对图片进行标注,产生了VOC格式的数据,但标注生成的xml文件标签值难免会产生个别错误造成程序无法跑通,或后期有修改xml中标签值的需求,所以得使用Python代码对xml文件进行解析操作,当然也是参考了各种博客,故在此总结一下。

1. xml文件格式

由labelimg标注生成的xml文件格式如下所示,



<annotation>
    <folder>images1</folder>
    <filename>0.png</filename>
    <path>C:\Users\White\Desktop\images1\0.png</path>
    <source>
        <database>Unknown</database>
    </source>
    <size>
        <width>1080</width>
        <height>1920</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>box</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>345</xmin>
            <ymin>673</ymin>
            <xmax>475</xmax>
            <ymax>825</ymax>
        </bndbox>
    </object>
    <object>
        <name>box</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>609</xmin>
            <ymin>1095</ymin>
            <xmax>759</xmax>
            <ymax>1253</ymax>
        </bndbox>
    </object>
</annotation>



xml是可扩展标记语言,主要用来标记数据和定义数据类型,从结构上看,有以下特点:

只有一个根结点,其中结点中可以有属性;

一个结点由标签对组成,如<folder></folder>;

结点的标签对可以有属性,如<object  id = 0></object>;

标签对中可以存储数据,如<object  id = 0>123</object>;

结点可以相互嵌套,形成子结点,如<object  id = 0>  <name>box</name></object>;

2.xml.dom解析xml

文件对象模型(Document Object Model,简称DOM),dom在解析xml文件时一次性将整个文档加载至内存,在内存中使用树结构来保存xml文件中的标签元素和结构,同时你可以使用dom中的函数来解析获取文件信息或修改保存信息,以下为使用dom统计某个文件夹下xml文件中每类标签数量的Python代码,



#coding:utf-8
import os.path
import xml.dom.minidom

class_nums = {"box": 0, "person": 0}
n = 0
xmldir = "E:\\Project\\object\\merged_xml"

for xmlfile in os.listdir(xmldir):
     print(xmlfile)
     # 打开xml文件
     dom = xml.dom.minidom.parse(os.path.join(xmldir, xmlfile))
     # 获得元素对象
     root = dom.documentElement
     name_length = len(root.getElementsByTagName('name'))
     # 获取标签对name之间的值
     for i in range(name_length):
          key = str(root.getElementsByTagName('name')[i].firstChild.data)
          #print(key)
          if key in class_nums.keys():
              class_nums[key] += 1
     # print('node', root.getElementsByTagName('filename')[0].firstChild.data)
     # print('node', filename.firstChild.data)
     n += 1
print('processed file number is ', n)
for key in class_nums:
   print(key, ':', class_nums[key])



1. 导入Python中的xml.dom文件解析模块



import xml.dom.minidom



2. 获取dom对象和结点:xml.dom.minidom是Python用于处理xml文件的模块,其具有函数xml.dom.minidom.parse()



dom = xml.dom.minidom.parse(os.path.join(xmldir, xmlfile))



该函数传入xml文件的路径和文件名的字符串,用于打开一个xml文件,并得到dom文档树,并通过documentElement得到根结点,



root = dom.documentElement

name_length = len(root.getElementsByTagName('name'))



如果我们知道结点的名称,可以通过root.getElementsByTagName('  ')来获得所有名称等于传入字符串的结点,并可以通过索引来获得相同名称的不同结点,如下所示,



name0 = root.getElementsByTagName('name')[0]

name1 = root.getElementsByTagName('name')[1]



4. 获取标签对之间的数据:firstChild 属性返回被选节点的第一个子节点,.data表示获取该节点的数据。



key = str(root.getElementsByTagName('name')[i].firstChild.data)



5. 补充:获取标签的属性值getAttribute(),以前面的<object  id = 0>123</object>为例,



objectlist = root.getElementsByTagName('object')
object0 = objectlist[0]
id0=object0.getAttribute("id")



3.xml.etree.ElementTree解析xml

ElementTree也是处理xml文件的模块,其具有两种类型,python实现型的xml.etree.ElementTree和c语言实现型的xml.etree.cElementTree,后者比前者的速度更快,内存消耗更少。以下为将xml文件转换为CSV文件的代码示例,



#coding:utf-8
import os
import glob  
import pandas as pd  
import xml.etree.ElementTree as ET  


def xml_to_csv(path):  
    xml_list = []  
    for xml_file in glob.glob(path + '/*.xml'):
        #返回解析树
        tree = ET.parse(xml_file)
        #获取根节点
        root = tree.getroot()  
        # print(root)
        # 根据标签名查找root下的所有标签,并获取其值
        print(root.find('filename').text)
        #对所有目标进行解析
        for member in root.findall('object'):  
            value = (root.find('filename').text,  
                     int(root.find('size')[0].text),   #width  
                     int(root.find('size')[1].text),   #height  
                     member[0].text,  #object name
                     int(member[4][0].text),  #xmin
                     int(float(member[4][1].text)),  #ymin
                     int(member[4][2].text),   #xmax
                     int(member[4][3].text)    #ymax
                     )  
            xml_list.append(value)  
    column_name = ['filename', 'width', 'height', 'class', 'xmin', 'ymin', 'xmax', 'ymax']
    #pandas创建带列名的二维数据表
    xml_df = pd.DataFrame(xml_list, columns=column_name)  
    return xml_df



1.首先导入解析模块,



import xml.etree.ElementTree as ET



2. 返回解析树和获取根节点,



#返回解析树
        tree = ET.parse(xml_file)
        #获取根节点
        root = tree.getroot()



3. 可以通过根节点root可以遍历下一层的节点,.tag和.attrib可以获取标签名和标签属性,.text获取标签中的值,



for rootChild in root:
    print( 'tagname:', rootChild.tag, 'attribute:', rootChild.attrib, 'value:', rootChild.text)



4. 可以使用下标访问各层节点,



int(member[4][0].text),  #xmin
int(float(member[4][1].text)),  #ymin



5. 使用findall()查找所有相同标签名的标签,



#根据标签名查找root下的所有标签
    objectList = root.findall("object")



6.通过set和get来修改标签属性名,



object0 = root.findall('object')[0]
id = object0.get('id')
object0.set('id', 111)
id2 = object0.get('id')
print (id, id2)



4. 其他解析方式

  还可以通过xml.sax模块来解析xml,但前两者已经足够应付大部分需求,在这里就不展开介绍了。