Labelme标注数据一键转换YOLO/COCO格式实战指南当你用Labelme完成人体姿态标注后那些密密麻麻的JSON文件就像一堆未经雕琢的原材料——它们蕴含着价值但需要经过精加工才能被深度学习模型消化吸收。本文将带你跨越从原始标注到训练就绪数据的关键一步用Python脚本实现批量格式转换解决标注完却不会用的尴尬处境。1. 理解Labelme输出结构打开任意一个Labelme生成的JSON文件你会发现它像俄罗斯套娃一样层层嵌套着信息。这个结构化的数据容器主要包含以下几个关键部分{ version: 4.5.6, flags: {}, shapes: [ { label: person, points: [[x1,y1], [x2,y2], ...], group_id: null, shape_type: polygon, flags: {} }, { label: 1, points: [[x,y]], group_id: null, shape_type: point, flags: {} } ], imagePath: FALL_0000_0001.jpg, imageData: base64编码的图片数据, imageHeight: 720, imageWidth: 1280 }关键字段解析shapes数组包含所有标注元素矩形框、多边形、关键点等人体姿态标注通常包含一个rectangle或polygon表示人体边界框17个point类型的关键点标注对应COCO的17个标准关节点每个关键点的label字段通常对应关节点编号如1-17注意Labelme默认不强制规范标注顺序建议在标注前统一约定关键点编号规则避免后续转换混乱。2. YOLO格式转换实战YOLO格式要求每个图像对应一个.txt文件其中每行表示一个对象格式为class_id x_center y_center width height px1 py1 px2 py2 ... px17 py17坐标值都是相对于图像宽高的归一化数值0-1之间。以下是完整的Python转换脚本import json import os import glob from pathlib import Path def labelme_to_yolo(json_dir, output_dir, class_map): os.makedirs(output_dir, exist_okTrue) # 遍历所有JSON文件 for json_file in glob.glob(os.path.join(json_dir, *.json)): with open(json_file, r) as f: data json.load(f) img_width data[imageWidth] img_height data[imageHeight] txt_filename Path(json_file).stem .txt with open(os.path.join(output_dir, txt_filename), w) as f_txt: # 提取人体边界框和关键点 bbox None keypoints [None]*17 # 预分配17个关键点位置 for shape in data[shapes]: if shape[shape_type] rectangle: # 转换矩形框为YOLO格式 [[x1, y1], [x2, y2]] shape[points] x_center ((x1 x2) / 2) / img_width y_center ((y1 y2) / 2) / img_height width abs(x2 - x1) / img_width height abs(y2 - y1) / img_height bbox (x_center, y_center, width, height) elif shape[shape_type] point and shape[label].isdigit(): # 存储关键点假设标签是1-17的数字 point_id int(shape[label]) - 1 # 转为0-based索引 [[x, y]] shape[points] keypoints[point_id] (x / img_width, y / img_height) if bbox and all(keypoints): # 写入YOLO格式行 line [str(class_map[person])] list(map(str, bbox)) for kp in keypoints: line.extend(map(str, kp)) f_txt.write( .join(line) \n) # 使用示例 class_map {person: 0} # 类别ID映射 labelme_to_yolo(labelme_json/, yolo_labels/, class_map)关键处理逻辑遍历所有JSON文件解析图像尺寸和标注元素从矩形框标注计算归一化的中心坐标和宽高将17个关键点按编号顺序排列并归一化按YOLO格式组合所有信息写入txt文件提示实际应用中可能需要处理多人情况此时需要根据group_id或其他逻辑关联同一人的框和关键点。3. COCO格式转换方案COCO格式采用单个JSON文件描述整个数据集结构更为复杂但信息更完整。以下是核心字段的转换示例import json import datetime from collections import defaultdict def labelme_to_coco(json_files, output_file): # 初始化COCO数据结构 coco { info: { description: Human Pose Dataset, url: , version: 1.0, year: datetime.datetime.now().year, contributor: , date_created: datetime.datetime.now().isoformat() }, licenses: [], images: [], annotations: [], categories: [{ id: 1, name: person, keypoints: [nose,left_eye,right_eye,...,right_ankle], skeleton: [[16,14],[14,12],...,[15,17]] # 关节点连接关系 }] } image_id 1 annotation_id 1 for json_file in json_files: with open(json_file, r) as f: data json.load(f) # 添加图像信息 coco[images].append({ id: image_id, file_name: data[imagePath], width: data[imageWidth], height: data[imageHeight] }) # 解析标注 keypoints [0]*51 # 17个点x,y,v * 3 (v0不可见,1遮挡,2可见) bbox None for shape in data[shapes]: if shape[shape_type] rectangle: [[x1, y1], [x2, y2]] shape[points] bbox [x1, y1, x2-x1, y2-y1] # [x,y,width,height] elif shape[shape_type] point and shape[label].isdigit(): point_id int(shape[label]) - 1 [[x, y]] shape[points] keypoints[point_id*3] x keypoints[point_id*31] y keypoints[point_id*32] 2 # 默认可见 if bbox and any(keypoints): # 计算bbox面积 area bbox[2] * bbox[3] # 添加标注 coco[annotations].append({ id: annotation_id, image_id: image_id, category_id: 1, bbox: bbox, area: area, iscrowd: 0, keypoints: keypoints, num_keypoints: sum(1 for i in range(17) if keypoints[i*32] 0) }) annotation_id 1 image_id 1 # 保存COCO格式JSON with open(output_file, w) as f: json.dump(coco, f, indent2) # 使用示例 json_files glob.glob(labelme_json/*.json) labelme_to_coco(json_files, coco_pose.json)COCO格式特点关键点采用固定17点规范每个点包含(x,y,visibility)三个值需要显式定义关节点之间的连接关系skeleton支持多人场景通过不同annotation区分兼容目标检测、实例分割和关键点检测任务4. 格式对比与选型建议特性YOLO格式COCO格式文件结构每图一个txt文件整个数据集一个JSON文件关键点支持需要自定义格式原生支持17个标准关键点多人场景需要额外处理原生支持可视化工具有限支持广泛支持如COCO-Viewer训练框架兼容性YOLOv5/v8等MMPose, Detectron2等扩展性较差优秀选型建议选择YOLO格式当使用YOLO系列模型进行训练数据集规模较小简单项目需要快速验证原型选择COCO格式当使用基于COCO预训练的模型需要多人姿态估计计划公开数据集或长期维护需要利用丰富的COCO生态工具5. 常见问题与解决方案Q1: 关键点顺序不一致怎么办在转换前建立映射表统一顺序KEYPOINT_MAPPING { nose: 0, left_eye: 1, right_eye: 2, # ...其他关键点 } # 在转换时使用 keypoint_id KEYPOINT_MAPPING[shape[label]]Q2: 如何处理多人场景修改转换逻辑通过group_id关联同一人的元素from collections import defaultdict person_dict defaultdict(lambda: {bbox: None, keypoints: [None]*17}) for shape in data[shapes]: group_id shape.get(group_id, 0) # 默认0表示单人 if shape[shape_type] rectangle: person_dict[group_id][bbox] shape[points] elif shape[shape_type] point: person_dict[group_id][keypoints][int(shape[label])-1] shape[points][0]Q3: 转换后坐标偏移怎么办检查归一化计算是否正确特别是边界框的坐标顺序# 确保矩形框坐标是[x_min, y_min, x_max, y_max]顺序 x1, y1 min(p[0] for p in bbox_points), min(p[1] for p in bbox_points) x2, y2 max(p[0] for p in bbox_points), max(p[1] for p in bbox_points)Q4: 如何验证转换结果使用可视化脚本检查import cv2 import numpy as np def plot_yolo_annotation(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts list(map(float, line.strip().split())) # 绘制边界框 x_center, y_center, bw, bh parts[1:5] x1 int((x_center - bw/2) * w) y1 int((y_center - bh/2) * h) x2 int((x_center bw/2) * w) y2 int((y_center bh/2) * h) cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) # 绘制关键点 for i in range(17): px int(parts[5 i*2] * w) py int(parts[6 i*2] * h) cv2.circle(img, (px,py), 3, (0,0,255), -1) cv2.imshow(Preview, img) cv2.waitKey(0) # 使用示例 plot_yolo_annotation(FALL_0000_0001.jpg, yolo_labels/FALL_0000_0001.txt)6. 高级技巧与优化建议批量处理加速方案对于大规模数据集可以使用多进程加速from multiprocessing import Pool def process_single(json_file): # 单个文件的转换逻辑 pass if __name__ __main__: json_files glob.glob(labelme_json/*.json) with Pool(processes4) as pool: # 使用4个进程 pool.map(process_single, json_files)增量式转换当新增标注时只处理修改过的文件import hashlib def get_file_hash(filepath): with open(filepath, rb) as f: return hashlib.md5(f.read()).hexdigest() # 保存文件哈希值下次只处理有变化的文件与训练流程集成将转换脚本作为训练前的数据准备步骤# train.py if not os.path.exists(yolo_labels): print(Converting Labelme to YOLO format...) os.system(python labelme2yolo.py) # 继续训练流程格式转换的单元测试确保转换准确性编写测试用例import unittest class TestConversion(unittest.TestCase): def test_bbox_normalization(self): # 测试边界框归一化是否正确 test_bbox [[10, 20], [30, 40]] img_w, img_h 100, 100 x_center (10 30) / 2 / img_w self.assertAlmostEqual(x_center, 0.2) # 其他测试用例... if __name__ __main__: unittest.main()