修改 xml 格式的训练数据标注集(label / annotation)的 python 脚本

在R-FCN训练中遇到由于XML标注文件起点坐标为(0,0)导致的numpy运行时错误。通过fool-zz的博客找到问题根源后,编写了一个利用xml.etree模块的Python脚本,批量修正XML标注文件,避免LOSS出现Nan问题,提高标注修改效率。" 133606957,20015923,Oracle数据库内存优化:SGA与PGA的配置策略,"['Oracle数据库', '数据库优化', '内存管理']

最近遇到了 R-FCN 训练时,numpy 会报错的情况,错误如下:

RuntimeWarning: invalid value encountered in log targets_dw = np.log(gt_widths / ex_widths)

搞得 LOSS 一直是 Nan,很是头疼。
几经查询,看到了fool-zz 的博客,将问题确定在了标注上。查阅自己的 xml 标注文件( labelImage 生成),发现确实有的框的起点在图片的左上角,也就是 (0,0),引发了上述错误。

手动修改 xml 实在是太辛苦了,所以写了个 python 脚本,利用 xml.etree 进行修改,方便快捷。并且日后修改标注(xml格式)时简单修改就可以拿来使用,非常方便。

最后附上代码:

import os
import xml.etree.ElementTree as ET

path = './Annotations_needRectify'

for xml in os.listdir<
<think> 我们有一个任务:使用Python脚本修改.xml标注文件中的缺陷类型(即对象类别名称)。这些.xml文件可能是由LabelImg等工具生成的标注文件。 根据引用内容,我们可以看到一些相关的操作,比如修改xml文件中的内容(如path标签)或生成自定义的xml文件。 在引用[3]中,示例代码展示了如何通过ElementTree解析xml文件,然后查找并修改特定元素(如'path'元素),最后写回文件。 具体到我们的需求:修改缺陷类型(即对象类别名称)。假设在xml文件中,每个对象(object)都有一个类别名称(name)元素。 例如,一个典型的标注文件可能包含如下结构: <annotation> <object> <name>defect_type1</name> ... </object> ... 可能还有多个object </annotation> 我们需要做的是: 1. 遍历所有xml文件(或指定的xml文件)。 2. 解析每个xml文件。 3. 找到所有的object元素,然后修改其中的name元素的内容(将旧的缺陷类型名改为新的)。 步骤: 1. 导入ElementTree模块(通常使用xml.etree.ElementTree)。 2. 遍历每个xml文件(这里假设我们有一个文件列表,可以通过glob模块获取)。 3. 解析xml文件。 4. 获取根节点。 5. 使用findall或iter查找所有的object元素。 6. 对于每个object元素,找到其下的name元素,并修改其文本(text)为新的缺陷类型。 7. 将修改后的树写回到原文件(或新文件)。 注意:在写回文件时,要注意保留原文件格式。ElementTree在写回时可能不会保留原格式(如缩进),因此可能需要使用minidom进行美化。 但是,引用[3]中使用了ElementTree的write方法,没有进行美化。如果需要美化,可以参考以下步骤: - 使用ElementTree解析后,用minidom解析字符串并美化。 - 或者,使用lxml库(它可以保留格式,但需要安装)。 由于用户没有特别要求保留格式,我们可以先使用ElementTree,如果格式混乱再考虑用minidom美化。 另外,我们需要知道旧缺陷类型名和新缺陷类型名的映射关系。这里假设我们有一个映射字典,例如: mapping = { "old_defect1": "new_defect1", "old_defect2": "new_defect2", ... } 这样,在修改时,我们检查name元素的文本是否在映射字典的键中,如果在,就替换为对应的新名称。 代码结构: 1. 定义映射字典。 2. 获取所有xml文件的路径(可以使用glob.glob匹配)。 3. 遍历每个xml文件。 4. 解析xml文件。 5. 获取根节点。 6. 遍历所有的object元素(注意:xml中可能有多个object)。 7. 对于每个object,找到其下的name元素(使用find方法)。 8. 获取当前name的文本,检查是否在映射字典中,如果在,则替换。 9. 写回文件(覆盖原文件或另存为新文件)。 注意:如果原文件需要备份,建议先备份。 下面是一个示例代码: ```python import os import xml.etree.ElementTree as ET # 定义旧缺陷类型到新缺陷类型的映射 mapping = { "crack": "crack_v1", # 将裂缝改为crack_v1 "scratch": "scratch_v1" # 将划痕改为scratch_v1 # 根据实际情况添加更多 } # 包含xml文件文件夹路径 xml_dir = "/path/to/xml/files" # 遍历文件夹中的所有xml文件 for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue full_path = os.path.join(xml_dir, xml_file) # 解析xml文件 tree = ET.parse(full_path) root = tree.getroot() # 查找所有的object元素 for obj in root.findall('object'): # 在object元素下查找name元素 name_elem = obj.find('name') if name_elem is not None: current_name = name_elem.text # 如果当前名称在映射字典中,则进行替换 if current_name in mapping: new_name = mapping[current_name] name_elem.text = new_name # 写回原文件(覆盖) tree.write(full_path, encoding='utf-8') # 如果需要美化,可以转换为minidom重新写,但这样会丢失注释(如果有的话) # 下面是用minidom美化的步骤(可选) # from xml.dom import minidom # rough_string = ET.tostring(root, 'utf-8') # reparsed = minidom.parseString(rough_string) # with open(full_path, 'w', encoding='utf-8') as f: # reparsed.writexml(f, indent='\t', addindent='\t', newl='\n', encoding='utf-8') print("修改完成!") ``` 注意:如果xml文件中有注释,上述代码使用minidom可能会丢失注释。如果没有注释,并且希望格式美观,可以使用注释掉的那段代码代替tree.write。 另外,如果xml文件结构不同(比如有命名空间),那么查找元素的方式需要调整。但LabelImg生成的xml通常没有命名空间。 根据引用[3]中的方法,我们使用find和findall来查找元素,这些方法在ElementTree中用于在当前元素的直接子元素中查找。 如果object元素不在根元素的直接子元素下(比如在多层嵌套中),那么可能需要使用更通用的方法,比如使用root.iter('object')来递归查找所有object元素。但LabelImg生成的xml中,object是annotation的直接子元素,所以使用root.findall('object')即可。 如果用户有特殊结构,需要调整。 因此,上述代码应该可以满足需求。 如果用户需要修改多个文件夹,或者需要更复杂的映射,可以扩展这个脚本。 另外,引用[2]提到了使用lxml库,它更强大,但需要安装。如果用户已经安装了lxml,也可以使用lxml.etree,它在处理大型文件时可能更快,并且可以更好地保留格式(如缩进)。不过,标准库的ElementTree通常足够。 如果用户需要保留原格式(缩进和换行),并且不想用minidom(因为minidom会改变格式),可以考虑使用lxml。但是,由于标准库中已经包含ElementTree,我们优先使用它。 如果用户对格式要求很高,且原文件格式很重要,可以考虑使用lxml。这里不展开。 因此,我们提供上述代码作为解决方案。 如果用户需要,也可以将修改后的文件写入新目录,而不是覆盖原文件。这可以通过改变输出路径实现。 例如,创建一个新的目录“modified_xml”,然后将修改后的xml文件写入这个目录。 代码修改如下(可选): ```python import os import xml.etree.ElementTree as ET mapping = { "crack": "crack_v1", "scratch": "scratch_v1" } xml_dir = "/path/to/xml/files" output_dir = "/path/to/modified_xml" os.makedirs(output_dir, exist_ok=True) # 确保输出目录存在 for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue full_path = os.path.join(xml_dir, xml_file) tree = ET.parse(full_path) root = tree.getroot() for obj in root.findall('object'): name_elem = obj.find('name') if name_elem is not None and name_elem.text in mapping: name_elem.text = mapping[name_elem.text] # 写入输出目录 output_path = os.path.join(output_dir, xml_file) tree.write(output_path, encoding='utf-8') print("修改完成,文件保存在:", output_dir) ``` 这样就不会覆盖原文件。 综上所述,我们提供了两种方式:覆盖原文件和另存为新文件。 请用户根据实际需求选择。 注意:在运行脚本前,请备份原文件,以防万一。 </think> ### Python脚本修改XML标注文件中缺陷类型的方法 要使用Python脚本修改XML标注文件中的缺陷类型(对象类别名称),可以通过以下步骤实现: #### 核心方法(使用xml.etree.ElementTree) ```python import xml.etree.ElementTree as ET import os def modify_defect_type(xml_path, old_name, new_name): """修改XML文件中特定缺陷类型的标签名称""" tree = ET.parse(xml_path) root = tree.getroot() # 遍历所有object节点 for obj in root.findall('object'): name_elem = obj.find('name') if name_elem is not None and name_elem.text == old_name: name_elem.text = new_name # 替换缺陷类型名称 # 保持原格式写入(使用minidom美化输出) raw_str = ET.tostring(root, encoding='utf-8') from xml.dom import minidom parsed = minidom.parseString(raw_str) with open(xml_path, 'w', encoding='utf-8') as f: parsed.writexml(f, indent='\t', addindent='\t', newl='\n', encoding='utf-8') # 批量处理示例 xml_dir = "/path/to/xml_files" old_defect = "crack" # 原缺陷类型名称 new_defect = "fracture" # 新缺陷类型名称 for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): modify_defect_type(os.path.join(xml_dir, xml_file), old_defect, new_defect) ``` #### 关键步骤说明: 1. **XML解析**: - 使用`ET.parse()`加载XML文件 - 获取根节点`root = tree.getroot()` 2. **定位缺陷节点**: - 通过`root.findall('object')`获取所有缺陷对象 - 使用`obj.find('name')`定位类别名称节点 3. **名称替换**: - 检查当前名称是否匹配目标缺陷类型:`if name_elem.text == old_name` - 修改文本内容:`name_elem.text = new_name` 4. **格式保持**: - 使用`minidom.parseString`保持原XML缩进格式 - 通过`writexml()`参数保留文件结构 #### 高级应用场景 1. **多类别批量替换**(使用映射字典): ```python type_mapping = { "scratch": "surface_damage", "rust": "corrosion", "dent": "deformation" } for obj in root.findall('object'): name_elem = obj.find('name') if name_elem.text in type_mapping: name_elem.text = type_mapping[name_elem.text] ``` 2. **条件过滤修改**(仅修改特定尺寸的缺陷): ```python for obj in root.findall('object'): name_elem = obj.find('name') bndbox = obj.find('bndbox') # 计算缺陷面积 xmin = int(bndbox.find('xmin').text) ymin = int(bndbox.find('ymin').text) area = (int(bndbox.find('xmax').text) - xmin) * (int(bndbox.find('ymax').text) - ymin) # 小面积裂纹重分类 if name_elem.text == "crack" and area < 100: name_elem.text = "micro_crack" ``` #### 注意事项 1. **文件备份**: ```python import shutil shutil.copy(xml_path, xml_path + ".bak") # 修改前创建备份 ``` 2. **异常处理**: ```python try: tree = ET.parse(xml_path) except ET.ParseError: print(f"损坏文件跳过: {xml_path}") ``` 3. **命名空间处理**(若存在): ```python ET.register_namespace('', "http://example.com/ns") # 注册命名空间 ``` [^1]: 实现基于ElementTree的XML解析和修改方法 [^2]: 采用minidom保持原文件格式 [^3]: 通过条件过滤实现智能重分类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值