在大老板的安排下最近在某公司实习,实习期间要求实现一个图像识别模块的封装。无奈基础太薄弱,只能将任务细分,单独学习来实现。以此为背景……
本篇目标:通过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的坐标对应同比例变化,等按照上述代码实验一下,看看是否可行。