当前位置: 首页 > news >正文

YOLO 数据集转换:3种JSON格式解析与Python脚本适配实战

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格式解析与转换

COCO(Common 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(axis=0) x_max, y_max = points.max(axis=0) else: # 多边形转外接矩形 x_min, y_min = points.min(axis=0) x_max, y_max = points.max(axis=0) # 计算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 # 高度 } } ] } ]

适配策略

  1. 确认坐标是否已归一化
  2. 检查bbox表示方式(中心点+宽高 或 角点坐标)
  3. 验证标签命名一致性

通用转换函数

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_map=None): """智能转换入口函数""" Path(output_dir).mkdir(exist_ok=True) 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(f"Unsupported format: {format_type}") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("input", help="Input JSON file path") parser.add_argument("output_dir", help="Output directory for TXT files") parser.add_argument("--format", choices=["coco", "labelme", "custom"], required=True) parser.add_argument("--class_map", help="JSON 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.json

6. 验证与调试技巧

转换后务必进行数据验证:

  1. 可视化检查:使用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)
  1. 统计验证:检查类别分布和框尺寸分布
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, bins=50) 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(f"Error processing {file_path}: {str(e)}") return False def batch_convert(file_list, format_type, output_dir, class_map=None, workers=4): """批量转换入口""" 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(f"Successfully processed {sum(results)}/{len(file_list)} files")
http://www.jsqmd.com/news/1150548/

相关文章:

  • PyTorch 2.0 多GPU训练实战:DataParallel 与 DistributedDataParallel 性能对比分析
  • WarcraftHelper:魔兽争霸3实用技巧与高效优化方法深度解析
  • 共享单车时空分布分析:基于 Pandas 与 GeoPandas 的 4 步数据处理流程
  • Sinkhorn算法Python实战:5行代码计算Wasserstein距离,误差<1%
  • OBJ 3D文件格式解析:从ASCII文本到MeshLab渲染的5个关键语句
  • 赛博朋克边缘行者第二季:视觉升级与剧情深度解析
  • NoSleep防休眠工具终极指南:如何让Windows电脑保持清醒工作
  • 稳健性设计实战:3步正交实验法降低产品性能变异系数 20%
  • 3种机器学习算法对比:SVM、ML与MD在湿地分类中的97.2%精度差异分析
  • 如何免费获取专业级鼠标性能测试工具:5个实用技巧提升你的设备表现
  • OpenCV 4.8 实战:Harris角点检测与SIFT特征提取的3种MATLAB代码对比
  • 免费开源显存稳定性测试工具memtest_vulkan:你的显卡健康诊断专家
  • PyTorch 2.0 实战:CIFAR-10 图像分类,5个Epochs实现85%+准确率
  • 随机森林 vs 梯度提升树:5个回归任务实测,R²与训练速度对比
  • PINN 求解四阶 PDE 实战:PyTorch 实现 u_xx - u_yyyy 误差 0.0019
  • FanControl V270终极指南:如何彻底解决Windows风扇噪音与散热难题
  • 数据挖掘中的维归约:PCA主成分分析原理与3大应用场景解析
  • Simulink 2024b 4种数字调制系统仿真:BASK/BFSK/BPSK/QPSK 误码率对比分析
  • TensorFlow 2.x 石头剪刀布数据集:3步使用 tf.data.Dataset 重构数据流
  • FME 2023.2 + PythonCaller 批量压缩照片:单次处理1000张,日志精准定位失败项
  • ArcGIS Pro 3.2 分区统计:12种统计类型实战对比与多维栅格处理
  • AI视频生成技术实战:从Stable Video Diffusion到游戏技能特效制作
  • NoSleep:终极Windows防休眠解决方案,告别意外锁屏烦恼的完整指南
  • HOG与LBP特征实战:线性灰度变换下的3种不变性验证与代码实现
  • 从对抗样本到分布偏移:3类鲁棒性挑战的数学原理与代码实战
  • Ideogram 4.0:AI图像生成的文字排版革命与实战指南
  • U-Net 医学图像分割实战:PyTorch 1.13 复现细胞边缘检测,IoU 达 0.85
  • Claude官方使用全解析:从技术原理到开发实践
  • git 压缩本地最近多个commit
  • Nebula-matrix M1600网卡驱动未来展望:m1600-driver路线图详解