批量修改xml标注框坐标,类别名称,删除子节点,删除属性列表,根据xml切割图片
批量修改xml标注框坐标,类别名称,删除子节点,删除属性列表,根据xml切割图片
·
1. 批量修改xml标注框坐标
- 用训练的模型生成了一些标注框,但是都有些往左上角偏移,一张图里的框又太多,一张张手动调比较费时,根据网上的轮改了改,读取xml中xmin,ymin,xmax,ymax对应节点并对其值进行微调,调整过后标注框的位置就比较合适了,完整代码如下:
- 注意这个是直接对xml进行修改了,为避免出错可以提前备份一份
# coding:utf-8
import os
import os.path
import xml.dom.minidom
# 更换为xml文件所在路径
path = r'E:\picture'
files = os.listdir(path) # 得到文件夹下所有文件名称
for xmlFile in files: # 遍历文件夹
# 根据文件后缀只处理xml文件
if xmlFile[-4:] in ['.xml']:
# if not os.path.isdir(xmlFile): # 判断是否是文件夹,不是文件夹才打开
dom = xml.dom.minidom.parse(os.path.join(path, xmlFile))
root = dom.documentElement
# 找到xml里对应的节点
xmin = root.getElementsByTagName('xmin')
ymin = root.getElementsByTagName('ymin')
xmax = root.getElementsByTagName('xmax')
ymax = root.getElementsByTagName('ymax')
# 修改相应标签的值
for i in range(len(xmin)):
a = xmin[i].firstChild.data
xmin[i].firstChild.data = int(a)+4
for j in range(len(ymin)):
b = ymin[j].firstChild.data
ymin[j].firstChild.data = int(b)+3
for k in range(len(xmax)):
c = xmax[k].firstChild.data
xmax[k].firstChild.data = int(c)+4
for l in range(len(ymax)):
d = ymax[l].firstChild.data
ymax[l].firstChild.data = int(d)+3
# 保存修改到xml文件中
with open(os.path.join(path, xmlFile), 'w') as fh:
dom.writexml(fh)
print('修改标注框坐标成功')
2. 批量修改xml里的类别名称
- 分为以下两种情况,其实跟上方修改坐标差不多的思路,找到对应的节点进行修改
- 1.将文件夹📂下所有的类别替换为同一个名称
- 2.将特定类别的标注框名称进行替换
- 注意这个是直接对xml进行修改了,为避免出错可以提前备份一份
2.1 所有的类别替换
# coding:utf-8
import os
import os.path
import xml.dom.minidom
path = r'E:\picture'
files = os.listdir(path) # 得到文件夹下所有文件名称
for xmlFile in files: # 遍历文件夹
if xmlFile[-4:] in ['.xml']:
dom = xml.dom.minidom.parse(os.path.join(path, xmlFile))
root = dom.documentElement
# 获取xml中name对应节点
name = root.getElementsByTagName('name')
# 修改相应节点的值
for i in range(len(name)):
name[i].firstChild.data = 'c'
# 保存修改到xml文件中
with open(os.path.join(path, xmlFile), 'w',encoding='UTF-8') as fh:
dom.writexml(fh,encoding='UTF-8')
print('全部类别名称修改完成')
2.2 指定类别的替换
# coding:utf-8
import os
import os.path
import xml.dom.minidom
path = r'E:\picture'
files = os.listdir(path) # 得到文件夹下所有文件名称
for xmlFile in files: # 遍历文件夹
if xmlFile[-4:] in ['.xml']:
dom = xml.dom.minidom.parse(os.path.join(path, xmlFile))
root = dom.documentElement
# 获取xml中name对应节点
name = root.getElementsByTagName('name')
# 将标签中类别为a的更换为b
for i in range(len(name)):
if name[i].firstChild.data =='a':
name[i].firstChild.data = 'b'
# 保存修改到xml文件中
with open(os.path.join(path, xmlFile), 'w',encoding='UTF-8') as fh:
dom.writexml(fh,encoding='UTF-8')
print('指定类别名称替换完成')
3. 子节点的删除
- 利用以上文件对xml进行修改后发现有一些图片用labelImg打开没有标注框,检查后发现没有框的都是path路径中有中文的,当时就想着把path这一节点删除,
import xml.etree.ElementTree as ET
import os
path1 = "E:/picture/" # xml文件存放路径
save_path = "E:/picture/123/" # 修改后的xml文件存放路径
# imgpath = "F:\\ai_models\\REALCP-position-20201124\\pic\\" # 新的照片path路径
files = os.listdir(path1) # 读取路径下所有文件名
for xmlFile in files:
if xmlFile.endswith('.xml'):
tree = ET.ElementTree(file=path1 + xmlFile) # 打开xml文件,送到tree解析
root = tree.getroot() # 得到文档元素对象
# root[0].text = 'pic' # root[0].text是annotation下第一个子节点中内容,直接赋值替换文本内容
# root[1].text = xmlFile
# root[1].text = root[1].text.replace('xml','jpg') #修改根节点下的内容
# # root[1].text = root[1].text.split('.')[0] #根据需求决定要不要文件名后缀
# root[2].text = imgpath + xmlFile
for path in root.findall('path'):
# name = object.find('name').text # 获取每一个object节点下name节点的内容
# if name == 'plate':
# object.find('name').text = str('pb') #修改指定标签的内容
# else:
root.remove(path) # 删除除了name属性值为'plate'之外object节点的所有object节点
tree.write(save_path + xmlFile) # 替换后的内容保存在内存中需要将其写出
print('子节点删除完成')
- path子节点确实被删除了,但是folder节点乱码了,不太可取

4. 批量删除属性列表
- 感觉删除path子节点也不太好,再看了看发现原来修改完后编译头发生了变化,原本是这样子的

- 用了上方的替换代码后变成了这样,当时保存时还没加
encoding='UTF-8'
- 百度后发现应该要加
encoding='UTF-8'(所以上方代码保存时加了dom.writexml(fh,encoding='UTF-8')),但是加了之后annotation没有另起一行,并且labelImg还是不显示框
- 所以最后就选择把第一行的属性列表删掉,labelImg显示框了,删除后如下图

- 删除属性列表代码如下,主要也是查找替换
# -*- coding:utf-8 -*-
import os
xmldir = r'E:\picture'
savedir = r'E:\picture\123'
xmllist = os.listdir(xmldir)
for xml in xmllist:
if '.xml' in xml:
fo = open(savedir + '/' + '{}'.format(xml), 'w', encoding='utf-8')
print('{}'.format(xml))
fi = open(xmldir + '/' + '{}'.format(xml), 'r', encoding='utf-8')
content = fi.readlines()
for line in content:
# line = line.replace('a', 'b') # 例:将a替换为b
line = line.replace('<?xml version="1.0" encoding="UTF-8"?>', '')
# line = line.replace('<folder>测试图片</folder>', '<folder>车辆图片</folder>')
# line = line.replace('<name>class1</name>', '<name>class2</name>')
fo.write(line)
fo.close()
print('替换成功')
5. 根据xml切割图片
- 方法一 根据xml切出图片
# 导入模块
import cv2
import xml.etree.ElementTree as ET
import os
from pathlib import Path
import numpy as np
import random
# 原图片、标签文件、裁剪图片路径
img_path = r'E:\picture'
xml_path = r'E:\picture'
obj_img_path = r'E:\picture\cut'
# 声明一个空字典用于储存裁剪图片的类别及其数量
Numpic = {}
# 把原图片裁剪后,按类别新建文件夹保存,并在该类别下按顺序编号
for img_file in os.listdir(img_path):
if img_file[-4:] in ['.png', '.jpg']: # 判断文件是否为图片格式
img_filename = os.path.join(img_path, img_file) # 将图片路径与图片名进行拼接
# print(img_filename)
img_cv = cv2.imread(img_filename) # 读取图片
img_name = (os.path.splitext(img_file)[0]) # 分割出图片名,如“000.png” 图片名为“000”
xml_name = xml_path + '\\' + '%s.xml' % img_name # 利用标签路径、图片名、xml后缀拼接出完整的标签路径名
if os.path.exists(xml_name): # 判断与图片同名的标签是否存在,因为图片不一定每张都打标
root = ET.parse(xml_name).getroot() # 利用ET读取xml文件
for obj in root.iter('object'): # 遍历所有目标框
name = obj.find('name').text # 获取目标框名称,即label名
xmlbox = obj.find('bndbox') # 找到框目标
x0 = xmlbox.find('xmin').text # 将框目标的四个顶点坐标取出
y0 = xmlbox.find('ymin').text
x1 = xmlbox.find('xmax').text
y1 = xmlbox.find('ymax').text
# 利用数组的切片
obj_img = img_cv[int(y0):int(y1), int(x0):int(x1)] # cv2裁剪出目标框中的图片
Numpic.setdefault(name, 0) # 判断字典中有无当前name对应的类别,无则新建
Numpic[name] += 1 # 当前类别对应数量 + 1
my_file = Path(obj_img_path + '\\' + name) # 判断当前name对应的类别有无文件夹
if 1 - my_file.is_dir(): # 无则新建
os.mkdir(obj_img_path + '\\' + str(name))
# cv2.imwrite(obj_img_path + '\\' + name + '\\' + '%04d' % (Numpic[name]) + '.jpg', obj_img) # 保存裁剪图片,图片命名4位,不足补0
#opencv编码问题遇到路径中有中文时不要使用imwrite,使用imencode`
cv2.imencode('.jpg', obj_img)[1].tofile(obj_img_path + '\\' + name + '\\' +img_name+'_'+ '%03d' % (Numpic[name]) + '.jpg')
# print(obj_img_path + '\\' + name + '\\' +img_name+'_'+ '%03d' % (Numpic[name]) + '.jpg')
# print('-'*50)
- 方法二 可同时切出图片和xml
# 切割图片 按名称切割放入文件夹
import xml.etree.ElementTree as ET
import cv2
import os
from lxml import etree
from tqdm import tqdm
class GEN_Annotations:
def __init__(self, filename):
self.root = etree.Element("annotation")
child1 = etree.SubElement(self.root, "folder")
child1.text = "a"
child2 = etree.SubElement(self.root, "filename")
child2.text = filename
child3 = etree.SubElement(self.root, "source")
child4 = etree.SubElement(child3, "annotation")
child4.text = "c"
child5 = etree.SubElement(child3, "database")
child5.text = "Unknown"
# child6 = etree.SubElement(child3, "image")
# child6.text = "flickr"
# child7 = etree.SubElement(child3, "flickrid")
# child7.text = "35435"
def set_size(self, witdh, height, channel):
size = etree.SubElement(self.root, "size")
widthn = etree.SubElement(size, "width")
widthn.text = str(witdh)
heightn = etree.SubElement(size, "height")
heightn.text = str(height)
channeln = etree.SubElement(size, "depth")
channeln.text = str(channel)
def savefile(self, filename):
tree = etree.ElementTree(self.root)
tree.write(filename, pretty_print=True, xml_declaration=False, encoding='utf-8')
def add_pic_attr(self, label, xmin, ymin, xmax, ymax):
object = etree.SubElement(self.root, "object")
namen = etree.SubElement(object, "name")
namen.text = label
bndbox = etree.SubElement(object, "bndbox")
xminn = etree.SubElement(bndbox, "xmin")
xminn.text = str(xmin)
yminn = etree.SubElement(bndbox, "ymin")
yminn.text = str(ymin)
xmaxn = etree.SubElement(bndbox, "xmax")
xmaxn.text = str(xmax)
ymaxn = etree.SubElement(bndbox, "ymax")
ymaxn.text = str(ymax)
def get_ext(w,h,b):
# up down 100 left right 50
b1 = []
# print(b[0],b[1],b[2],b[3])
# b1.append(max(0, b[0] - 300))
# b1.append(max(0, b[1] - 300 ))
# b1.append(min(w, b[2] + 300))
# b1.append(min(h, b[3] + 300))
b1.append(max(0, b[0]))
b1.append(max(0, b[1]))
b1.append(min(w, b[2]))
b1.append(min(h, b[3]))
return b1
# 原图
path_img = r'E:\picture'
path_xml = r'E:\picture'
# 切割后
out_path_img =r'E:\picture\jpg'
out_path_xml = r'E:\picture\ann'
def makedir(*new_dir):
for dir in new_dir:
if not os.path.exists(dir):
os.makedirs(dir)
makedir(out_path_img, out_path_xml)
img_dir = os.listdir(path_img)
xml_dir = os.listdir(path_xml)
idx = 0
# clss此处填写类别名称
clss = ['a','b','c']
for i in tqdm(range(len(img_dir))):
if img_dir[i][-4:] in ['.png', '.jpg']: # 判断文件是否为图片格式
print(i)
xml_path = os.path.join(path_xml, img_dir[i][:-3]+'xml')
jpg_file = os.path.join(path_img, img_dir[i])
if not os.path.exists(xml_path):
continue
im = cv2.imread(jpg_file)
# print('-'*100)
# print(jpg_file)
tree = ET.parse(xml_path) # 解析xml
root = tree.getroot()
size = root.find('size') # 图片尺寸<Element 'size' at 0x000002A6DF8DB778>
w = int(size.find('width').text) # 图片的宽
# print(w)
h = int(size.find('height').text) # 图片的高
# print(h)
# is_show = False
is_show = True
for obj in root.iter('object'):
cls = obj.find('name').text
if cls in clss:
# is_show = False
xmlbox = obj.find('bndbox')
b = [float(xmlbox.find('xmin').text), float(xmlbox.find('ymin').text), float(xmlbox.find('xmax').text),
float(xmlbox.find('ymax').text)]
b1 = get_ext(w, h, b)
xmin, ymin, xmax, ymax = int(b[0]-b1[0] ), int(b[1]-b1[1]), int(b[2]-b1[0]), int(b[3] - b1[1])
# print(xmin,ymin,xmax,ymax)
new_img = im[int(b1[1]):int(b1[3]), int(b1[0]):int(b1[2])].copy()
# new_img = im[ymin:ymax, xmin:xmax].copy()
idx += 1
new_img_path = os.path.join(out_path_img, img_dir[i][:-4]+'_'+str(idx).zfill(6)+".jpg")
new_xml_path = os.path.join(out_path_xml, img_dir[i][:-4]+'_'+str(idx).zfill(6)+".xml")
# print(new_img_path)
# 写图片
# print('图片',new_img_path)
#cv2.imwrite(new_img_path,new_img)
cv2.imencode('.jpg', new_img)[1].tofile(new_img_path)
print(new_img_path)
# 写标签
anno = GEN_Annotations(new_img_path)
nh, nw, nc = new_img.shape
anno.set_size(nw, nh, nc)
# anno.add_pic_attr(cls, xmin, ymin, xmax, ymax)
anno.add_pic_attr(cls, int(b1[0]), int(b1[1]), int(b1[2]), int(b1[3]))
# print('xml', new_xml_path)
anno.savefile(new_xml_path)
# cv2.waitKey(0)
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐




所有评论(0)