YOLO 数据集转换3种JSON格式解析与Python脚本适配实战在计算机视觉项目中数据格式的兼容性往往是第一个需要攻克的堡垒。当你从不同来源获取标注数据时可能会遇到COCO、Labelme和自定义JSON等多种格式而YOLO训练却要求统一的TXT格式。本文将深入解析这三种主流JSON格式的结构差异并提供可复用的Python转换方案助你打通数据预处理的关键环节。1. 理解YOLO标注格式的核心要求YOLO所需的TXT标注文件遵循严格的规范化标准。每个图像对应一个同名文本文件其中每行表示一个对象的标注信息格式为class_id x_center y_center width height所有坐标值必须是归一化的0到1之间计算方式如下x_center 边界框中心x坐标 / 图像宽度y_center 边界框中心y坐标 / 图像高度width 边界框宽度 / 图像宽度height 边界框高度 / 图像高度关键检查点确认坐标值是否已完成归一化验证class_id是否从0开始连续编号检查图像尺寸与标注是否匹配2. COCO JSON格式解析与转换COCOCommon Objects in Context是当前最通用的目标检测数据集格式其标注文件包含以下核心结构{ images: [{id: 0, width: 640, height: 480, file_name: 001.jpg}], annotations: [ { id: 1, image_id: 0, category_id: 2, bbox: [x,y,width,height], # 左上角坐标宽高 area: 1200, iscrowd: 0 } ], categories: [{id: 0, name: person}, {id: 1, name: car}] }转换时需要特别注意COCO使用绝对像素坐标非归一化bbox格式为[x_top_left, y_top_left, width, height]需要建立category_id到class_id的映射关系完整转换脚本import json from pathlib import Path def coco2yolo(coco_path, output_dir): with open(coco_path) as f: data json.load(f) # 创建类别映射表 cat_map {cat[id]: idx for idx, cat in enumerate(data[categories])} # 按图像ID分组标注 img_anns {} for ann in data[annotations]: img_id ann[image_id] img_anns.setdefault(img_id, []).append(ann) # 处理每张图像 for img in data[images]: img_id img[id] img_w, img_h img[width], img[height] txt_path Path(output_dir) / f{Path(img[file_name]).stem}.txt with open(txt_path, w) as f_txt: for ann in img_anns.get(img_id, []): # 转换bbox格式 x, y, w, h ann[bbox] x_center (x w/2) / img_w y_center (y h/2) / img_h w_norm w / img_w h_norm h / img_h # 写入YOLO格式 class_id cat_map[ann[category_id]] line f{class_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n f_txt.write(line)3. Labelme JSON格式深度处理Labelme是流行的图像标注工具其生成的JSON文件结构如下{ version: 5.1.1, flags: {}, shapes: [ { label: dog, points: [[x1,y1], [x2,y2]], # 多边形点或矩形对角点 shape_type: rectangle, flags: {} } ], imagePath: image.jpg, imageData: null, imageHeight: 600, imageWidth: 800 }特殊处理要点支持多边形和矩形两种标注方式需要将多边形转换为外接矩形对于非矩形标注点坐标可能是浮点数或整数转换代码实现import json import numpy as np from pathlib import Path def labelme2yolo(labelme_path, output_dir, class_map): with open(labelme_path) as f: data json.load(f) img_h, img_w data[imageHeight], data[imageWidth] txt_path Path(output_dir) / f{Path(data[imagePath]).stem}.txt with open(txt_path, w) as f_txt: for shape in data[shapes]: label shape[label] points np.array(shape[points]) # 获取边界框坐标 if shape[shape_type] rectangle: x_min, y_min points.min(axis0) x_max, y_max points.max(axis0) else: # 多边形转外接矩形 x_min, y_min points.min(axis0) x_max, y_max points.max(axis0) # 计算YOLO格式坐标 width x_max - x_min height y_max - y_min x_center (x_min width/2) / img_w y_center (y_min height/2) / img_h width / img_w height / img_h # 写入文件 class_id class_map[label] line f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n f_txt.write(line)4. 自定义JSON格式的灵活适配自定义JSON格式千变万化我们需要先分析其结构。以下是一个典型示例[ { image: 001.jpg, annotations: [ { label: cat, bbox: { x: 0.25, # 中心x (已归一化) y: 0.35, # 中心y w: 0.1, # 宽度 h: 0.15 # 高度 } } ] } ]适配策略确认坐标是否已归一化检查bbox表示方式中心点宽高 或 角点坐标验证标签命名一致性通用转换函数def custom_json2yolo(json_path, output_dir, class_map): with open(json_path) as f: data json.load(f) for item in data: img_name Path(item[image]).stem txt_path Path(output_dir) / f{img_name}.txt with open(txt_path, w) as f_txt: for ann in item[annotations]: label ann[label] bbox ann[bbox] # 假设已经是YOLO格式 if all(key in bbox for key in [x, y, w, h]): x, y, w, h bbox[x], bbox[y], bbox[w], bbox[h] # 处理角点格式 elif all(key in bbox for key in [x1, y1, x2, y2]): x1, y1, x2, y2 bbox[x1], bbox[y1], bbox[x2], bbox[y2] w x2 - x1 h y2 - y1 x x1 w/2 y y1 h/2 class_id class_map[label] line f{class_id} {x:.6f} {y:.6f} {w:.6f} {h:.6f}\n f_txt.write(line)5. 实战构建通用转换工具链将上述模块整合为可配置的转换流水线import argparse from pathlib import Path def auto_convert(input_path, output_dir, format_type, class_mapNone): 智能转换入口函数 Path(output_dir).mkdir(exist_okTrue) if format_type coco: coco2yolo(input_path, output_dir) elif format_type labelme: if not class_map: raise ValueError(class_map required for Labelme format) labelme2yolo(input_path, output_dir, class_map) elif format_type custom: if not class_map: raise ValueError(class_map required for custom format) custom_json2yolo(input_path, output_dir, class_map) else: raise ValueError(fUnsupported format: {format_type}) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(input, helpInput JSON file path) parser.add_argument(output_dir, helpOutput directory for TXT files) parser.add_argument(--format, choices[coco, labelme, custom], requiredTrue) parser.add_argument(--class_map, helpJSON file mapping labels to class IDs) args parser.parse_args() class_map json.loads(Path(args.class_map).read_text()) if args.class_map else None auto_convert(args.input, args.output_dir, args.format, class_map)使用示例# 转换COCO格式 python converter.py annotations.json output_coco --format coco # 转换Labelme格式需提供类别映射 python converter.py labelme.json output_labelme --format labelme --class_map classes.json6. 验证与调试技巧转换后务必进行数据验证可视化检查使用OpenCV绘制边界框import cv2 def visualize_yolo(img_path, txt_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: class_id, x, y, w_box, h_box map(float, line.split()) # 转换回像素坐标 x1 int((x - w_box/2) * w) y1 int((y - h_box/2) * h) x2 int((x w_box/2) * w) y2 int((y h_box/2) * h) # 绘制矩形和标签 cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) cv2.putText(img, class_names[int(class_id)], (x1,y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (36,255,12), 2) cv2.imshow(Preview, img) cv2.waitKey(0)统计验证检查类别分布和框尺寸分布import matplotlib.pyplot as plt def analyze_annotations(txt_dir): class_counts {} box_ratios [] for txt_path in Path(txt_dir).glob(*.txt): with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue class_id int(parts[0]) w float(parts[3]) h float(parts[4]) # 统计类别 class_counts[class_id] class_counts.get(class_id, 0) 1 # 计算宽高比 if h 0: box_ratios.append(w/h) # 绘制统计图 plt.figure(figsize(12,5)) plt.subplot(121) plt.bar(class_counts.keys(), class_counts.values()) plt.title(Class Distribution) plt.subplot(122) plt.hist(box_ratios, bins50) plt.title(Width/Height Ratio Distribution) plt.show()7. 高级应用处理复杂场景多任务数据转换同时处理检测和分割标注def coco2yolo_seg(coco_path, output_dir): 转换COCO实例分割标注 with open(coco_path) as f: data json.load(f) cat_map {cat[id]: idx for idx, cat in enumerate(data[categories])} img_anns defaultdict(list) for ann in data[annotations]: img_anns[ann[image_id]].append(ann) for img in data[images]: txt_path Path(output_dir) / f{Path(img[file_name]).stem}.txt with open(txt_path, w) as f_txt: for ann in img_anns.get(img[id], []): if not ann[segmentation]: continue class_id cat_map[ann[category_id]] # 获取所有多边形点COCO允许多个多边形 segments [] for seg in ann[segmentation]: points np.array(seg).reshape(-1,2) # 归一化 points[:,0] / img[width] points[:,1] / img[height] segments.append(points.flatten().tolist()) # YOLO分割格式class_id x1 y1 x2 y2 ... xn yn line f{class_id} .join(map(str, sum(segments, []))) \n f_txt.write(line)性能优化技巧使用多进程处理大型数据集from multiprocessing import Pool def process_file(args): 包装函数供多进程调用 file_path, format_type, output_dir, class_map args try: auto_convert(file_path, output_dir, format_type, class_map) return True except Exception as e: print(fError processing {file_path}: {str(e)}) return False def batch_convert(file_list, format_type, output_dir, class_mapNone, workers4): 批量转换入口 tasks [(f, format_type, output_dir, class_map) for f in file_list] with Pool(workers) as pool: results pool.map(process_file, tasks) print(fSuccessfully processed {sum(results)}/{len(file_list)} files)