Labelme生成的JSON文件别乱扔!手把手教你用Python脚本批量转成YOLO格式
Labelme标注数据工程化:Python脚本实现YOLO格式批量转换实战
在计算机视觉项目的实际开发流程中,数据标注往往只完成了整个工作流的20%,而剩下的80%精力都消耗在数据清洗、格式转换和验证环节。当你用Labelme精心标注了数百张图像后,那些生成的JSON文件就像未经雕琢的玉石——价值连城但需要专业加工才能发挥真正作用。
1. 理解Labelme与YOLO的数据格式差异
Labelme生成的JSON文件采用绝对坐标记录多边形顶点,而YOLO需要的却是归一化后的中心点坐标和宽高比例。这种本质差异导致直接使用原始标注会面临三个核心挑战:
- 坐标系转换:从图像像素坐标系到YOLO的归一化坐标系(0-1范围)
- 形状描述转换:从多边形顶点序列到边界框的数学表达
- 标签映射:从文本标签到YOLO要求的类别索引
典型的Labelme JSON结构关键字段如下:
{ "version": "5.1.1", "flags": {}, "shapes": [ { "label": "cat", "points": [[302,240],[402,240],[402,340],[302,340]], "shape_type": "polygon" } ], "imagePath": "example.jpg", "imageWidth": 800, "imageHeight": 600 }对应的YOLO格式要求每张图片一个txt文件,每行表示一个对象:
<class_id> <x_center> <y_center> <width> <height>2. 构建Python转换脚本的核心逻辑
2.1 基础转换函数实现
创建一个labelme2yolo.py文件,首先实现核心几何计算函数:
import json import os import numpy as np def polygon_to_yolo(polygon_points, img_width, img_height): """将多边形顶点转换为YOLO格式的边界框""" points = np.array(polygon_points) x_min, y_min = np.min(points, axis=0) x_max, y_max = np.max(points, axis=0) # 计算中心点和宽高(归一化) x_center = ((x_min + x_max) / 2) / img_width y_center = ((y_min + y_max) / 2) / img_height width = (x_max - x_min) / img_width height = (y_max - y_min) / img_height return x_center, y_center, width, height2.2 批量处理与文件输出
添加目录遍历和文件输出逻辑:
def process_labelme_json(json_path, class_mapping, output_dir): with open(json_path, 'r') as f: data = json.load(f) txt_lines = [] for shape in data['shapes']: if shape['shape_type'] != 'polygon': continue class_name = shape['label'].lower() if class_name not in class_mapping: continue # 转换坐标 x_center, y_center, width, height = polygon_to_yolo( shape['points'], data['imageWidth'], data['imageHeight'] ) txt_lines.append(f"{class_mapping[class_name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") # 写入YOLO格式文件 base_name = os.path.splitext(os.path.basename(json_path))[0] txt_path = os.path.join(output_dir, f"{base_name}.txt") with open(txt_path, 'w') as f: f.write('\n'.join(txt_lines))3. 工程化实践中的关键问题处理
3.1 复杂多边形的优化策略
当遇到复杂多边形时,直接取最小外接矩形可能导致标注质量下降。我们可以在转换前对多边形进行凸包处理:
from scipy.spatial import ConvexHull def optimize_polygon(points): """对复杂多边形进行凸包优化""" hull = ConvexHull(points) return [points[i] for i in hull.vertices]3.2 多线程批量处理
对于大型数据集,添加多线程支持可以显著提升处理速度:
from concurrent.futures import ThreadPoolExecutor def batch_convert(input_dir, output_dir, class_mapping, workers=4): os.makedirs(output_dir, exist_ok=True) json_files = [f for f in os.listdir(input_dir) if f.endswith('.json')] with ThreadPoolExecutor(max_workers=workers) as executor: for json_file in json_files: executor.submit( process_labelme_json, os.path.join(input_dir, json_file), class_mapping, output_dir )4. 数据验证与质量检查
转换完成后必须验证结果准确性,这里提供一个可视化检查脚本:
import cv2 def visualize_yolo_annotation(image_path, txt_path, class_names): image = cv2.imread(image_path) height, width = image.shape[:2] with open(txt_path, 'r') as f: for line in f: class_id, xc, yc, w, h = map(float, line.strip().split()) # 转换回像素坐标 x = int((xc - w/2) * width) y = int((yc - h/2) * height) box_w = int(w * width) box_h = int(h * height) # 绘制边界框 cv2.rectangle(image, (x,y), (x+box_w,y+box_h), (0,255,0), 2) cv2.putText(image, class_names[int(class_id)], (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,255,0), 2) cv2.imshow('Validation', image) cv2.waitKey(0) cv2.destroyAllWindows()5. 完整项目结构与管理
建议采用以下目录结构组织转换项目:
labelme2yolo/ ├── src/ │ ├── converter.py # 主转换脚本 │ ├── validator.py # 验证脚本 │ └── utils.py # 工具函数 ├── configs/ │ └── classes.yaml # 类别映射配置 ├── input_data/ # 原始Labelme数据 │ ├── images/ # 原图目录 │ └── annotations/ # JSON标注目录 └── output_data/ # 转换输出 ├── images/ # 图片(可符号链接) └── labels/ # YOLO格式标签示例classes.yaml配置文件:
class_mapping: cat: 0 dog: 1 person: 26. 高级技巧与性能优化
6.1 内存映射加速大文件处理
对于超大JSON文件(>100MB),可以使用ijson库进行流式处理:
import ijson def process_large_json(json_path): with open(json_path, 'rb') as f: objects = ijson.items(f, 'shapes.item') for shape in objects: # 处理每个shape对象 pass6.2 增量处理与断点续传
添加检查点机制,避免重复处理:
def batch_convert_with_checkpoint(input_dir, output_dir, checkpoint_file): processed = set() if os.path.exists(checkpoint_file): with open(checkpoint_file, 'r') as f: processed.update(f.read().splitlines()) with open(checkpoint_file, 'a') as checkpoint: for json_file in os.listdir(input_dir): if json_file in processed: continue # 处理文件... checkpoint.write(f"{json_file}\n")6.3 并行GPU加速计算
对于超大规模数据集,可以使用CUDA加速几何计算:
import cupy as cp def gpu_polygon_to_yolo(polygon_points, img_width, img_height): points = cp.array(polygon_points) x_min, y_min = cp.min(points, axis=0) x_max, y_max = cp.max(points, axis=0) x_center = ((x_min + x_max) / 2) / img_width y_center = ((y_min + y_max) / 2) / img_height width = (x_max - x_min) / img_width height = (y_max - y_min) / img_height return x_center.get(), y_center.get(), width.get(), height.get()