最近在項目中使用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,但前兩者已經足夠應付大部分需求,在這里就不展開介紹了。