批量修改xml标注框坐标,类别名称,删除子节点,删除属性列表,根据xml切割图片

本文档详细介绍了如何批量修改XML图像标注文件中的坐标、类别名称,删除子节点,删除属性列表以及根据XML切割图片。提供了具体的Python代码实现,包括对XML中的xmin, ymin, xmax, ymax坐标进行微调,批量替换所有或指定类别的名称,删除path子节点,以及删除XML文件的第一行属性列表。此外,还展示了两种根据XML切割图片的方法,一种仅切出图片,另一种同时切出图片和XML。
摘要由CSDN通过智能技术生成

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)
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值