ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LabelMe JSON转YOLO格式:多边形到边界框的坐标重映射

LabelMe JSON转YOLO格式:多边形到边界框的坐标重映射 简介本资源是一个轻量级的LabelMe至YOLO格式转换工具包面向计算机视觉方向的算法工程师、AI初学者及数据标注人员解决分割数据集从LabelMe JSON格式批量转为YOLOv5/v7兼容文本标签的核心痛点尤其适用于需快速构建实例分割训练数据的科研与项目场景。压缩包共6个文件9KB含核心脚本labelme2yolo.py、详细说明文档README.md、依赖清单requirements.txt、许可协议LICENSE及2份说明类txt文件覆盖转换逻辑、参数配置、目录组织与使用示例结构精简、开箱即用。目前已有213人学习下载用户可直接运行脚本完成训练/验证集自动划分、JSON批量解析、多边形掩码到YOLO规范坐标系的精准映射并支持--seg参数启用实例分割专用格式输出显著降低数据预处理门槛与出错率。1. LabelMe JSON 转 YOLO 文本不是格式搬运而是语义对齐的坐标重映射如果你刚用 LabelMe 完成一批带多边形掩码polygon的图像分割标注却卡在模型训练入口——YOLO 系列v5/v8/v10只认.txt文件里归一化的归一化中心点宽高class_id x_center y_center width height而 LabelMe 输出的是原始像素坐标的顶点数组points: [[x1,y1],[x2,y2],...]那么你面对的就不是简单的文件后缀替换而是两类标注范式间的几何语义转换从任意形状的封闭轮廓到目标检测所需的最小外接矩形Bounding Box参数。这个过程丢失了分割精度但换来了 YOLO 检测器的兼容性。本文聚焦于labelme2yolo.py这类工具的核心逻辑——它不生成新数据而是用 OpenCV 或 NumPy 对每个多边形做cv2.boundingRect()计算再将结果按图像尺寸归一化、写入指定目录结构。适合已标注好 LabelMe 数据集、正准备接入 YOLO 训练 pipeline 的 CV 工程师与算法实习生尤其当你发现labelme_json_to_dataset生成的label.png无法被ultralytics直接读取时这里就是你该停下的第一站。2. 为什么必须重写转换逻辑LabelMe 多边形 vs YOLO BBox 的三重错位LabelMe 和 YOLO 在数据表达层存在根本性差异直接字符串替换或字段映射必然失败。理解这三重错位是写出健壮转换脚本的前提。2.1 坐标系原点与归一化基准不一致LabelMe 的points数组使用图像左上角为 (0,0)所有坐标均为整数像素值YOLO 要求.txt中的x_center,y_center,width,height全部除以图像宽高范围在[0,1]区间。若忽略归一化模型会因输入数值量级错误如x_center420而非0.63导致梯度爆炸或收敛停滞。提示必须从 LabelMe JSON 的imageWidth和imageHeight字段读取真实尺寸不能依赖文件名或硬编码。常见错误是用PIL.Image.open().size二次读图——当 JSON 中宽高与实际图像不一致如缩放后未更新 JSON时会导致 bbox 严重偏移。2.2 标签类别映射需显式声明不可依赖字符串顺序LabelMe 的label是字符串如carYOLO 的class_id是从0开始的整数索引。转换脚本必须提供明确的类别映射表否则[person, car, traffic_light]可能被错误地映射为0→traffic_light。网络上流传的简易脚本常省略此步导致训练时类别混淆。2.3 多边形掩码需降维为矩形框且需处理空标注与无效多边形LabelMe 支持单图多对象、单对象多区域如分割人体不同部位而 YOLO 每行.txt仅描述一个 bbox。转换时需对每个shape循环计算其 bounding box跳过points少于 3 个顶点的无效多边形LabelMe 允许画线或点但 YOLO 不接受当points全为(0,0)或超出图像边界时记录警告而非静默丢弃。下面这段核心转换逻辑正是解决上述三重错位的最小可行实现import json import cv2 import numpy as np from pathlib import Path def convert_labelme_to_yolo(json_path: str, class_mapping: dict, output_dir: str): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] yolo_lines [] for shape in data[shapes]: label shape[label] if label not in class_mapping: print(fWarning: unknown label {label} in {json_path}, skipped) continue class_id class_mapping[label] points np.array(shape[points], dtypenp.int32) # 过滤无效多边形顶点数 3 或坐标越界 if len(points) 3: continue if np.any(points[:, 0] 0) or np.any(points[:, 0] img_w) or \ np.any(points[:, 1] 0) or np.any(points[:, 1] img_h): print(fWarning: polygon out of bounds in {json_path}, skipped) continue # OpenCV 计算最小外接矩形x, y, w, h x, y, w, h cv2.boundingRect(points) # 归一化中心点 宽高全部除以图像尺寸 x_center (x w / 2.0) / img_w y_center (y h / 2.0) / img_h width w / img_w height h / img_h # YOLO 格式class_id x_center y_center width height yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 写入 .txt 文件与原图同名不含路径 txt_filename Path(json_path).stem .txt output_path Path(output_dir) / txt_filename with open(output_path, w, encodingutf-8) as f: f.write(\n.join(yolo_lines))这段代码的关键参数说明class_mapping: 字典如{person: 0, car: 1, dog: 2}必须与你的data.yaml中names顺序严格一致cv2.boundingRect(): 返回(x_min, y_min, width, height)非中心点故需手动计算x_center x_min w/2:.6f: 保留 6 位小数避免浮点误差累积YOLO 训练对小数精度敏感Path(json_path).stem: 提取无扩展名的文件名确保.jpg图像与同名.txt配对。2.4 为什么不用labelme.utils.shapes_to_label()——官方工具链的隐含陷阱LabelMe 自带的labelme/utils/shapes_to_label.py旨在生成语义分割用的label.png其输出是像素级分类图而非 YOLO 所需的 bbox 文本。试图复用该函数会得到全黑或全白的 mask 图像再用cv2.findContours()逆向提取 bbox 效率极低且易出错。社区中不少教程误将shapes_to_label当作通用转换入口实则南辕北辙。正确路径是绕过labelme的内部工具直接解析 JSON 结构——因为 JSON 是 LabelMe 的唯一权威标注源label.png只是衍生品。3. 用labelme2yolo.py在本地跑通最小命令从解压到验证的完整链路标题中的.zip文件通常包含一个名为labelme2yolo.py的脚本其设计目标是“开箱即用”。但实际部署时需补全三个关键环节环境依赖、目录结构约定、类别映射配置。以下是以 Ultralytics YOLOv8 为下游的完整执行流程。3.1 环境准备与依赖安装labelme2yolo.py本质是 Python 脚本依赖极简numpy,opencv-python,json,pathlib。但需注意 OpenCV 版本兼容性——cv2.boundingRect()在 4.5.0 稳定低于此版本可能返回负坐标。推荐使用 pip 安装pip install numpy opencv-python4.8.1.78注意不要用conda install opencv某些 conda 渠道的 OpenCV 编译选项缺失boundingRect支持导致ValueError: not enough values to unpack错误。3.2 目录结构必须严格遵循 YOLO 规范YOLO 训练器如ultralytics train要求数据集按train/,val/,test/子目录组织且每个子目录下必须同时存在images/和labels/。labelme2yolo.py通常不自动创建此结构需手动初始化# 假设你的 LabelMe 原始数据在 ./labelme_dataset/ mkdir -p yolo_dataset/{train,val,test}/{images,labels} # 将原始图片复制到对应目录示例train cp ./labelme_dataset/*.jpg ./yolo_dataset/train/images/ # 确保 JSON 文件与 JPG 同名放在同一目录LabelMe 默认如此 ls ./labelme_dataset/ | grep .json$ | wc -l # 应等于 JPG 数量3.3 执行转换命令并验证输出假设labelme2yolo.py位于当前目录且你已定义好classes.txt每行一个类别顺序即 class_id# 方式一使用内置 --classes 参数如果脚本支持 python labelme2yolo.py \ --input_dir ./labelme_dataset/ \ --output_dir ./yolo_dataset/train/labels/ \ --classes person car traffic_light # 方式二更可控——先生成 class_mapping 字典再调用函数 # 创建 classes.txt echo -e person\ncar\ntraffic_light classes.txt # 执行转换此命令调用上节代码的封装版 python -c import sys; sys.path.append(.); from labelme2yolo import convert_labelme_to_yolo; mapping {line.strip(): i for i, line in enumerate(open(classes.txt))}; convert_labelme_to_yolo(./labelme_dataset/001.json, mapping, ./yolo_dataset/train/labels/) 3.4 验证转换结果是否合法三步快速检查法转换完成后必须验证.txt文件是否符合 YOLO 规范否则训练会报AssertionError: dataset xxx images not found等隐蔽错误检查项方法合格标准文件名配对ls ./yolo_dataset/train/images/ | sed s/.jpg$// | sort img_list.txt; ls ./yolo_dataset/train/labels/ | sed s/.txt$// | sort lbl_list.txt; diff img_list.txt lbl_list.txt两文件完全一致无差异输出数值范围head -n 1 ./yolo_dataset/train/labels/001.txt5 个数字第2-5个均在0.0到1.0之间如0 0.421875 0.532227 0.218750 0.195312行数匹配wc -l ./yolo_dataset/train/labels/001.txt行数 该图中 LabelMe 标注的对象数打开001.json查shapes长度若任一检查失败立即回溯labelme2yolo.py中的img_w/img_h读取逻辑——90% 的问题源于 JSON 中宽高字段缺失或为0。4.labelme2yolo.py的 3 个必调参数与 2 类典型故障排查开源社区中的labelme2yolo.py实现各异但核心参数高度收敛。掌握以下三个参数的含义与调整时机可覆盖 95% 的定制需求而两类高频故障则需结合日志与图像可视化定位。4.1--keep_original_polygon保留原始多边形坐标用于后续分割任务YOLO 检测只需 bbox但你可能计划后续迁移到 YOLOv8-seg 或 SAM 做实例分割。此时不应丢弃points数据。启用该参数后脚本会在.txt同目录生成_polygon.txt文件每行格式为class_id x1 y1 x2 y2 ... xn yn归一化后的顶点序列。例如# 001_polygon.txt 0 0.123 0.456 0.132 0.467 0.128 0.472 0.120 0.461 1 0.654 0.234 0.662 0.231 0.668 0.239 0.660 0.242提示此参数不改变主.txt输出仅新增文件。若你用ultralytics训练需自行修改dataset.py加载_polygon.txt官方不支持。4.2--min_area_ratio过滤过小目标避免 YOLO 训练噪声LabelMe 允许标注微小物体如远处的交通标志但 YOLO 对小于32x32像素的目标召回率极低。添加面积阈值可主动剔除噪声# 在 convert 函数内插入 area w * h min_area (img_w * img_h) * min_area_ratio # min_area_ratio 默认 0.0005 if area min_area: continue设置--min_area_ratio 0.001意味着仅保留占图像总面积千分之一以上的 bbox对 640x480 图像即307像素以上。该值需根据你的图像分辨率和目标尺度实验确定——无人机航拍图宜设0.0001手机拍摄图宜设0.002。4.3--output_format支持 YOLOv5/v8/v10 的 bbox 坐标微调虽然三者都用x_center y_center width height但 v10 新增了confidence字段训练时为 1.0而某些部署工具要求class_id后跟score。--output_format参数可切换格式命令参数输出示例适用场景v5--output_format v50 0.421 0.532 0.218 0.195Ultralytics v8.0.x 及之前v10--output_format v100 1.0 0.421 0.532 0.218 0.195Ultralytics v10.0.0 或 TensorRT 部署4.4 故障一.txt文件为空但 JSON 明确有shapes此问题 100% 源于cv2.boundingRect()输入了非法点集。典型诱因有两个LabelMe 版本升级导致 JSON 结构变更新版 LabelMe5.4.0在points外增加group_id和flags字段但points本身仍是二维数组。检查print(len(data[shapes][0][points]))是否为0多边形顶点被 LabelMe 自动简化当用户快速拖拽画多边形时LabelMe 可能将[[10,20],[15,25],[12,22]]简化为[[10,20],[12,22]]仅两点触发len(points)3被跳过。解决方案是改用cv2.convexHull(points)生成凸包再计算 bbox但会轻微扩大框范围。4.5 故障二YOLO 训练报box xyxy are not within [0,1]但.txt看似合规这是归一化计算错误的典型症状。根源常在于图像实际尺寸与 JSON 中imageWidth/imageHeight不符用ffprobe -v quiet -show_entries streamwidth,height -of csvp0 ./yolo_dataset/train/images/001.jpg获取真实宽高对比 JSON 中字段坐标计算未考虑 OpenCV 的整数截断cv2.boundingRect()返回int当x0, w1时xw/2 0.5正确但若x0, w0退化为点则w/20x_center0合法。真正危险的是ximg_w-1, w1→x_center(img_w-10.5)/img_w ≈ 0.999...看似合规但浮点误差可能导致1.0。修复方法是在归一化后强制裁剪x_center max(0.0, min(1.0, x_center))。5. 进阶技巧批量验证转换质量——用 OpenCV 可视化反向渲染 bbox转换脚本输出.txt后最可靠的验证不是肉眼检查数字而是将 bbox 画回原图确认其是否准确覆盖目标。以下脚本可一键生成带 bbox 的预览图暴露所有坐标错位问题import cv2 import numpy as np from pathlib import Path def visualize_yolo_labels(image_path: str, label_path: str, class_names: list, output_path: str): img cv2.imread(image_path) h, w img.shape[:2] if not Path(label_path).exists(): cv2.imwrite(output_path, img) # 无标注图直接保存 return with open(label_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) 5: continue class_id int(parts[0]) x_center, y_center, width, height map(float, parts[1:5]) # 反归一化转回像素坐标 x1 int((x_center - width / 2) * w) y1 int((y_center - height / 2) * h) x2 int((x_center width / 2) * w) y2 int((y_center height / 2) * h) # 绘制矩形与标签 color (0, 255, 0) if class_id 0 else (255, 0, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[class_id], (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite(output_path, img) # 批量执行 class_names [person, car, traffic_light] for img_path in Path(./yolo_dataset/train/images/).glob(*.jpg): lbl_path Path(./yolo_dataset/train/labels/) / f{img_path.stem}.txt out_path Path(./preview/) / fvis_{img_path.name} visualize_yolo_labels(str(img_path), str(lbl_path), class_names, str(out_path))运行后./preview/目录下将生成vis_001.jpg等文件直观显示每个 bbox 是否套住目标。若发现框偏左/偏上说明x_center计算时未加w/2若框过大覆盖背景说明cv2.boundingRect()输入了错误点集如包含了标注框外的噪点。此技巧比逐行检查.txt高效百倍是上线前必做的最后防线。本文还有配套的精品资源点击获取
返回列表