【学习笔记】使用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的坐标对应同比例变化,等按照上述代码实验一下,看看是否可行。

猜你喜欢

转载自blog.csdn.net/yourgreatfather/article/details/83996031
今日推荐