一套labelme资源文件:JSON转YOLO/MMSeg与语义分割预标注全解析
简介在深度学习和计算机视觉领域数据标注是关键环节Labelme是常用的图像标注工具。这套资源围绕Labelme集成了预训练模型、标注json文件及格式转换脚本面向AI标注工程师、语义分割与目标检测方向的研究者帮助他们减少重复标注、快速准备训练数据。资源包共175个文件压缩包大小737.5MB涵盖jpg/png图片样本、json标注数据、txt辅助文件、onnx预训练模型、py转换脚本以及yaml配置。其中的预训练模型可辅助自动识别物体json文件记录多边形标注信息py脚本支持将json转换为YOLO格式和MMSeg语义分割格式使得标注结果能直接用于检测与分割模型的训练。目前已有485人学习/下载这份资源提供了可直接使用的模型、标注样本与转换工具省去了从零整理数据格式的繁琐工作适合需要高效构建训练数据集的深度学习开发者。1. 一套 labelme 资源文件能少走多少弯路标注工具选来选去最后大多落在 labelme 上——多边形一点类别一填CtrlS 存出一个 JSON。但等真要拿去训练时JSON 这个格式谁都不要YOLO 要 txt 框或 txt 多边形MMSeg 要 8-bit 灰度 mask模型训练要 png 目录结构。于是大量时间耗在重新写格式转换上而不是在调模型。这套 labelme 资源文件解决的就是这件事里面带了可直接做语义分割 demo 的预训练模型也把 JSON 转 YOLO、JSON 转 MMSeg 的脚本和目录约定一次性补齐适合正在做数据标注、卡在格式转换这一步的算法工程师和研究生。看懂它等于把标注到训练的最后一公里铺完。2. 看懂 labelme 资源文件JSON 字段与预训练模型各自解决什么问题2.1 labelme 标注结果的数据结构每个字段决定转换脚本怎么写拿到任何一份 labelme 的 JSON先用文本编辑器或 Python 把结构打出来。常见字段是 imagePath、imageData、imageWidth、imageHeight 和 shapes。shapes 是个数组每个元素代表一个标注对象里面包含 label类别名、points多边形的顶点坐标列表、shape_typepolygon、rectangle、circle 等、group_id 和 flags。转换脚本的根基就是把 points 读出来按目标格式重算坐标。import json with open(example.json, r, encodingutf-8) as f: data json.load(f) print(图片路径:, data[imagePath]) print(图片尺寸:, data[imageWidth], data[imageHeight]) print(标注对象数:, len(data[shapes])) for shape in data[shapes]: print(类别:, shape[label], | 点数:, len(shape[points]), | 类型:, shape[shape_type])如果 shapes 里的 shape_type 是 polygonpoints 就是按顺序闭合的多边形点列如果是 rectanglepoints 只有两个点即左上角和右下角。转换脚本里最关键的是判断这个类型因为 YOLO 检测框可以直接由两个点算出而实例分割和语义分割必须把 polygon 完整投影到图上。2.2 预训练模型在资源里的角色语义分割 demo 的权重文件怎么用labelme 自带的 AI 标注辅助功能里能直接给图像做预标注的是基于 DeepLab 的语义分割模型。常见做法是拿 ResNet101 做骨干的预训练权重来跑推理标注完一张图后模型会自动把区域割出来存成 JSON 的 shapes你只需要改类别名或微调边缘点。这个预训练模型不是用来做最终业务的而是为了少点一轮鼠标。调用的链路一般是加载预训练权重 → 把图片缩放到模型输入尺寸通常是 512 或 321→ 前向推理得到每个像素的类别概率 → 把概率最大的类别映射回原图尺寸 → 用连通域分析把同类别区域转成 polygon 点集 → 写入 shapes。这里有个容易误用的点模型训练时用的类别数和你实际业务的类别数很可能不一致加载权重前要确认最后一层输出的通道数否则要么直接报 shape mismatch要么推理结果全是背景。2.3 资源文件在完整流程中的位置从标注到训练集的链路设计这套资源的价值不在单个脚本而在它把流程串起来了。完整链路是用 labelme 打开原图 → 手动标注或调用预训练模型做预标注 → 保存 JSON → 用转换脚本生成 YOLO 数据集图片 txt→ 同时生成 MMSeg 数据集图片 灰度 mask→ 分别喂给检测模型和分割模型训练。实际项目里检测任务和分割任务常常共享同一批标注。因为 polygon 本身就是最完整的标注形态把多边形外接矩形化就得到检测框把多边形填充成面就得到分割 mask。所以我的建议是标注阶段永远用 polygon不要为了偷懒标 rectangle——polygon 可以退化成 bbox但 bbox 补不了 polygon。数据集的目录结构也建议一次到位图片单独放一份原图转换脚本只负责生成各自的标签文件避免训练时还要做图片拷贝。3. labelme 的 JSON 转 YOLO 格式检测框与实例分割两套脚本一次跑通3.1 转 YOLO 检测框从 polygon 到 cx, cy, w, h 的归一化计算YOLO 系列的目标检测训练格式是每个图片对应一个同名 txt 文件每一行是class_id x_center y_center width height所有值都是相对图片宽高的比例范围 0~1。labelme 的 JSON 转成这个格式需要做四步读入 shapes、把类别名映射成数字 id、求多边形的最小外接矩形、归一化坐标。注意这里必须用 polygon 所有顶点的 min/max 来框不能简单用第一个点。import json import os def labelme_to_yolo_det(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] base_name os.path.splitext(os.path.basename(json_path))[0] lines [] for shape in data[shapes]: if shape[label] not in class_map: raise ValueError(f类别 {shape[label]} 不在 class_map 中) class_id class_map[shape[label]] points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 计算中心点和宽高并归一化 x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h box_w (x_max - x_min) / img_w box_h (y_max - y_min) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_path os.path.join(out_dir, base_name .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) print(f已生成: {out_path})归一化之后所有坐标都在 0~1 之间yolo 训练时不需要再关心原图尺寸。class_map 是整个项目的核心配置比如{person: 0, car: 1, tree: 2}这个映射必须和训练配置里的names顺序严格一致。如果项目中同时检测人和行人请在标注阶段就合并成同一个类别名否则转换脚本会直接报错。3.2 转 YOLOv8-seg 实例分割格式多边形坐标的归一化细节YOLOv8-seg 和 YOLOv5-seg 的训练标签在 bbox 基础上多了多边形坐标段一行格式是class_id x_center y_center width height x1 y1 x2 y2 ...其中 bbox 是外接矩形后面的点是归一化的多边形顶点。labelme 的 polygon 在这里能直接复用顺序保持 JSON 里的原始顺序即可。def labelme_to_yolo_seg(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] base_name os.path.splitext(os.path.basename(json_path))[0] lines [] for shape in data[shapes]: if shape[label] not in class_map: continue class_id class_map[shape[label]] points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h box_w (x_max - x_min) / img_w box_h (y_max - y_min) / img_h poly_str .join( f{p[0] / img_w:.6f} {p[1] / img_h:.6f} for p in points ) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f} {poly_str}) out_path os.path.join(out_dir, base_name .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) print(f已生成: {out_path})这里有个隐藏要求YOLOv8-seg 训练时会对多边形做采样顶点数太多会拖慢训练太少会丢失边缘细节。如果 JSON 里一个 polygon 有几百个点建议在转换前用 Douglas-Peucker 算法做抽稀保留 20~60 个点就够了。实操中最好在标注时就开启 labelme 的平滑和抽稀选项而不是转换后处理。3.3 批量转换与数据集划分一条命令处理整个文件夹一个项目的标注文件往往有几百上千个单文件转换脚本还不够需要加一层批量逻辑。常见做法是遍历目录下的所有 JSON配合一个类别映射文件最后按 8:1:1 或 9:0.5:0.5 划分 train/val/test同时拷贝图片。# 目录结构示例 raw_annotations/ img_001.json img_001.jpg img_002.json img_002.jpg out_yolo/ images/train/ images/val/ labels/train/ labels/val/批量脚本的思路是遍历 JSON 文件 → 提取同名的图片拷贝到 images 目录 → 调用 3.1 或 3.2 的转换函数生成 txt 到 labels 目录 → 用随机种子做划分。这里最值得注意的一点是划分时图片和标签必须同步移动否则训练时会出现图片没标签的报错。我习惯先把所有图片路径打乱随机种子再按比例切片标签路径用替换后缀的方式同步生成这样不会出现错位。4. labelme 的 JSON 转 mmseg 格式polygon 转灰度 mask 的目录与脚本4.1 mmseg 标准目录结构img_dir 与 ann_dir 的对应关系MMSegmentation 的数据集组织方式很固定一个数据集根目录下img_dir放原始图像ann_dir放标注的 mask 图。训练配置里通过datadict(traindict(img_dir..., ann_dir...))指定。mask 图要求是单通道 PNG每个像素值是该像素的类别索引背景通常是 0。注意不能想当然直接用 RGB 的彩色标签图mmseg 默认读的是灰度索引。data/ img_dir/ train/ img_001.png img_002.png val/ img_003.png ann_dir/ train/ img_001.png # 8-bit 灰度像素值为类别 id img_002.png val/ img_003.png脚本要做的事是把每个 labelme JSON 的图片放到img_dir/train把标注内容画成灰度图放到ann_dir/train文件名保持一致。val 集同理。这里有个容易翻车的点ann_dir 的图片尺寸必须和 img_dir 完全一致否则训练时 mmseg 的数据加载会报 size mismatch。转换前先检查 JSON 里的 imageWidth 和 imageHeight 是否和实际图片一致。4.2 用 OpenCV 把多边形填充为灰度 maskfillPoly 的正确姿势核心操作是把 JSON 的每个 shape 映射成灰度值然后调用cv2.fillPoly填到一张全零的单通道图上。实现逻辑简单但类别 id 的处理有个细节背景是 0第一个标注类别应该是 1不是 0否则背景和第一个类别会混在一起导致评估时的 mIoU 计算整体错乱。import json import cv2 import numpy as np import os def labelme_to_mmseg(json_path, img_dir_out, ann_dir_out, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_h data[imageHeight] img_w data[imageWidth] mask np.zeros((img_h, img_w), dtypenp.uint8) for shape in data[shapes]: label shape[label] if label not in class_map: continue # 背景从 0 开始标注类别从 1 开始 class_id class_map[label] points np.array(shape[points], dtypenp.int32).reshape(-1, 1, 2) cv2.fillPoly(mask, [points], colorclass_id) img_path data[imagePath] base_name os.path.splitext(os.path.basename(img_path))[0] cv2.imwrite(os.path.join(ann_dir_out, base_name .png), mask) print(fmask 已生成: {base_name}.png, 类别数: {len(set(mask.flatten()))})fillPoly的第二个参数要传入一个数组的数组即每个多边形是一个N×1×2的 int32 点列。有个常见的坑是直接传原始 points 浮点数组OpenCV 会报错或填充位置偏移。填充完成后要检查 mask 里像素值的集合确认没有出现标注类别以外的值尤其是当两个不同类别的多边形重叠时后画的会覆盖先画的这也是合理的优先级设定。4.3 类别顺序与调色板和训练配置里的 ignore_index 保持一致mmseg 训练配置里有ignore_index255意思是像素值为 255 的区域不参与 loss 计算。转换时如果想忽略某些标注不确定的区域可以在填充前把这些区域标成 255而不是 0。另外 mmseg 支持用调色板读取彩色标签但如果你用灰度图就不需要调色板文件数据集配置里保持默认即可。类别映射文件的顺序会影响模型的混淆矩阵输出。比如你的 JSON 里有 person、car、tree 三个类class_map 建议写成{person: 1, car: 2, tree: 3}与训练配置文件里的CLASSES列表一一对应CLASSES (background, person, car, tree)如果项目还有未知类别建议把未知类别统一归为背景在标注阶段就不要产生第四种 label。这是踩过坑的人都会同意的做法宁可牺牲细粒度信息也不要让模型学习一个不稳定的类。资源里如果有调色板文件通常是为了可视化用的训练时用不上。5. 转换链路里的 5 个高频坑现象、原因、解决5.1 imageData 丢失导致脚本直接崩现象json 加载成功但data[imageData]是空字符串而脚本依赖它解码图片运行报 KeyError 或 base64 解码错误。原因labelme 在保存时有时会因图片过大自动把 imageData 置空只保留 imagePath尤其是移动端或网页端标注的结果。解决转换前用cv2.imread(os.path.join(os.path.dirname(json_path), data[imagePath]))读原图不要依赖 imageData。此后再加一层判断如果图片不存在就跳过该 JSON 并输出日志避免批量转换中断。5.2 多边形点列没有按顺序闭合现象生成的 bbox 和 mask 完全变形mask 像被撕裂。原因多边形 points 的顺序在手工标注时偶尔会乱特别是在编辑过顶点之后labelme 的 undo 操作可能改变点序。解决转换前检查 polygon 的闭合性若首尾两点距离大于一个像素在填充前手动补齐闭合点或者对点列做凸包纠正。对于不规则凹多边形不能直接做凸包这种情况下只能人工重新标注这也是为什么做分割标注时建议尽量用简单的凹多边形。5.3 类别名混入大小写和空格现象转换过程中KeyError频发或生成后发现大量标签为空。原因不同标注人员习惯写 Person、person 、person 同一个类出现多个变体。解决加载 class_map 时对 label 做 strip 和 lower 归一化转换脚本统一为小写再去映射。更稳妥的做法是在标注完成后跑一个静态检查统计 JSON 里所有出现的 label 种类和 class_map 做集合差集找出那些看似同一个、实际拼写不同的类。5.4 YOLO 转出的 bbox 坐标越过图像边界现象训练时 YOLO 报错 all pixels are masked 或 loss 变成 NaN。原因标注框有一部分画在图片外面归一化后 x_center 或 w 大于 1模型在 anchor 匹配时出现非法值。解决转换脚本里加边界钳制把归一化后的 x_center、y_center 限制在 0~1 之间w 和 h 也做同样处理。同时打印出哪些框被修改了方便反查原图。这种问题在边缘样本多的工业数据里尤其常见宁可早发现早处理也不要等训练崩溃再来查。5.5 预训练模型推理结果全黑或全零现象labelme 里调预标注跑完生成的 mask 是全黑或者所有像素都被预测为同一类。原因大概率是输入图像的 channel 顺序或归一化方式与训练权重不一致。ResNet101 预训练权重一般要求 BGR 输入并做均值方差归一化如果直接传 RGB 或不做归一化输出概率分布会退化。解决检查预处理脚本的 mean/std 参数常见的是mean[0.485, 0.456, 0.406]std[0.229, 0.224, 0.225]。如果是自己训练过的模型还要确认 resize 的插值方式是否和训练时一致。这个坑最折磨人的地方在于它不报错只给你一个错误的结果建议首次使用预标注前拿一张已知的地面真值图做前向测试确认输出合理再批量化。6. 转换结果的验证技巧可视化对比与训练前的最后一道把关转换完成不等于数据能用我在每个项目里都会强制跑一遍可视化验证。方法是把原图、YOLO bbox、mmseg mask 叠在一张图上人眼扫一遍就能发现大部分错位问题。验证脚本的思路是读原图 → 遍历 YOLO txt 画框 → 读取 mmseg mask 并映射调色板 → 左右拼接保存成一张大图。批量跑完后抽样 20 张能覆盖标注边界和复杂场景的检查需求。import cv2 import numpy as np # 画 YOLO bbox img cv2.imread(val_001.jpg) h, w img.shape[:2] with open(val_001.txt, r) as f: for line in f.readlines(): cls_id, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_bbox.jpg, img) # 检查 mmseg mask 是否与原图对齐 mask cv2.imread(ann_dir/val/val_001.png, cv2.IMREAD_UNCHANGED) if mask.shape[:2] ! img.shape[:2]: print(尺寸不一致:, mask.shape, img.shape)这套验证还有一个用途找出标注时漏标的目标。如果 YOLO 框明显少框住某个物体说明标注阶段有遗漏这时回到 labelme 补标比训练完再发现要便宜得多。验证通过后再用yolo export或 mmseg 的小模型跑 5 个 epoch 的冒烟训练确认数据加载、类别数、标签索引全部对齐再投入完整训练。另外提一个我自己的血泪经验无论脚本写得多顺手都要保留原始 JSON 的备份不要做完转换就把标注删掉。YOLO 格式可以再生成JSON 是多边形原始信息一旦丢了想加一个类别或改标注规则就只能重新画图——这个后悔药没有卖的。数据工程这种事慢就是快验证脚本多写十分钟训练翻车少熬十小时。希望帮到你。本文还有配套的精品资源点击获取