简介:这是一款面向深度学习初学者与数据工程师的轻量级图像标注工具,专为Mask R-CNN等实例分割模型的数据集制作而设计,支持矩形框与多边形标注,兼顾VIA和LabelMe的交互逻辑,同时提供完整C#源码,便于二次开发与教学演示。资源包共65个文件,含20个核心C#源码(如Form1.cs、DrawShapes.cs、SqLiteHelper.cs)、3个可执行程序(exe)及配套配置(config)、图标(ico)、资源文件(resx/resources)和数据库(db),结构清晰,模块职责分明,附带《使用说明.docx》与Visual Studio解决方案(sln),开箱即用。目前已有2340人学习下载,适合需快速构建高质量分割样本、理解标注工具底层实现或开展课程实验的开发者与学生。
1. 图像标注工具包:专为 Mask R-CNN 样本制作而生的轻量级落地方案
你手头有一批工业缺陷图、医疗切片或遥感影像,想训一个能抠出像素级轮廓的 Mask R-CNN 模型,但卡在第一步——标注。不是标框,是标掩码(mask);不是标一次,是标成千上万张带语义分割标签的图;不是用 Photoshop 手动画,而是要导出 COCO 或 VOC 格式、能直接喂进 detectron2/mmdetection 的训练 pipeline。这时候,VIA 和 LabelMe 这类通用工具就暴露短板了:VIA 不原生支持 mask 导出为 polygon 坐标序列,LabelMe 默认导出 JSON 结构松散、字段命名不统一,和 detectron2 的COCODataset加载器对不上号。本资源不是另一个 GUI 工具,而是一套经过某实验室真实项目验证的「标注-清洗-转换」三件套:含定制化 LabelMe 启动脚本、VIA 配置模板、以及核心的mask_to_coco.py/voc_to_yolo_mask.py转换器。它不替代标注动作本身,但把标注后 70% 的脏活——格式校验、坐标归一化、类别映射、无效 mask 过滤、小目标剔除——全写死了。适合正在跑通第一个 Mask R-CNN pipeline 的工程师,也适合带学生做课程设计的某高校导师。如果你已经标注了 300 张图却还在手动改 JSON 字段,这篇就是你的后悔药。
2. 为什么选 VIA + LabelMe 组合:不是图省事,是图可控性与可审计性
Mask R-CNN 对样本质量极其敏感:一个错位的多边形顶点、一个漏标的 instance、一个坐标越界的 mask,都可能让 loss 突然爆炸或 AP 在 val 上掉 15 个点。因此,标注阶段不能只看“有没有”,更要看“能不能被模型正确读取”。VIA 和 LabelMe 并非最优 UI,但它们在三个关键维度上胜过商业工具:一是输出结构完全透明(JSON 纯文本,无二进制黑匣子),二是支持离线部署(不依赖云服务或账号体系),三是配置项可编程控制(比如 VIA 的region_shape_attributes字段可强制限定为polygon,LabelMe 的--nodata参数可跳过空图)。本资源正是基于这个前提构建:所有转换脚本的输入假设,都严格对齐 VIA 1.0.19+ 和 LabelMe 5.4.1 的原始输出规范,不加任何中间层抽象。这意味着你不需要学新工具,只需微调已有流程——这比推倒重来、迁移到某个“更智能”的标注平台,风险更低、复现更快。
2.1 VIA 标注实操:从启动到导出 JSON 的四步闭环
VIA 的默认行为对 Mask R-CNN 不友好:它允许用户画矩形、椭圆、点等多种 shape,但 Mask R-CNN 只认polygon;它默认把 region 存在regions数组里,但没强制要求shape_attributes必须含all_points_x/all_points_y;它导出的 JSON 顶层是metadata,而 detectron2 期望的是images+annotations结构。本资源提供via_config.json模板,必须在启动 VIA 前加载:
{ "project": { "name": "mask_rcnn_sample", "file": {"image": {"enable": true, "dir": "./images/"}}, "region": {"shape": ["polygon"], "attribute": ["category"]} } }提示:
"region": {"shape": ["polygon"]}是硬性约束——VIA 会禁用所有非 polygon 的绘图按钮,从源头杜绝误操作。"attribute": ["category"]则强制每个 polygon 必须填类别名(如"scratch"、"crack"),避免后期靠索引猜类别。
启动命令(Linux/macOS):
python3 via-standalone.html --port=8001 --config=via_config.json注意:via-standalone.html是 VIA 官方单文件版,本资源已打包对应版本(v3.1.0),无需 npm install。标注完成后,点击右上角「Export Project」→ 选择「Export as JSON (VIA project format)」,保存为via_export.json。此文件即为后续转换的唯一输入源。
2.2 LabelMe 标注实操:规避 JSON 字段漂移的启动参数
LabelMe 的坑在于版本迭代导致 JSON schema 变动:LabelMe 4.x 的shapes里points是[x,y]对数组,5.x 改为[[x,y],[x,y]];imagePath在 4.x 是相对路径,5.x 默认绝对路径。本资源适配 LabelMe 5.4.1(Python 3.8+),启动时必须加两个参数:
labelme --nodata --output-format polygon --labels labels.txt--nodata:跳过图像数据嵌入(即不把 base64 图存进 JSON),减小文件体积,避免imageData字段干扰解析;--output-format polygon:强制所有标注 shape 输出为polygon类型(即使你画了矩形,也会转成 4 点 polygon),保证结构一致性;--labels labels.txt:指定类别文件,内容为每行一个类别名(如defect,background),确保shape["label"]字段值严格来自该列表,防止拼写错误。
标注完后,LabelMe 自动生成xxx.json,其关键结构如下:
{ "version": "5.4.1", "flags": {}, "shapes": [ { "label": "scratch", "points": [[120.5, 80.2], [150.1, 80.3], [150.0, 110.8], [120.6, 110.7]], "shape_type": "polygon" } ], "imagePath": "IMG_001.jpg", "imageHeight": 1024, "imageWidth": 1280 }注意:
points必须是浮点数(非整数),这是 detectron2 多边形渲染的隐式要求;imageHeight/imageWidth必须存在且准确,否则坐标归一化会出错。本资源的labelme_to_coco.py脚本会校验这两项,缺失则报错退出,不静默修复。
2.3 标注质量预检:三行命令筛出 90% 的低质样本
标注完成不等于可用。我们曾在一个 PCB 缺陷项目中发现:12% 的标注图存在points少于 3 个顶点(无法构成 polygon)、8% 的imageWidth/imageHeight为 0、5% 的label值不在labels.txt中。这些图若直接进训练,会在dataloader的__getitem__阶段随机 crash,极难定位。本资源提供precheck.py,运行后生成quality_report.csv:
python3 precheck.py \ --input_dir ./labelme_annos/ \ --labels_file labels.txt \ --min_vertices 3 \ --min_dim 64参数说明:
--input_dir:LabelMe 生成的.json文件所在目录;--labels_file:与labelme --labels指定的同一文件,用于校验 label 合法性;--min_vertices 3:polygon 至少含 3 个顶点(三角形是最小合法 polygon);--min_dim 64:图像宽高均不得小于 64 像素(过滤过小缩略图)。
脚本逻辑:遍历每个 JSON,提取shapes→ 检查len(points) >= 3→ 检查imageWidth > 64 and imageHeight > 64→ 检查label in labels_set。任一失败则记录到 CSV,并输出具体文件名与错误类型。这不是可选项,是必跑步骤——我带过的三个课程设计组,全部在第一次 run train 之前漏了这步,结果 debug 时间远超标注时间。
3. 核心转换器详解:从原始 JSON 到 detectron2 可加载的 COCO 格式
Mask R-CNN 的训练入口(如 detectron2 的DefaultTrainer)只认标准 COCO 格式:一个instances_train2017.json文件,含images、annotations、categories三大顶级 key。VIA 和 LabelMe 的输出与此结构相去甚远,需做字段映射、坐标变换、ID 重编。本资源的mask_to_coco.py不是简单 copy-paste 的脚本,而是按 detectron2 v0.6+ 的COCODataset加载逻辑反向设计的——它生成的 JSON,能直接作为register_coco_instances("my_dataset", {}, "path/to/instances_train2017.json", "path/to/images/")的输入。
3.1 VIA JSON → COCO:via_to_coco.py的字段映射逻辑
VIA 导出的via_export.json是扁平结构:顶层metadata下挂file_list和file_attributes,region 数据分散在各file_list[i]["regions"]中。via_to_coco.py的核心任务是重组为 COCO 的三层嵌套:
images数组:每个元素含id(自增 int)、file_name(相对路径)、width、height;annotations数组:每个元素含id(自增 int)、image_id(对应images[i]["id"])、category_id(查表得)、segmentation(polygon 坐标转[x1,y1,x2,y2,...])、area(多边形面积,用 shoelace 公式计算)、bbox(包围盒[x,y,width,height]);categories数组:每个元素含id(从 1 开始)、name(类别名)、supercategory(固定为"none")。
执行命令:
python3 via_to_coco.py \ --via_json via_export.json \ --image_dir ./images/ \ --output_json coco_via_train.json \ --categories "defect,scratch,crack"参数说明:
--via_json:VIA 导出的原始 JSON;--image_dir:图像所在目录,用于提取width/height(若 JSON 中缺失);--output_json:输出 COCO 格式文件路径;--categories:逗号分隔的类别字符串,顺序即为category_id(defect=1,scratch=2,crack=3)。
关键细节:
segmentation字段必须是 float 类型的一维数组(非嵌套 list),且长度为偶数;area必须为正数(脚本会过滤 area≤0 的 polygon);bbox的x/y是左上角坐标,非中心点。这些在 detectron2 的COCOInstanceNewBaselineDatasetMapper中有硬性校验,本脚本提前满足。
3.2 LabelMe JSON → COCO:labelme_to_coco.py的坐标归一化处理
LabelMe 的points是像素坐标,而 detectron2 的segmentation字段虽也是像素坐标,但其bbox计算逻辑依赖imageWidth/imageHeight的准确性。labelme_to_coco.py的核心增强在于:它不直接复制points,而是先做一次「坐标归一化检查」——计算每个点是否在[0, imageWidth] × [0, imageHeight]范围内,越界则 clip 并 warn:
# 伪代码逻辑(实际在 labelme_to_coco.py 中) for point in shape["points"]: x, y = point x = np.clip(x, 0, image_width) y = np.clip(y, 0, image_height) if abs(x - point[0]) > 1e-3 or abs(y - point[1]) > 1e-3: warnings.warn(f"Clipped point {point} to ({x:.1f},{y:.1f}) in {json_path}")执行命令:
python3 labelme_to_coco.py \ --input_dir ./labelme_annos/ \ --output_json coco_labelme_train.json \ --labels_file labels.txt \ --image_ext ".jpg"参数说明:
--input_dir:LabelMe JSON 目录;--output_json:输出 COCO JSON;--labels_file:类别文件,用于生成categories;--image_ext:图像扩展名(.jpg或.png),用于匹配 JSON 中的imagePath。
血泪经验:某次实验因
imagePath写成IMG_001.JPG(大写 JPG)而image_ext设为.jpg,脚本找不到图,imageWidth/imageHeight取 0,后续所有 bbox 计算失效。从此我养成了习惯:运行前先ls ./labelme_annos/*.json | head -3 | xargs -I{} jq -r '.imagePath' {}看实际后缀。
3.3 COCO 格式验证:用 detectron2 自带工具做最终确认
生成coco_train.json后,别急着 train。先用 detectron2 的COCODataset加载器做一次「冷启动验证」,确认无字段缺失、ID 错位、坐标越界:
from detectron2.data import DatasetCatalog, MetadataCatalog from detectron2.data.datasets.coco import load_coco_json # 注册数据集(仅验证,不训练) DatasetCatalog.register( "my_coco_test", lambda: load_coco_json("coco_labelme_train.json", "path/to/images/", "my_coco_test") ) MetadataCatalog.get("my_coco_test").set(thing_classes=["defect", "scratch", "crack"]) # 尝试加载第一条数据 dataset_dicts = DatasetCatalog.get("my_coco_test") print("Loaded", len(dataset_dicts), "images") print("First image:", dataset_dicts[0]["file_name"]) print("First annotation bbox:", dataset_dicts[0]["annotations"][0]["bbox"])若成功打印,说明 JSON 结构 100% 合规;若报KeyError: 'image_id'或IndexError,说明via_to_coco.py或labelme_to_coco.py的 ID 映射有 bug,需回查脚本中的image_id生成逻辑(应为i+1,非i)。
4. 避坑指南:标注与转换阶段最常翻车的五个现场
这些坑,我在三个不同项目里至少踩过两次。不是文档没写,是它们藏得太深,往往在训练跑了一半才暴露,debug 成本极高。以下按「现象 → 原因 → 解决」列出,每一条都对应真实报错日志。
4.1 现象:RuntimeError: Expected all tensors to be on the same device
原因:VIA 导出的 JSON 中,某些 region 的shape_attributes缺失all_points_x/all_points_y字段(比如用户画了矩形但未切换为 polygon),导致via_to_coco.py生成的segmentation为[],后续torch.tensor([])默认在 CPU,而模型在 GPU,device mismatch。
解决:在via_to_coco.py的parse_region()函数开头加强制校验:
if "all_points_x" not in region["shape_attributes"] or "all_points_y" not in region["shape_attributes"]: print(f"SKIP region without polygon points in {file_name}") continue4.2 现象:ValueError: not enough values to unpack (expected 4, got 0)
原因:LabelMe 的points数组为空([]),常见于用户点了「Create Polygons」但未画任何点就按 Ctrl+Enter。labelme_to_coco.py将其转为segmentation=[],detectron2 的PolygonMasks初始化时报错。
解决:在labelme_to_coco.py的process_shape()函数中,添加:
if len(shape["points"]) < 3: print(f"SKIP shape with {len(shape['points'])} points in {json_path}") continue4.3 现象:AssertionError: A tensor is not a valid mask!
原因:segmentation坐标是整数(如[120,80,150,80,...]),但 detectron2 的mask_utils.decode()要求 float32。VIA 的all_points_x默认是 string,若未转 float 会出错。
解决:via_to_coco.py中,all_points_x/all_points_y必须显式转float:
x_coords = [float(x) for x in region["shape_attributes"]["all_points_x"]] y_coords = [float(y) for y in region["shape_attributes"]["all_points_y"]]4.4 现象:IndexError: list index out of range在dataloader的__getitem__
原因:coco_train.json中annotations数组为空(即没有一条 annotation),但images有 100 条。detectron2 默认认为每张图至少有一个 instance,空图会被跳过,但若 batch_size=2 且两张图都空,则batch为[],后续操作崩。
解决:precheck.py已包含--min_annotations 1参数,运行时必须开启:
python3 precheck.py --input_dir ./labelme_annos/ --min_annotations 1并删除所有annotations为空的 JSON。
4.5 现象:训练 loss 为 nan,val AP=0
原因:segmentation坐标中存在inf或nan(如某次 LabelMe 导出时points为[[inf, nan], [150,80], ...]),源于图像元数据损坏。
解决:在labelme_to_coco.py的process_shape()中,添加 numpy 级校验:
import numpy as np points = np.array(shape["points"]) if not np.isfinite(points).all(): print(f"SKIP shape with inf/nan points in {json_path}") continue5. 进阶技巧:用coco_to_yolo_mask.py一键生成 YOLOv8-Seg 兼容格式
虽然 Mask R-CNN 是主流,但越来越多团队在边缘设备部署时转向 YOLOv8-Seg——它 inference 更快,对小目标更鲁棒,且官方提供了ultralytics库的完整训练 pipeline。但 YOLOv8-Seg 的标注格式和 COCO 完全不同:它要求每张图一个.txt文件,每行一个 instance,格式为class_id center_x center_y width height x1 y1 x2 y2 ...(polygon 点坐标归一化到[0,1])。手动转?1000 张图就是 1000 个 txt,每个 txt 里几十行坐标,错一位就 mask 错位。coco_to_yolo_mask.py就是为此而生。
5.1 YOLOv8-Seg 格式规范与coco_to_yolo_mask.py的映射规则
YOLOv8-Seg 的.txt文件(如IMG_001.txt)内容示例:
0 0.521 0.432 0.124 0.087 0.512 0.421 0.530 0.421 0.530 0.443 0.512 0.443 1 0.712 0.623 0.098 0.072 0.705 0.615 0.719 0.615 0.719 0.631 0.705 0.631- 第 1 列:
class_id(从 0 开始); - 第 2~5 列:
center_x center_y width height(归一化 bbox); - 第 6 列起:
x1 y1 x2 y2 ...(归一化 polygon 点,必须为偶数个,且 ≥6 个)。
coco_to_yolo_mask.py的核心逻辑:
- 读取
coco_train.json; - 对每个
image,创建同名.txt(如IMG_001.jpg→IMG_001.txt); - 对每个
annotation,计算center_x = (bbox[0] + bbox[2]/2) / image_width,同理center_y、width、height; - 对
segmentation,将每个x,y归一化:x_norm = x / image_width,y_norm = y / image_height; - 拼接为一行,写入
.txt。
执行命令:
python3 coco_to_yolo_mask.py \ --coco_json coco_labelme_train.json \ --image_dir ./images/ \ --output_dir ./yolo_labels/ \ --classes "defect,scratch,crack"参数说明:
--coco_json:输入 COCO JSON;--image_dir:图像目录,用于读取width/height;--output_dir:输出.txt目录;--classes:类别字符串,用于映射category_id→class_id(defect=0,scratch=1,crack=2)。
注意:YOLOv8-Seg 要求所有 polygon 点数一致(如全为 20 点),但实际标注点数不等。本脚本不做插值,而是保留原始点数——
ultralytics的AutoShape会自动处理变长点序列,无需 pad。
5.2 验证 YOLOv8-Seg 标签:用plot_yolo_mask.py可视化检查
生成.txt后,不能只信文件存在。必须可视化抽查,确认归一化无偏移、类别 ID 正确、polygon 闭合。plot_yolo_mask.py读取一张图及其.txt,用 OpenCV 绘制 mask 轮廓并叠加原图:
python3 plot_yolo_mask.py \ --image_path ./images/IMG_001.jpg \ --label_path ./yolo_labels/IMG_001.txt \ --classes "defect,scratch,crack" \ --output_path ./vis/IMG_001_vis.jpg它会生成IMG_001_vis.jpg,其中:
- 原图灰度显示;
defect用红色轮廓;scratch用绿色轮廓;crack用蓝色轮廓;- 每个轮廓旁标注
class_id和点数。
若看到轮廓严重偏移(如整个 mask 在图外)、颜色错乱(class_id=0却画成绿色),说明--classes顺序与coco_to_yolo_mask.py中的category_id映射不一致,需核对coco_train.json的categories数组。
5.3 从 COCO 到 YOLO 的边界:为什么不用coco_to_yolo.py(非 seg 版)?
网上很多coco_to_yolo.py只转 bbox,不转 segmentation。它们输出的.txt是:
0 0.521 0.432 0.124 0.087 1 0.712 0.623 0.098 0.072这只能用于 YOLOv8-Detect,不能用于 YOLOv8-Seg。YOLOv8-Seg 的训练必须有 polygon 坐标,否则loss_mask项为 0,模型只学 bbox。本资源的coco_to_yolo_mask.py是专为 Seg 场景写的,它确保第 6 列起永远是成对的x_norm y_norm,且数量 ≥6。这是区分「能跑」和「能训好」的关键。
从那以后我每次导出 YOLO 格式,都强制走一遍plot_yolo_mask.py抽查 5 张图——哪怕项目 deadline 压得只剩 2 小时。因为 mask 错位的模型,训得再久也是在拟合噪声。希望帮到你。
本文还有配套的精品资源,点击获取