合并使用labelimg标注的同一张图片的两个不同xml标签

    技术2022-07-11  98

    目录

    待解决问题目标效果解决思路解决方案先了解一些 xml 基本操作代码奉上单张图片批量操作

    待解决问题

    对于某个文件夹下的所有图片,使用labelimg进行标注,获得voc类型的xml格式的标签。由于在进行标注工作时,将不同的类别分开标注了,得到标签文件夹有两个,第一个文件夹存储了类别 a 和类别 b 的 labels ,第二个文件夹存储了类别 c, d, e… 的 labels 。现在想要将相同图片的标签进行,即原本一张图片可能有两个对应的 xml,现在要使得一张图片只有一个对应的 xml。

    目标效果

    原图: 标签1 标签2 标签合并

    解决思路

    通过分析 xml 文件,得知关键的标签信息存在于 xml 中的 object节点中,那么只要将相同图片的两个 xml 文件中的object进行拼接即可。

    解决方案

    例如:

    xml1:

    <annotation> <folder>images</folder> <filename>1_191.jpg</filename> <path>/home/shan/Desktop/VOC/images/1_191.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>no</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>240</xmin> <ymin>37</ymin> <xmax>290</xmax> <ymax>79</ymax> </bndbox> </object> <object> <name>no</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>401</xmin> <ymin>23</ymin> <xmax>460</xmax> <ymax>65</ymax> </bndbox> </object> </annotation>

    xml2:

    <annotation> <folder>xu</folder> <filename>1_191.jpg</filename> <path>/home/aibc/Desktop/xu/1_191.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>excavator arm</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>938</xmin> <ymin>38</ymin> <xmax>1063</xmax> <ymax>116</ymax> </bndbox> </object> <object> <name>person</name> <pose>Unspecified</pose> <truncated>1</truncated> <difficult>0</difficult> <bndbox> <xmin>1037</xmin> <ymin>1</ymin> <xmax>1047</xmax> <ymax>28</ymax> </bndbox> </object> </annotation>

    xml1 和 xml2 都是 图片1_191.jpg 的标签,我们现在将它们合并。

    先了解一些 xml 基本操作

    读取

    读取文档:tree = ET.parse() 获得根节点:root = tree.getroot() 获得所有子节点:list() 查找子节点,注意这里不会递归查找所有子节点:root.findall('object') 查找子节点,递归查找所有子节点:root.iter() 查看节点名称:root.tag

    写入

    创建节点:root = ET.Element) 创建文档:tree = ET.ElementTree() 设置文本值:element.text 设置属性:element.set() 添加节点:root.append() 写入文档:tree.write()

    代码奉上

    单张图片

    from xml.etree.ElementTree import ElementTree, Element, parse import xml.etree.ElementTree as ET # 缩进对齐 def __indent(elem, level=0): i = "\n" + level*"\t" if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + "\t" if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: __indent(elem, level+1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i # 读取xml1文件 # 为了方便演示,我将两个 1_191.xml 分别手动重命名为 1_191_1.xml 和 1_191_2.xml tree_1 = parse('1_191_1.xml') root_1 = tree_1.getroot() # annotation new_1 = tree_1 # 读取xml2文件 tree_2 = parse('1_191_2.xml') root_2 = tree_2.getroot() # annotation # 找到所有object节点 object = (tree_2.findall('object')) length = len(object) # 将tree_2中的所有object节点加入到root_1中 for i in range(length): root_1.append(object[i]) __indent(root_1) new_1.write('./1_191.xml')

    得到的输出结果为:

    <annotation> <folder>images</folder> <filename>1_191.jpg</filename> <path>/home/shan/Desktop/VOC/images/1_191.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>no</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>240</xmin> <ymin>37</ymin> <xmax>290</xmax> <ymax>79</ymax> </bndbox> </object> <object> <name>no</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>401</xmin> <ymin>23</ymin> <xmax>460</xmax> <ymax>65</ymax> </bndbox> </object> <object> <name>excavator arm</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>938</xmin> <ymin>38</ymin> <xmax>1063</xmax> <ymax>116</ymax> </bndbox> </object> <object> <name>person</name> <pose>Unspecified</pose> <truncated>1</truncated> <difficult>0</difficult> <bndbox> <xmin>1037</xmin> <ymin>1</ymin> <xmax>1047</xmax> <ymax>28</ymax> </bndbox> </object> </annotation>

    可以看到,问题解决_

    批量操作

    from xml.etree.ElementTree import ElementTree, Element, parse import xml.etree.ElementTree as ET import os import shutil hole_path = './Annotations' arm_path = './Anno' out_path = './Fusing' # 格式化 def __indent(elem, level=0): i = "\n" + level*"\t" if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + "\t" if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: __indent(elem, level+1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i for hole_xml in os.listdir(hole_path): # 将同名xml合并 if os.path.exists(os.path.join(arm_path,hole_xml)): print('fusing',hole_xml) tree_hole = parse(os.path.join(hole_path,hole_xml)) root_hole = tree_hole.getroot() # annotation new_hole = tree_hole tree_arm = parse(os.path.join(arm_path,hole_xml)) root_arm = tree_arm.getroot() # annotation object = (tree_arm.findall('object')) for i in range(len(object)): root_hole.append(object[i]) __indent(root_hole) new_hole.write(os.path.join(out_path,hole_xml)) # 不同名xml复制 else: print('copying',hole_xml) shutil.copy(os.path.join(hole_path,hole_xml), out_path) # 将不同名xml复制 for arm_xml in os.listdir(arm_path): if not os.path.exists(os.path.join(out_path,arm_xml)): print('copying') shutil.copy(os.path.join(arm_path, arm_xml), out_path)

    参考链接:

    Python中XML的读写总结

    Processed: 0.011, SQL: 9