python批量合并两个voc标签格式的数据集

本文介绍了如何使用Python编程,通过ElementTree库合并两个VOC格式的人脸检测和打哈欠标签数据集,提高项目效率。主要步骤包括读取XML、合并同名文件并复制不同名文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

做深度学习项目时,不同的项目需要不同标签的数据集。譬如有两个数据集的图片数据是相同的,其中一个数据集标签是人脸(用于人脸检测),另外一个数据集标签是张嘴、闭嘴(用于检测是否打哈欠),这时,由于某些原因,我们想该数据集训练完后,既能检测人脸,又能检测打哈欠。一种就是笨办法,在打哈欠数据集中把人脸标签标注进去;另一方法就是本文将要介绍的,通过python脚本程序,合并两个数据集的xml文件。


提示:以下是本篇文章正文内容,下面案例可供参考

一、完整代码

1.引入库

from xml.etree.ElementTree import ElementTree, Element, parse
import xml.etree.ElementTree as ET
import os
import shutil

1.添加xml文件路径

face_path = r’F:\face_data\label\face_xml’ #人脸voc标签路径
yawn_path = r’F:\yawn_data\label\yawn_xml’ #打哈欠voc标签路径
contact_out_path = r’F:\A_s\rgyt_data\out_xml’ #合并后输出voc标签路径

二、程序主干

1. 格式化

代码如下(示例):

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

2.#将同名xml合并

代码如下(示例):

for hole_xml in os.listdir(face_path ):
    # 将同名xml合并
    if os.path.exists(os.path.join(yawn_path ,hole_xml)):
        print('fusing',hole_xml)
        tree_hole = parse(os.path.join(face_path ,hole_xml))
        root_hole = tree_hole.getroot()  # annotation

        new_hole = tree_hole

        tree_arm = parse(os.path.join(yawn_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(face_path ,hole_xml), out_path)

2.将不同名xml复制

for arm_xml in os.listdir(yawn_path ):
    if not os.path.exists(os.path.join(contact_out_path ,arm_xml)):
        print('copying')
        shutil.copy(os.path.join(yawn_path , arm_xml), contact_out_path )

总结

以上就是今天要讲的内容,超级好用,效率超级高,学好python,能提高工作效率!觉得还不错的感谢关注收藏,后续还会继续分享好用的数据处理脚本。

<think>我们之前已经通过数据增强得到了2000个json文件(原始500个,每个增强3个,共2000个)。现在需要将这些json文件转换为单通道PNG图像(分割掩码)和YOLO格式的txt标签文件。 步骤概述: 1. 将每个json文件中的多边形标注转换为单通道PNG图像(每个类别一个唯一的整数值,背景为0)。 2. 同时生成YOLO格式的txt标签文件(每个图像对应一个txt,包含类别和归一化的多边形点)。 但是注意:YOLOv8的分割任务需要两种格式数据: - 图像:原始RGB图像(我们已经在增强时保存了) - 标签:一种是单通道的PNG分割掩码(用于训练),另一种是YOLO格式的txt文件(包含类别和归一化的多边形坐标,用于验证和评估) 然而,根据YOLOv8官方文档,训练分割模型需要: - 图像放在 `images/train` 等目录 - 对应的标签有两种形式可选(但通常同时提供): a) 单通道PNG掩码(每个像素值代表类别id) b) 与图像同名的txt文件,每行格式:`class_id x1 y1 x2 y2 ... xn yn`(归一化的多边形点) 因此,我们需要做两件事: A. 从json生成单通道PNG掩码(单张图片上每个类别的区域用不同的整数像素值表示) B. 从json生成YOLO格式的txt文件(包含归一化的多边形点) 由于用户要求生成单通道PNG和txt标签文件,我们将分别处理。 方法: 我们可以使用labelme提供的工具将json转换为单通道PNG,同时自己编写脚本生成YOLO格式的txt。 但为了统一,我们可以: 1. 使用labelme的`labelme2voc.py`脚本生成VOC格式的语义分割数据集(包括单通道PNG),然后 2. 将VOC格式的标注转换为YOLO格式的txt(多边形点)或者直接通过json生成YOLO格式的txt。 然而,用户要求直接生成txt标签文件(YOLO格式),所以我们可以分两步走: 方案一(推荐): 步骤1:将json批量转换为单通道PNG(使用labelme自带的转换工具) 步骤2:从json中提取多边形信息,直接生成YOLO格式的txt文件(归一化坐标) 具体操作: 步骤1:生成单通道PNG掩码 使用labelme提供的命令行工具: ```bash labelme_json_to_dataset [options] json_file1 json_file2 ... --output dir ``` 但是,这个命令一次只能处理一个文件,我们需要批量处理。因此,可以编写一个脚本批量调用。 或者,使用以下Python脚本批量转换(使用labelme的API): ```python import glob import os import labelme def convert_to_png(json_dir, output_dir): json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: # 使用labelme提供的转换函数 label_file = labelme.LabelFile(filename=json_file) img = labelme.utils.img_data_to_arr(label_file.imageData) # 创建保存目录 base_name = os.path.splitext(os.path.basename(json_file))[0] save_dir = os.path.join(output_dir, base_name) os.makedirs(save_dir, exist_ok=True) # 保存原始图像(可选,因为我们已经有增强后的图像了,但这里我们只需要标签) # 我们真正需要的是label.png(单通道掩码) # 调用labelme的转换函数生成掩码 data = label_file.load(img, None) # 生成标签图像 lbl, _ = labelme.utils.shapes_to_label( img_shape=img.shape, shapes=data['shapes'], label_name_to_value={} # 自动生成映射 ) # 保存为PNG labelme.utils.lblsave(os.path.join(save_dir, 'label.png'), lbl) # 注意:这样每个json文件会生成一个子目录,里面包含label.png ``` 但是,这样生成的掩码是单通道的PNG,每个子目录一个,我们需要将其整理到一个目录中,且重命名。 步骤2:生成YOLO格式的txt文件 从json文件中读取每个多边形的类别和点,然后归一化(除以图像宽高),写入txt。 ```python import json import os def convert_to_yolo_txt(json_dir, output_txt_dir, class_list): # class_list: 类别列表,用于映射类别名到id os.makedirs(output_txt_dir, exist_ok=True) json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: with open(json_file, 'r') as f: data = json.load(f) image_width = data['imageWidth'] image_height = data['imageHeight'] base_name = os.path.splitext(os.path.basename(json_file))[0] txt_file = os.path.join(output_txt_dir, base_name + '.txt') with open(txt_file, 'w') as f_txt: for shape in data['shapes']: if shape['shape_type'] != 'polygon': continue label = shape['label'] class_id = class_list.index(label) points = shape['points'] # 归一化 normalized_points = [] for point in points: x, y = point normalized_x = x / image_width normalized_y = y / image_height normalized_points.append(normalized_x) normalized_points.append(normalized_y) # 写入一行:class_id x1 y1 x2 y2 ... line = [str(class_id)] + [str(p) for p in normalized_points] f_txt.write(' '.join(line) + '\n') ``` 但是,上述方法需要提前知道class_list。 然而,我们注意到:YOLO格式的txt要求每个对象一行,每行是: class_id x1 y1 x2 y2 ... xn yn 步骤1和步骤2可以合并成一个脚本,同时生成单通道PNG和YOLO的txt?但是,生成单通道PNG需要调用labelme的转换函数,而生成txt则不需要。 考虑到效率,我们可以分别进行。 但是,我们也可以使用另一种方法:使用labelme2voc生成整个VOC数据集,然后使用voc2yolo转换(但这样可能更复杂)。 为了简化,我建议分别生成: A. 使用一个循环处理每个json,生成单通道PNG(保存到指定目录,按原名称命名,如:IMG_001_aug0.png) B. 使用另一个循环生成txt文件。 优化后的步骤: 步骤1:生成单通道PNG掩码(批量) 修改上面的转换脚本,将每个json转换得到的label.png直接重命名为与json同名的png文件,放在一个统一的目录中。 步骤2:生成txt标签(同上) 具体实现: 步骤1:批量生成单通道PNG掩码(不使用子目录) ```python import glob import os import json import numpy as np import labelme from labelme import utils def json_to_png(json_dir, png_output_dir): os.makedirs(png_output_dir, exist_ok=True) json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: # 读取json文件 label_file = labelme.LabelFile(filename=json_file) # 获取图像数据(如果原图不在,可以使用我们之前保存的增强图像?但是json中已经包含了图像数据?) # 注意:在增强时我们保存了图像,但json中可能包含base64编码的图像数据,所以可以直接用 img = labelme.utils.img_data_to_arr(label_file.imageData) # 获取标注信息 shapes = label_file.shapes # 创建类别名称到值的映射(自动生成) label_name_to_value = {'_background_': 0} for shape in shapes: label_name = shape['label'] if label_name not in label_name_to_value: label_name_to_value[label_name] = len(label_name_to_value) # 生成标签图像 lbl, _ = utils.shapes_to_label( img_shape=img.shape, shapes=shapes, label_name_to_value=label_name_to_value ) # 保存为PNG png_filename = os.path.basename(json_file).replace('.json', '.png') utils.lblsave(os.path.join(png_output_dir, png_filename), lbl) ``` 注意:这里生成的PNG是单通道的,像素值从0开始(0是背景,1,2,...是各个类别)。 步骤2:生成YOLO格式的txt文件(与上面相同) 但是,步骤2中我们需要的类别列表(class_list)可以通过遍历所有json文件来收集所有类别,或者我们提前定义好。 如果类别是固定的,我们可以提前定义。如果不固定,我们可以收集所有json中出现的类别(除了背景)。 假设我们已知类别,或者通过一个函数收集: ```python def get_class_list(json_dir): classes = set() json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: with open(json_file, 'r') as f: data = json.load(f) for shape in data['shapes']: classes.add(shape['label']) # 将集合转换为列表,确保顺序(可以按字母序) return sorted(list(classes)) ``` 然后调用: ```python class_list = get_class_list(json_dir) convert_to_yolo_txt(json_dir, output_txt_dir, class_list) ``` 最后,我们得到: - 单通道PNG掩码:在 `mask_output_dir` 下,共2000个png文件(与json同名) - YOLO格式txt:在 `txt_output_dir` 下,共2000个txt文件(与json同名) 注意:YOLO训练时,需要将原始图像(我们增强后的jpg)和txt文件放在同一个目录(或对应目录)用于训练,而单通道PNG用于分割训练(在YOLOv8中,分割训练需要单通道PNG作为标签)。 根据YOLOv8文档,数据集结构应该是: dataset/ images/ train/ img1.jpg ... val/ labels/ train/ img1.txt ... val/ masks/ train/ img1.png # 单通道的掩码 ... val/ 因此,我们需要将生成的图像(原始增强后的jpg)、txt标签和PNG掩码分别放到对应的目录中。 但是,用户原始增强后的图像已经保存在一个目录(如 `aug_img_dir`),我们现在需要将: - 图像复制到 `dataset/images/train/` - 将txt标签复制到 `dataset/labels/train/` - 将单通道PNG掩码复制到 `dataset/masks/train/` 这样,在训练YOLOv8分割模型时,配置yaml文件: path: dataset train: images/train val: images/val # 如果有验证集 masks: masks/train # 注意:官方文档中可能需要指定分割标签的路径 但是,请注意,YOLOv8的分割训练要求标签是单通道PNG格式且与图像同名(放在不同目录)。同时,它也需要多边形信息(在txt中)用于计算指标?实际上,YOLOv8的分割训练只需要单通道PNG掩码,不需要txt。txt文件是用于目标检测的,而分割任务需要的是掩码。 然而,在YOLOv8的分割中,它同时输出检测框和分割掩码。因此,训练时,它需要两种标签:一种是检测框(YOLO格式txt),另一种是分割掩码(PNG)。但是,根据官方文档,我们只需要提供分割掩码的目录,它会自动读取。检测框信息可以通过分割掩码转换得到(在训练前预处理时)。所以,实际上我们只需要提供单通道PNG掩码即可。 但是,为了确保正确,我们参考官方文档:https://docs.ultralytics.com/datasets/segment/ 文档中说明: 分割数据集使用与检测数据集相同的格式,但额外需要一个与图像同名的PNG格式的分割掩码文件(放在`labels`目录旁边的`masks`目录中)。 因此,数据集结构: dataset/ images/ train/ image1.jpg val/ masks/ train/ image1.png val/ 且,每个掩码图像是单通道的PNG,像素值0表示背景,1表示第一个类别,2表示第二个类别,以此类推。 同时,文档中没有提到需要txt文件。所以,我们可能不需要生成txt文件?但用户要求生成txt文件。 用户要求:生成单通道PNG和txt标签文件。所以,我们按照要求生成。 总结步骤: 1. 使用上述脚本将json转换为单通道PNG(保存到 `masks/train` 目录) 2. 使用上述脚本将json转换为YOLO格式的txt(保存到 `labels/train` 目录) 3. 将增强后的图像(已经有的jpg)复制到 `images/train` 目录 这样,我们就有了完整的训练数据。 注意:在转换过程中,确保文件名一致(除了扩展名)。 完整代码示例(分为两个脚本): 脚本1:json_to_png.py ```python import glob import os import json import numpy as np import labelme from labelme import utils import cv2 def json_to_png(json_dir, png_output_dir): os.makedirs(png_output_dir, exist_ok=True) json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: print(f'Processing: {json_file}') label_file = labelme.LabelFile(json_file) # 从json中读取图像(如果图像数据存在) if label_file.imageData is not None: img = utils.img_data_to_arr(label_file.imageData) else: # 如果json中没有图像数据,则从imagePath读取 img_path = os.path.join(os.path.dirname(json_file), label_file.imagePath) img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 创建标签映射 label_name_to_value = {'_background_': 0} for shape in label_file.shapes: label_name = shape['label'] if label_name not in label_name_to_value: label_name_to_value[label_name] = len(label_name_to_value) # 生成标签图像 lbl, _ = utils.shapes_to_label( img_shape=img.shape, shapes=label_file.shapes, label_name_to_value=label_name_to_value ) # 保存为PNG png_filename = os.path.basename(json_file).replace('.json', '.png') out_path = os.path.join(png_output_dir, png_filename) utils.lblsave(out_path, lbl) if __name__ == '__main__': json_dir = 'path/to/aug/jsons' # 2000个json文件所在目录 png_output_dir = 'dataset/masks/train' json_to_png(json_dir, png_output_dir) ``` 脚本2:json_to_yolo_txt.py ```python import glob import os import json def get_class_list(json_dir): classes = set() json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: with open(json_file, 'r') as f: data = json.load(f) for shape in data['shapes']: classes.add(shape['label']) # 将背景类排除,因为YOLO格式不需要背景(背景不作为对象) if '_background_' in classes: classes.remove('_background_') return sorted(list(classes)) def convert_to_yolo_txt(json_dir, output_txt_dir, class_list): os.makedirs(output_txt_dir, exist_ok=True) json_files = glob.glob(os.path.join(json_dir, "*.json")) for json_file in json_files: with open(json_file, 'r') as f: data = json.load(f) image_width = data['imageWidth'] image_height = data['imageHeight'] base_name = os.path.splitext(os.path.basename(json_file))[0] txt_file = os.path.join(output_txt_dir, base_name + '.txt') with open(txt_file, 'w') as f_txt: for shape in data['shapes']: # 跳过非多边形 if shape['shape_type'] != 'polygon': continue label = shape['label'] # 跳过背景 if label == '_background_': continue class_id = class_list.index(label) points = shape['points'] # 归一化 normalized_points = [] for point in points: x, y = point normalized_x = x / image_width normalized_y = y / image_height normalized_points.append(normalized_x) normalized_points.append(normalized_y) # 写入一行:class_id x1 y1 x2 y2 ... line = [str(class_id)] + [str(p) for p in normalized_points] f_txt.write(' '.join(line) + '\n') if __name__ == '__main__': json_dir = 'path/to/aug/jsons' output_txt_dir = 'dataset/labels/train' class_list = get_class_list(json_dir) print("Class list:", class_list) convert_to_yolo_txt(json_dir, output_txt_dir, class_list) ``` 最后,将增强后的图像(jpg)复制到 `dataset/images/train`。 注意:在转换过程中,确保每个json文件对应的图像存在(在增强时已经保存了图像)。 这样,我们就得到了2000张图像,2000个单通道PNG掩码,2000个txt标签文件。 §§相关问题§§ 1. 如何确保单通道PNG掩码的像素值正确对应类别? 2. 如果数据集中出现新的类别,如何更新类别列表? 3. 在YOLO格式的txt文件中,为什么需要归一化坐标? 4. 如何验证生成的单通道PNG掩码与原始图像的对齐情况? 5. 对于大型数据集,如何优化转换过程以提高效率?</think>要将2000份LabelMe JSON文件转换为单通道PNG分割掩码和YOLO格式的txt标签文件,请按照以下完整方案操作: ### 完整转换方案 ```python import os import json import numpy as np import cv2 from tqdm import tqdm from labelme import utils def json_to_mask_and_yolo(json_dir, mask_output_dir, txt_output_dir, class_map): """ 将LabelMe JSON文件转换为单通道PNG掩码和YOLO格式txt标签 :param json_dir: JSON文件目录 :param mask_output_dir: 掩码输出目录 :param txt_output_dir: 标签输出目录 :param class_map: 类别名称到ID的映射字典 """ os.makedirs(mask_output_dir, exist_ok=True) os.makedirs(txt_output_dir, exist_ok=True) json_files = [f for f in os.listdir(json_dir) if f.endswith('.json')] for json_file in tqdm(json_files, desc="Processing JSONs"): json_path = os.path.join(json_dir, json_file) base_name = os.path.splitext(json_file)[0] # 加载JSON数据 with open(json_path, 'r') as f: data = json.load(f) img_h = data['imageHeight'] img_w = data['imageWidth'] # 创建空白掩码 mask = np.zeros((img_h, img_w), dtype=np.uint8) txt_lines = [] # 处理每个多边形 for shape in data['shapes']: label = shape['label'] points = shape['points'] # 更新类别映射 if label not in class_map: class_map[label] = len(class_map) + 1 # 0保留给背景 class_id = class_map[label] # 绘制多边形到掩码 pts = np.array(points, dtype=np.int32) cv2.fillPoly(mask, [pts], color=class_id) # 准备YOLO格式数据 normalized_points = [] for point in points: x, y = point normalized_points.extend([x/img_w, y/img_h]) yolo_line = f"{class_id} " + " ".join([f"{p:.6f}" for p in normalized_points]) txt_lines.append(yolo_line) # 保存单通道PNG掩码 mask_path = os.path.join(mask_output_dir, f"{base_name}.png") cv2.imwrite(mask_path, mask) # 保存YOLO格式标签 txt_path = os.path.join(txt_output_dir, f"{base_name}.txt") with open(txt_path, 'w') as f: f.write("\n".join(txt_lines)) # 使用示例 if __name__ == "__main__": JSON_DIR = "path/to/2000_jsons" # JSON文件目录 MASK_DIR = "dataset/masks/train" # 掩码输出目录 TXT_DIR = "dataset/labels/train" # 标签输出目录 # 自动生成类别映射(0=背景) CLASS_MAP = {} json_to_mask_and_yolo(JSON_DIR, MASK_DIR, TXT_DIR, CLASS_MAP) # 保存类别映射文件 with open("dataset/class_map.txt", "w") as f: for name, id in CLASS_MAP.items(): f.write(f"{name}:{id}\n") print(f"转换完成!共处理{len(os.listdir(JSON_DIR))}个文件") print(f"发现类别: {CLASS_MAP}") ``` ### 关键步骤说明 1. **单通道PNG掩码生成** - 创建与图像同尺寸的空白掩码 - 使用`cv2.fillPoly`填充多边形区域 - 每个类别分配唯一整数值(0=背景) - 保存为PNG格式(无损压缩) 2. **YOLO格式标签生成** - 每个对象一行:`class_id x1 y1 x2 y2 ...` - 坐标归一化:除以图像宽高(0.0-1.0范围) - 保留6位小数精度 3. **自动类别映射** - 动态创建类别名称到ID的映射 - 自动生成class_map.txt映射文件 - 格式:`class_name:class_id` ### 数据集目录结构 ```bash dataset/ ├── images/ │ └── train/ # 原始图像(JPEG格式) ├── masks/ │ └── train/ # 单通道PNG掩码 ├── labels/ │ └── train/ # YOLO格式txt标签 └── class_map.txt # 类别映射文件 ``` ### YOLOv8训练配置 创建`dataset.yaml`配置文件: ```yaml # YOLOv8 分割数据集配置 path: ./dataset train: images/train val: images/val # 验证集目录 test: images/test # 测试集目录 # 类别信息 names: 0: background 1: class1 # 根据实际类别修改 2: class2 ... # 分割掩码路径 mask_dir: masks/train ``` ### 使用说明 1. 将原始图像放入`dataset/images/train` 2. 运行转换脚本生成掩码和标签 3. 修改`dataset.yaml`中的类别名称 4. 启动YOLOv8训练: ```bash yolo task=segment mode=train data=dataset.yaml model=yolov8n-seg.pt epochs=100 imgsz=640 ``` ### 验证结果 ```python # 可视化验证 import matplotlib.pyplot as plt def visualize_mask(image_path, mask_path): img = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB) mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.title("Original Image") plt.imshow(img) plt.axis('off') plt.subplot(122) plt.title("Segmentation Mask") plt.imshow(mask, cmap='tab20') # 使用彩色映射 plt.axis('off') plt.show() # 示例调用 visualize_mask("dataset/images/train/IMG_001.jpg", "dataset/masks/train/IMG_001.png") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

onnx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值