news 2026/9/29 22:27:14

LabelMe标注转YOLO分割数据集:完整流程与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LabelMe标注转YOLO分割数据集:完整流程与避坑指南

做分割数据集这件事,最折磨人的往往不是模型调参,而是数据从“画框”到“能训练”之间那段没人替你走的弯路。LabelMe 画完一堆多边形 JSON 之后,如果你要用 YOLO 系列做实例分割或语义分割,就会发现格式完全对不上:LabelMe 存的是像素坐标和逐点轮廓,YOLO 要的是归一化坐标加类别 ID 的 txt。这就是 labelme2yolo 这类工具存在的意义。这篇文章我会把从 LabelMe 标注到 YOLO 分割数据集的完整流程拆开讲,包括脚本怎么写、目录怎么组织、标注规范怎么定,并用息肉分割数据集这类小目标、多目标场景做例子,把转换和验证的每个坑都指出来,适合正在做语义分割数据集制作、又不想在格式转换上反复折腾的读者。

1. 为什么是 LabelMe,以及 YOLO 分割格式到底长什么样

1.1 先搞清楚你要做的是“实例分割”还是“语义分割”

很多人在标注之前没想清楚自己的任务类型,导致转换脚本写了一半才发现思路错了。YOLO 分割格式本身是“实例级”的:一个目标一个轮廓,同一类别的两个相邻目标也要分成两行来写。这跟传统语义分割里“每个像素一个类别号”的 PNG 掩膜不一样,也跟 COCO 那种 polygons + annotations 的结构不一样。LabelMe 输出的 JSON 天然适合做实例级标注,因为每个 polygon 就是独立的,这也决定了 labelme2yolo 的转换逻辑:把每个 polygon 变成一行 txt,而不是把所有同类目标合并成一个掩膜。

如果你的最终目标是纯语义分割,只有类别标签没有实例区分,我建议也别在标注阶段合并,保留实例信息带来的好处是:以后想切到实例分割、想统计目标数量、想按面积过滤小目标,都有退路。只要在训练或评估时把同类的 mask 叠加起来做语义指标就行,不用因为格式选型把自己困死。

1.2 YOLO 分割 txt 的数据结构拆解

YOLO 分割标签的每一行长这样:

0 0.4821 0.6331 0.4512 0.6722 0.4434 ...

第一个数字是类别 ID,后面的坐标是归一化的多边形顶点。归一化公式很简单:用像素坐标除以图像的宽和高。LabelMe 的 JSON 里记录了 imageWidth 和 imageHeight,所以转换时直接用这两个值做除法,这一步看着简单,却是出错率最高的地方。我见过不少人写脚本时把 x 除以了 height,把 y 除以了 width,结果可视化验证时所有轮廓都扭曲得不成样子。

值得注意的细节是:像素坐标除以图像尺寸后会产生浮点数,不要四舍五入到太少的位数。建议保留 6 位小数,否则在长焦大图上,多边形边界会出现肉眼可见的锯齿。YOLO 对坐标的精度要求不苛刻,但 0.000001 级别的小数点误差在多边形顶点累计起来,能影响几个像素的轮廓贴合度,尤其是息肉分割数据集这种边界本身就细微的场景,更得注意。

2. labelme2yolo 工具选型与目录结构设计

2.1 现成工具与自己写脚本怎么选

目前社区里流传的 labelme2yolo 工具大致有两类:一类是封装好的命令行工具,安装后直接按目录递归转换;另一类是依托 LabelMe 官方提供的 labelme 包,自己写十行脚本读取 JSON,然后解析 shapes 生成 txt。我的建议是:第一次做数据集、对脚本不熟,可以用现成工具快速出结果;但做严肃项目,务必吃透自己写的转换脚本,因为这些工具大多按固定的“LabelMe 默认导出结构”工作,一旦你的 JSON 里有 flags 标注、有多个同名 label、或者用了矩形框工具而不是多边形工具,就可能静默出错。

我现在的工作流是直接写脚本,核心依赖只有一个 labelme 包。它负责读取 LabelMe 的 JSON 文件,我们自己处理坐标和类别映射。这样做的好处是中间每个环节都能打印日志,一旦后面训练出问题,可以顺着原始 JSON 一路排查,而不是在黑盒工具前猜测数据哪里被改坏了。

2.2 数据目录结构怎么设计才不容易乱

无论用工具还是自写脚本,我强烈建议先按下面的结构组织数据:

datasets/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt ├── dataset.yaml

images 放原图,labels 放转换后的 txt,一一对应。脚本只负责“把 JSON 转成 txt”,不负责划分训练集验证集,划分在转换前用文件列表完成。这样做的理由很朴实:每次训练前想重新划分、增加验证集比例,只需要修改文件列表重跑一次转换,完全不碰原始标注,省去了移动几百个文件的痛苦。

classes.txt 里每行一个类名,顺序就是类别 ID。这个顺序定了就不能轻易改,否则之前转换的 txt 里的类别 ID 会全部错位。转换脚本里读 classes 的顺序必须和你训练配置里的 class names 保持一致,这是多类别数据集中最常见的翻车现场,我放在后面“问题排查”里细说。

3. 核心转换逻辑:从 LabelMe JSON 到 YOLO 分割 txt

3.1 解析 JSON 时重点检查哪些字段

每个 LabelMe JSON 文件至少包含四个关键字段:imagePath、imageHeight、imageWidth、shapes。shapes 是个列表,每个元素是一个目标,里面有 label、points、shape_type。转换时从 shapes 逐个取目标,label 用来查类别 ID,points 就是要输出的多边形顶点。

在实际转换前,我会加几道校验:

  • shape_type 是不是 polygon。如果你在 LabelMe 里不小心用了 rectangle 或者 circle,后续 YOLO 不认,最好在转换阶段就报出来,别让它静默变成诡异的预测。
  • points 数量是否小于 3。少于三个点无法构成有效多边形,转出来后训练时会报错,应该提前过滤并人工检查。
  • label 是否都在已定义的 classes 里。有一个类名拼写不一致,比如 Polyps 和 polyps,就会被当成两个类别,导致最终类别数凭空多一倍。

这些校验写进脚本里,每个文件出现问题时打印文件名和原因,你就会知道哪些标注需要回头修,而不是等到训练 Loss 异常了再满世界找原因。

3.2 一个精简但完整的转换脚本参考

下面这段脚本做了两件事:遍历一个 JSON 文件夹,把每个 JSON 转成同名 txt;同时维护一份按 label 分组的统计数据,方便转换完核对各类别数量。实现用的是最小依赖,除了 labelme 负责读 JSON,其他都用标准库:

import json import os from collections import defaultdict from pathlib import Path import labelme CLASS_NAMES = ["background", "polyp"] # 顺序决定类别 ID,务必与训练配置一致 def process_json(json_path, out_dir): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) if "shapes" not in data: return [], None img_w = int(data["imageWidth"]) img_h = int(data["imageHeight"]) if img_w == 0 or img_h == 0: return [], None lines = [] stats = defaultdict(int) for shape in data["shapes"]: label = shape["label"] if label not in CLASS_NAMES: continue if shape["shape_type"] != "polygon": print(f"[warn] {json_path.name}: shape_type={shape['shape_type']} skipped: {label}") continue points = shape["points"] if len(points) < 3: print(f"[warn] {json_path.name}: too few points: {label}") continue cls_id = CLASS_NAMES.index(label) normalized = [] for x, y in points: nx = max(0.0, min(1.0, x / img_w)) ny = max(0.0, min(1.0, y / img_h)) normalized.append(f"{nx:.6f}") normalized.append(f"{ny:.6f}") lines.append(f"{cls_id} " + " ".join(normalized)) stats[label] += 1 return lines, stats def convert_dataset(json_root, label_root): os.makedirs(label_root, exist_ok=True) total_stats = defaultdict(int) count = 0 for json_path in Path(json_root).rglob("*.json"): lines, stats = process_json(json_path, label_root) if lines is None: continue rel_path = os.path.relpath(json_path, json_root) txt_path = os.path.join(label_root, os.path.splitext(rel_path)[0] + ".txt") os.makedirs(os.path.dirname(txt_path), exist_ok=True) with open(txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) for k, v in stats.items(): total_stats[k] += v count += 1 print(f"converted {count} files") print("class stats:", dict(total_stats)) if __name__ == "__main__": convert_dataset("json_dir", "labels_dir")

这段脚本不处理嵌套子目录的问题——用 Path.rglob 已经能递归找到所有 JSON,输出时用相对路径保持目录结构一致。你要是有大量文件,建议加上 tqdm 进度条,否则转几千个 JSON 时你会怀疑脚本是不是卡死了。

3.3 转换前预处理:过滤小目标和重叠多边形

数据集制作的真正加分项是转换前预处理。如果你做的是息肉分割数据集,内镜图像里经常出现几十个像素的小息肉,或者带反光的伪目标。多边形面积过小,在 YOLO 分割训练里会被 downsampling 吞掉。我一般会在转换脚本里顺手算一下多边形面积,过滤掉面积小于图像面积 0.05% 的目标,单独输出到一个 suspicious.txt 里供人工复查。

顺带提一句重叠多边形:LabelMe 允许多边形互相覆盖,但在 YOLO 分割里,同一像素只属于一个实例的严格约束会被打破。我的原则是:重叠面积超过小目标面积 30% 的目标,要么去标注软件里把边界裁开,要么在训练时用 mask 后处理手段处理,不要让模型去学“被覆盖的类别还是可见的”。这对内镜息肉场景尤其关键,因为息肉的边缘经常和反光斑块交织,标注图看起来“差不多”,转换成 YOLO 后边界可能就打架了。

4. 标注规范:以息肉分割数据集为例

4.1 医学图像里“边界清晰”和“边界可学”是两回事

热搜里挂着“息肉分割数据集”,我就拿它当一整节来讲。医学分割目标边界往往不锐利,内镜图像中息肉的边缘和正常黏膜存在过渡带,模型要学到的是那种“软边界感”。因此标注规范里最好约定:紧贴可见边缘画点,不要自己臆想延伸边界,更不要为了追求圆滑把点画到周围黏膜上。我曾经让两位标注员标同一批息肉图像,边界 IoU 只有 0.72,原因就是一个人喜欢把边界往外扩两三像素,另一个人贴着画。后来我们统一规则:只标碘染色或图像上肉眼清晰可见的范围,不标模糊过渡带。

另一个息肉数据集的常见坑是大小极端不均衡。一个图像里可能只有一个 10x10 像素的小隆起,另一个图像里的息肉占半张图。YOLO 对尺度变化敏感,如果按原始尺寸直接训练,小目标召回率会非常难看。我的经验是:标注阶段不去重采样图像,但转换后期一定要按目标面积做统计分析,该裁剪的裁剪,该过采样的过采样,别把尺度难题全部丢给模型。

4.2 LabelMe 标注操作的关键动作

打开 LabelMe 创建多边形时,有几个容易被忽略的交互细节:

  • 多边形点不要过密,也不要过疏。过密会导致每个多边形数百个顶点,txt 行非常长,训练时读取慢;过疏会让边界明显失真。我的习惯是直线部分 1-2 个点,曲率大的地方 4-6 个点,一个中等畸形目标 20 个点以内搞定。
  • 使用“Create Polygons”模式后,别忘了保存时确认每个 shape 的 label 一致。LabelMe 的编辑界面可以把相近 label 合并,但更常见的情况是手滑写了个“polyp ”带空格,这类问题只在统计类别数量时才会暴露。
  • 一批图像标注完成,先手动打开三个以上 JSON 看内部结构,确认 requires label 布局正常。不要相信眼睛,要相信 json.load 出来的数据结构。标注工具渲染看着正常不代表 JSON 里的 points 顺序和 shape_type 都正确。

4.3 多类别数据集的命名与 ID 管理

如果数据集不只有息肉,还有腺瘤、炎症、正常组织等类别,classes 的顺序就得更严谨。我的做法是:

0 background 1 polyp 2 adenoma 3 normal 4 other

建议把 background 留出来当 0 号类别。YOLO 分割训练的默认配置经常把 0 号当作背景,虽然不强制,但这种约定能少很多脑子里的转换负担。而且后续做数据增强,比如 Mosaic,背景类别单独处理也方便。

还有个小技巧:classes.txt 文件跟 dataset.yaml 里 names 字段保持同一份来源,不要手打两遍。你可以让 dataset.yaml 自动读取 classes.txt,这样所有地方的类别 ID 都是同一个索引,不可能因为手误错位。

5. 实操验证:转换完成后如何检查和拍错

5.1 可视化是最快的校验方式

转换完成后,先用可视化脚本把原始图像和转换后的多边形叠在一起看。最简单的方法是用 OpenCV 画线连接 txt 里的坐标。我写了个快速检查脚本:

import cv2 import numpy as np from pathlib import Path CLASS_COLORS = { 0: (0, 0, 255), # red for background 1: (0, 255, 0), # green for polyp } def draw_yolo_label(image_path, txt_path, img_w, img_h): img = cv2.imread(str(image_path)) with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() cls = int(parts[0]) pts = np.array([[float(parts[i]) * img_w, float(parts[i + 1]) * img_h] for i in range(1, len(parts), 2)], dtype=np.int32) cv2.polylines(img, [pts], isClosed=True, color=CLASS_COLORS[cls], thickness=2) cv2.putText(img, str(cls), tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, CLASS_COLORS[cls], 2) return img # 调试时单张查看 img_w, img_h = 640, 480 out = draw_yolo_label("images/001.jpg", "labels/001.txt", img_w, img_h) cv2.imwrite("check/001.jpg", cv2.resize(out, (img_w * 2, img_h * 2)))

我不是说直接用 YOLO 官方自带的 visualize 不行,而是这个手写脚本能精确控制拿着哪张图看、看哪个区域,尤其是在多类别场景下,可以单独指定类别渲染,快速定位某个类的轮廓有没有画歪。可视化这一步建议走完整个 val 集或随机抽样 200 张,别只看十几张。只有抽检量到一定规模,你才会发现那种“某些图像的 bounds 越界了”这类低频糙问题。

5.2 用 Ultralytics 做一次快速训练验证

转换脚本再稳,最后还是要在训练里见真章。用 Ultralytics YOLOv8-seg 或 YOLO11-seg 跑一个 10 个 epoch 的小型验证训练,是最直接的健康检查。

yolo segment train model=yolov8s-seg.pt data=dataset.yaml epochs=10 imgsz=640 batch=8

看到 box_loss 和 seg_loss 都正常下降,再把 val 集的预测可视化翻出来看,重点看预测 mask 是不是能完整贴合目标轮廓,而不是碎成一堆小斑块。如果 seg_loss 掉得很慢,优先回去查是不是有多边形压到了图像边界,或者归一化坐标算错了。

这种“10 个 epoch 验证”和我正式训练用的并不是同一套,但我几乎是每次换数据集都跑一遍。因为数据格式问题通常在前几百个 iteration 就能暴露:读取异常、类别数不匹配、越界坐标等,都会体现在 loss 的怪异抖动里。

6. 常见问题与排查技巧实录

6.1 类别错位

症状:训练时声称的类别数和混淆矩阵里看到的类别数对不上;模型输出的 mask 明显画在了错误的位置。

排查:首先确认 dataset.yaml 里的 names 顺序,是不是和 classes.txt 一一对应。很多人会忽略一个细节:classes.txt 换行符在 Windows 上可能是 \r\n,读取时把 label 末尾带了 \r,导致 “polyp” 和 “polyp\r” 被当成两个类。用 Python 读取时记得 strip 一下,这是真实发生过的事故。

6.2 坐标越界提示

症状:训练突然报 “Line ... has out-of-bounds coordinate”。

原因:多边形顶点像素坐标超出图像宽高,常见于标注工具里拖动点出了画面边界,归一化后得到大于 1.0 的值。我的转换脚本里用 clamp 限制到 [0,1],但这只是治标。治本是:在 LabelMe 标注时把视图缩放调到合适比例,别让边缘目标的关键节点脱离画面;转换时单独把被 clamp 的坐标打日志,人工检查这些目标的边界是不是真的该裁切。

6.3 JSON 里混入了非 polygon 形状

LabelMe 默认可以画矩形、圆、直线。YOLO 分割只认多边形,所以转换时把非 polygon 全部跳过看似省事,但如果一张图里 30% 的目标都是矩形框,你会得到一堆空洞洞的 txt。更合理的做法是:转换前用脚本扫描所有 JSON 的 shape_type 分布,如果存在非 polygon 且面积占比大,立刻回到 LabelMe 把对应目标重画成多边形,而不是靠转换脚本硬吞。

6.4 同名 label 被合并,导致实例丢失

症状:两个相邻息肉同类别,转换后 txt 里只有一个多边形。

原因:看是不是形状点实际上是一条线还是两个点序列。LabelMe 里如果一个目标被分割成两个 polygon,但名字相同,转换时如果按名称聚合,就会把两个多边形合并成一个。我的约定是:LabelMe 里一个目标就画一个 polygon,相邻区域如果必须切开,用不同 label 后缀,例如 polyp_left、polyp_right,再在 classes.txt 里映射同一个类别 ID。这样既保留实例边界,又不会触发同名合并。

7. 一点实用打磨技巧

做分割数据集没有一次成型这么一说。跑完第一个 10 epoch 验证,我一般会再抽 50 张最难的目标图,放大看标注的边界偏移,把明显不贴合的部分重新标。这套“标注 -> 转换 -> 验证训练 -> 人工复核”的流程跑两遍以后,数据质量基本能撑到正式训练。

另外一个小技巧是:保留转换前的 JSON 文件,永远别只留 txt。txt 是“渲染后的结果”,改起来费劲;JSON 是“源文件”,用 LabelMe 打开就能修改。团队协作时可以让标注员只碰 JSON,脚本负责同步生成 labels,这样出问题时能快速定位是标注问题还是转换问题。我自己在项目目录里永远放着 json_src 和 labels 两个文件夹,只有标签文件不进 Git,原始 JSON 必须进版本管理。

如果你正在做息肉分割数据集这类医学图像项目,我还建议在标注前就把敏感数据处理干净,确认数据的来源和授权范围,标注和实验尽量在受控环境里进行。这类数据集的边界质量直接决定模型在真实场景上的表现,多花一周做数据审查和标注规范,比训练时反复试错要划算得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 22:26:22

考研英语 翻译 被动转主动

英语翻译 - 被动转主动 省略被字 During this transfer, traditional historical methods were augmented by additional methodologies designed to interpret the new forms of evidence in the historical study.在这种转变过程中&#xff0c;传统的历史研究方法增加了新的方…

作者头像 李华
网站建设 2026/9/29 22:26:22

从“做PPT的人”到“审PPT的人”:aigcbiye的AI PPT让我换了一种活法

aigcbiye官网 微信公众号搜一搜 aigcbiye 你有没有算过一笔账&#xff1a;从开题到答辩&#xff0c;你到底花了多少时间在PPT上&#xff1f; 我说的不是构思内容的时间&#xff0c;而是调字号、对齐全、找图标、改配色的时间。是那种明明脑子里装着清晰的逻辑&#xff0c;却被…

作者头像 李华
网站建设 2026/9/29 22:23:42

2026 国内 AI 论文辅助工具综合实力排行榜

测评维度&#xff1a;功能完整度、国内高校适配度、AIGC 检测能力、文档安全、上手难度。本次榜单聚焦本科、硕士毕业论文全流程场景&#xff0c;区分一站式平台与专项工具&#xff0c;方便毕业生按需挑选。榜单总表排名工具名称综合得分核心定位优势短板最适合人群1PaperXie9.…

作者头像 李华