【學習筆記】使用python批量讀取並修改xml檔案
在大老闆的安排下最近在某公司實習,實習期間要求實現一個影象識別模組的封裝。無奈基礎太薄弱,只能將任務細分,單獨學習來實現。以此為背景……
本篇目標:通過python批量訪問並修改xml檔案。
目前,存在的問題是,標註好一批圖片後,若改變圖片尺寸,則原始的xml檔案中的bnbbox資料作廢,針對改變尺寸後的圖片還得重新標註。費事費力,在模組封裝任務中也必須解決這個問題。因此,目前急需實現批量修改xml檔案,減少標註工作量。
主要參考部落格:coding思想博主的博文。
1.引入
我所使用的xml檔案為labelImg程式標註的Pascal VOC格式的xml檔案,檔案格式如下:
<annotation> <folder>img_oil_leakage</folder> <filename>A0000005.jpg</filename> <path>/home/kanghao/catkin_ws/src/ros_tf_cv_key/scripts/img_oil_leakage/A0000005.jpg</path> <source> <database>Unknown</database> </source> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>A</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>200</xmin> <ymin>100</ymin> <xmax>409</xmax> <ymax>233</ymax> </bndbox> </object> </annotation>
其中<folder>標籤表示圖片所在的資料夾,<filename>表示對應圖片名稱;<path>對應圖片地址;<source>這裡記不清了;緊接著就是<size>;是標註圖片時圖片的長寬以及通道數;<segmented>表示是否用於語義分割,不是就寫0;<name>表示標註影象中該類的名稱;<pose>印象中表示拍攝角度?是做左邊還是右邊?(存疑);<truncated>記不清了!;<difficult>表示影象中的目標是否難以識別;<bndbox>重中之重,表示我們標註影象時bounding box的左下角以及右上角的座標值。
在上述標籤中,希望改變的是<path>和<bndbox>。由淺入深,先從修改單一xml檔案說起。
2.修改單一xml檔案
#coding=utf-8
import xml.dom.minidom
###讀取單個xml檔案
dom=xml.dom.minidom.parse('A0000005.xml')
root=dom.documentElement
###獲取標籤對xmin/ymin之間的值
xmin=root.getElementsByTagName('xmin')
ymin=root.getElementsByTagName('ymin')
###原始資訊
print('原始資訊')
xi0 = xmin[0]
print xi0.firstChild.data
# ~ b = unicode.encode(xi0.firstChild.data)
# ~ yi0 = ymin[0]
# ~ print yi0.firstChild.data
###修改標籤對之間的資訊
###疑問?如何將xi0.firstChild.data資料轉為int變數?
###如何讓該式直接可以運算xi0.firstChild.data=xi0.firstChild.data/2
xi0.firstChild.data=200
# ~ xi0.firstChild.data=int(b)/100
yi0.firstChild.data=60
#列印輸出(修改後)
print xi0.firstChild.data
print yi0.firstChild.data
先貼程式碼,python中針對xml檔案使用xml.dom.minidom包。基本按照註釋就可以理解程式碼了。理解程式碼後,我產生了一個想法,那就是我們是否可以在大孩.資料(咳咳,估計惡意機翻一波),就是firstChild.data賦值替換語句中,我們針對不同的圖片是否可以直接讀取它的值,併除一個相同的比例呢?立刻按照想法實踐後發現,firstChild.data返回的資料格式是unicode格式,我們需要int與int格式之間進行運算。
於是,請注意這一句:
b = unicode.encode(xi0.firstChild.data)
我們將純數字的unicode格式轉換為str格式,再使用簡單的int()函式將str格式轉換為int格式。
b = unicode.encode(xi0.firstChild.data)
便實現了我們“一勞永逸”針對單個xml檔案的讀取與修改(修改結果並未儲存在xml檔案中)。
3.遍歷xml檔案
完成了對單一xml檔案的讀取與修改,我們如何遍歷所有的xml檔案呢?首先我們先實現遍歷資料夾中的所有檔案(是的,下面的程式碼會遍歷同一資料夾中所有格式的檔案……好在資料集中xml檔案都是單獨存放)
#coding=utf-8
import os
import os.path
### import xml.dom.minidom
path="/home/×××××/SSD-Tensorflow/VOC2007/Annotations/"
files=os.listdir(path) #得到資料夾下所有檔名稱
for xmlFile in files: #遍歷資料夾
if not os.path.isdir(xmlFile): #判斷是否是資料夾,不是資料夾才打開
print xmlFile
#遍歷指定路徑下的檔案
程式碼執行效果如下圖所示:
在實現了這個功能後,原博主的思想是將遍歷程式碼和修改單個xml檔案程式碼進行融合,融合後的程式碼為:
#coding:utf-8
import os
import os.path
import xml.dom.minidom
#path="../xml/"
path='/home/kanghao/learning_something/about_xml/xml/'
files=os.listdir(path) #得到資料夾下所有檔名稱
s=[]
for xmlFile in files: #遍歷資料夾
if not os.path.isdir(xmlFile): #判斷是否是資料夾,不是資料夾才打開
print xmlFile
#xml讀取操作
#將獲取到的xml檔名送入到dom解析
#錯誤程式碼:dom=xml.dom.minidom.parse(xmlFile)
dom=xml.dom.minidom.parse(xmlFile) ####錯誤出現在這裡
###dom=xml.dom.minidom.parse(os.path.join(path,xmlFile)) #拼接地址,將每個具體的.xml檔案帶入
root=dom.documentElement
###獲取標籤對xmin/ymin之間的值
xmin=root.getElementsByTagName('xmin')
ymin=root.getElementsByTagName('ymin')
###原始資訊
print('原始資訊')
xi0 = xmin[0]
print xi0.firstChild.data
a= xi0.firstChild.data
print(type(a))
yi0 = ymin[0]
print yi0.firstChild.data
###修改標籤對之間的資訊
###疑問?如何將xi0.firstChild.data資料轉為int變數?
###如何讓該式直接可以運算xi0.firstChild.data=xi0.firstChild.data/2
###下面這個方法可以將純數字的unicod格式轉換為str格式
# ~ b = unicode.encode(xi0.firstChild.data)
# ~ print(b)
# ~ print(type(b))
xi0.firstChild.data=100
yi0.firstChild.data=60
#列印輸出(修改後)
print xi0.firstChild.data
print yi0.firstChild.data
print '##################'
注意看標註了“#錯誤出現在這裡的”那句語句,對比讀取單個xml的程式碼,我的理解是這裡雖然讀取到了xml檔案,但是傳入
dom=xml.dom.minidom.parse(xmlFile)
這句時僅僅傳入了一個檔名稱,並沒有具體地址所在地。所以會報錯:
報錯圖片來自這裡。因此,原作者使用了python中的地址拼接,即將檔案地址和檔名同時傳入。 也就是
###dom=xml.dom.minidom.parse(os.path.join(path,xmlFile)) #拼接地址,將每個具體的.xml檔案帶入
這一句,在執行程式碼後,輸出結果如下:
至此,我們完成了遍歷資料夾中的xml檔案,讀取標籤值,並將讀取到的標籤值替換為我們需要的值。然而,一個很重要的問題就是,現在只是讀取標籤值,賦新值並列印顯示,可是最後修改的結果並未儲存在xml檔案中。接下來,我們就要將我們修改的結果儲存在xml檔案中。
4.批量讀取xml檔案修改並儲存
現在,我們實現剛才沒有儲存修改結果到xml檔案中的功能,主要程式碼如下:
#儲存修改到xml檔案中
with open(os.path.join(path,xmlFile),'w') as fh:
dom.writexml(fh)
print('恭喜,寫入xmin/ymin成功!')
整體程式碼為:
#coding:utf-8
import os
import os.path
import xml.dom.minidom
#path="../xml/"
path='/home/kanghao/learning_something/about_xml/xml/'
files=os.listdir(path) #得到資料夾下所有檔名稱
s=[]
for xmlFile in files: #遍歷資料夾
if not os.path.isdir(xmlFile): #判斷是否是資料夾,不是資料夾才打開
print xmlFile
#xml讀取操作
#將獲取到的xml檔名送入到dom解析
#錯誤程式碼:dom=xml.dom.minidom.parse(xmlFile)
dom=xml.dom.minidom.parse(os.path.join(path,xmlFile))
root=dom.documentElement
###獲取標籤對xmin/ymin之間的值
xmin=root.getElementsByTagName('xmin')
ymin=root.getElementsByTagName('ymin')
#修改相應標籤的值
for i in range(len(xmin)):
print xmin[i].firstChild.data
a = xmin[i].firstChild.data
print(type(a))
xmin[i].firstChild.data=200
print xmin[i].firstChild.data
for j in range(len(ymin)):
print ymin[j].firstChild.data
ymin[j].firstChild.data=100
print ymin[j].firstChild.data
#儲存修改到xml檔案中
with open(os.path.join(path,xmlFile),'w') as fh:
dom.writexml(fh)
print('恭喜,寫入xmin/ymin成功!')
程式碼的執行效果如下:
我們讀取每個xml檔案中xmin與ymin的值,並分別賦值200和100,並儲存在xml檔案中。不妨讓我們檢查一下每個xml檔案:
經檢查,資料夾中每個xml檔案對應的資料均已改變。至此,我們實現了批量讀取xml修改並儲存的功能。我們可以發現,遍歷xml檔案並不是按照順序遍歷,彷彿是隨機遍歷?但是感覺對使用沒有影響,這裡先不糾結了。
接下來的想法,是改變圖片尺寸後,x、y的座標對應同比例變化,等按照上述程式碼實驗一下,看看是否可行。