目录:
- XML 介绍
- ElementTree 基础讲解 2.1 节点属性 2.2 节点搜索
- 获取矩形框的坐标和类别
- 参考
我需要对目标检测标注工具 labelimg 得到的xml文件进行解析,得到其中矩形框对应的类别和坐标值,所以对python下面的ElementTree
研究了一下。
1. XML 介绍
XML全称为可扩展标记语言(Extensible Markup Language),在很多应用程序中XML编码格式的数据被用来在系统之间构建、存储和传输数据。现在用来与服务端交互的主要是Json和XML格式的数据,常用于网页数据抓取和解析结构化文档。
本文使用python内置的ElementTree
包来解析XML文件,从而提取出有用的数据。
ElementTree
将整个XML文件放到内存中,将XML数据在内存中解析成一个树结构,通过树来操作XML,可以任意遍历树的节点,树中的每个节点都是一个对象。
XML 是一种分级的树状数据形式,它被表示为一棵树,ET 有两个对象来实现这个目的:
- ElementTree 将整个 XML 文件解析为一棵树;
- Element 将单个结点及其子节点解析为树。
2. ElementTree 基础讲解
我用这个 XML 文件做例子进行讲解:
<data version="2.0">
<items>
<item name="item1"> item1abc </item>
<item name="item2"> item2abc </item>
</items>
<object> human </object>
</data>
首先导入相关模块并加载xml文件:
import xml.etree.ElementTree as ET
# 加载xml文件
tree = ET.parse('10.xml')
# tree = ET.ElementTree(file='10.xml')#
# 获得root Element节点
root = tree.getroot()
ET.parse
和 ET.ElementTree
都可以加载xml文件,好像没什么区别,前者用得比较多。
getroot()
得到xml树的根节点,一旦我们能够访问根节点,我们就可以很容易地遍历树中的所有子结点(Element),因为树是一个连通图。
2.1 节点属性
还可以获得节点的一些属性,一个节点通常为:
<tag attrib> text </tag> tail
比如下面这个例子:
<item name='zll'> hello123 </item>
tag
:表示节点名字,即itemattrib
:即属性,用字典的形式保存,即{'name': 'zll'}text
:文本字符串,可以用来存储一些数据,即hello123,当这之间是其子节点时,此处为空字符tail
:尾字符串,并不是必须的,例子中没有这个属性。
查看根节点的属性:
print("tag: {}, attrib: {}".format(root.tag, root.attrib))
# 输出:
# tag: data, attrib: {'version': '2.0'}
查看根节点下一级子节点(items和object):
print(len(root[0])) # 连接根节点的子节点数
for elem in root:
print(elem.tag, elem.attrib, elem.text)
# 输出:
# 2
# items {}
# object {} human
两个子节点的 attrib
都为空字典,且因为 items
下面还有子节点,所以 items.text
为空。
也可以通过索引选择子结点:
print(root[0][0].tag, root[0][0].attrib, root[0][0].text)
# 输出
# ('item', {'name': 'item1'}, 'item1abc')
可以通过递归获取 XML 中的所有节点,ElementTree
对象和 Element
对象有一个 iter
方法可以创建一个树迭代器,以对所有结点进行深度优先遍历。
# 遍历xml文件中的所有节点
for elem in tree.iter():
print(elem.tag, elem.attrib)
# 输出:
# data {'version': '2.0'}
# items {}
# item {'name': 'item1'}
# item {'name': 'item2'}
# object {}
iter()
方法也可以接收一个tag,表示只遍历指定名字的节点:
for elem in tree.iter(tag='item'):
print(elem, elem.tag, elem.text)
# 输出:
# <Element 'item' at 0x7f86d0179958> item item1abc
# <Element 'item' at 0x7f86d01798b8> item item2abc
2.2 节点搜索
当XML文件较大或者其中的子节点 tag 非常多的时候,一个一个获取比较麻烦而且有很多不是需要的,可以通过 find()
或者 findall()
方法来查找指定 tag 的节点。
find(match)
:获取树中第一个与tag或path匹配的子节点findall(match)
:获取所有与tag或path匹配的子节点,并以列表的形式返回findtext(match)
:获取树中第一个与tag或path匹配的子节点的文本
for elem in root:
print(elem.find('item'))
print(elem.findtext('item'))
# 输出:
# <Element 'item' at 0x7f86d0179d68>
# item1abc
# None
# None
上面是找到节点中第一个名为item
的子节点和其文本内容,因为 object
节点没有名为 item
的子节点,所以返回 None。
下面程序为找到当前节点下名为item
的所有子节点:
for elem in root:
for subelem in elem.findall('item'):
print(subelem.attrib)
print(subelem.get('name'))
# 输出:
# {'name': 'item1'}
# item1
# {'name': 'item2'}
# item2
上面是只通过 tag(节点名)来查找节点,也可以通过节点的路径来找到所有节点:
for elem in root.findall('./items/item'): # path
print(elem.attrib)
# 输出:
# {'name': 'item1'}
# {'name': 'item2'}
3. 获取矩形框的坐标和类别
我用从 imglabel 工具得到的一个xml文件作为例子:
<annotation version="2.0">
<folder>images</folder>
<filename>00001.jpg</filename>
<path>C:imglabelimages00001.jpg</path>
<source>
<database>Unknown</database>
</source>
<size>
<width>33</width>
<height>495</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>dog</name>
<pose>Unspecified</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>139</xmin>
<ymin>152</ymin>
<xmax>221</xmax>
<ymax>257</ymax>
</bndbox>
</object>
<object>
<name>cat</name>
<pose>Unspecified</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>55</xmin>
<ymin>46</ymin>
<xmax>122</xmax>
<ymax>113</ymax>
</bndbox>
</object>
</annotation>
这个文件主要有如下内容:
- (folder)文件夹名称
- (filename)图片名
- (path)文件路径
- (size)图片宽高及深度
- (object->name)目标的类别名称
- (object->bndbox)边框坐标
我们主要是想获得的是 object
节点下的 name
和 bndbox
,它们分别对应矩形框的目标类别和坐标值。
程序如下:
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file="10.xml")
root = tree.getroot()
boxes = []
for elem in root.iter(tag='object'):
# 获取矩形框坐标
box = [int(el.text) for el in elem.find('bndbox')]
# 获取目标类别
box.append(elem.find('name').text)
boxes.append(box)
print(boxes)
# 输出:
# [[139, 152, 221, 257, 'dog'], [55, 46, 122, 113, 'cat']]
上面涉及到的知识点前面都有讲解,就不多说了,最后输出结果为一个列表,列表中的每个元素为一个矩形框的坐标值和类别。
4. 参考
xml.etree.ElementTree API
Reading and Writing XML Files in Python
Python XML with ElementTree: Beginner's Guide
Working with XML files in Python