☰
Mask R-CNN实例分割实战:数据采集与标注全流程解析
2026/10/22 18:28:49 网站建设 项目流程

MaskRcnn(一)数据采集与标注

很多刚开始接触实例分割的开发者,也包括当年的我,最容易犯的一个错误就是:先去把MaskRcnn的网络结构、backbone、损失函数研究得明明白白,甚至把官方仓库里的推理demo跑得飞起,然后兴冲冲地拿自己手机拍的照片去测试,结果发现模型什么都框不出来,或者框出来了但mask糊成一团。这时候才意识到,问题根本不在模型,而在我手里根本没有一批像样的训练数据。

这是Mask R-CNN系列的第一篇,我想把整条链路里最先遇到的关卡——数据采集与标注讲透。这个阶段的工作量通常占到一个实例分割项目总投入的六成以上,而且它的质量直接决定了后续训练出来的模型上限。没有一套规范、可复现的数据生产方案,后面所有的网络结构优化、训练技巧都是在沙滩上盖楼。这篇文章适合那些正准备用MaskRcnn做自己的检测分割项目,但还不知道怎么把手里的图像素材变成模型能吃的数据集的开发者;也适合团队里需要负责数据生产、标注规范制定的同学。我会把数据采集的规划思路、标注工具选型、JSON到COCO的格式转换细节和实际踩过的坑一起讲清楚。

通常我们把数据准备分成两步:采集和标注。采集解决的是"喂什么"的问题,标注解决的是"期望模型学到什么"的问题。这两步单独看都不难,但组合在一起,里面的讲究就多了。

1. 实例分割任务对数据的要求,和分类检测任务完全不一样

先必须把这个问题掰扯清楚,因为很多人还是拿做图像分类的思路来准备实例分割的数据。分类任务里,一张图只要有一个正确的类别标签就行,哪怕目标只占了画面一角,哪怕背景乱七八糟,模型都能通过全局特征学到个大概。但在MaskRcnn这类实例分割任务里,模型要做三件事:把目标框出来(检测)、给每个框里的像素做二分类(区分前景背景)、把每个实例从其他实例中分离开来(实例区分)。这决定了它对数据的要求是三维的:图像本身、每个目标的边界框、每个目标精确到像素的轮廓。

1.1 模型要求数据的三个层面:检测框、实例掩膜、类别标签

MaskRcnn的loss函数里主要包含三部分:分类loss(判断框里是什么)、检测框回归loss(把框的位置调准)、以及掩膜loss(逐像素判断是否属于目标)。其中掩膜分支是最有特色的部分——它对每个ROI区域单独做一次像素级预测。这一设计意味着训练数据必须包含逐像素的标注,而不是只画一个矩形框。

举个例子,同样是"检测图片里的交通标志",用YOLO之类的目标检测模型来训练,标注工具里拉一个矩形框就完事儿了;用MaskRcnn来训练,则要把标志的每一个角落、每一条边都仔细描出来。标志牌如果是圆形的,标注出来就应该是个接近完美的圆;如果你的标注是歪歪扭扭的多边形,模型学到的掩膜形状也会跟着歪。所以,标注质量在高分辨率细节上,会直接影响最终模型输出的轮廓精度。

还有一点容易被忽略,就是实例分离对数据的要求。MaskRcnn最终是通过区分每个实例的像素分配来实现分割的,训练数据里如果大量出现两个目标挨得很近、甚至相互遮挡的场景,模型需要学习的区分信息就更多。如果数据里全是目标之间离得远远的"干净图",模型学到的是"IOU大于某个阈值就算同一实例"这样的简化逻辑,等到了真实场景中目标一旦有遮挡,分割效果就会断崖式下跌。

1.2 "干净数据"陷阱:为什么你的模型在测试集上表现不错,一上真实场景就崩

在实际项目里,我观察到一个很普遍的现象:很多团队费了很大力气标注了一两千张图,训练出来的模型在测试集上效果还挺好,mAP能到0.7以上,可是部署到现场后表现惨不忍睹。复盘下来,问题几乎都出在数据分布和真实场景分布不一致上。

这种不一致体现在几个方面。第一是采集场景过于单一:比如做厂区车辆检测,采集的图像全是晴天白天,没有夜间、雨天、逆光环境的图像,模型自然没学过这些条件下的特征。第二是目标尺度失衡:MaskRcnn对尺度变化相对敏感,虽然它有FPN(特征金字塔)来多尺度检测,但训练数据里如果目标大小都很集中,模型对尺度的泛化能力就会很弱。我之前见过一个人做无人机视角的车辆检测,采集的图像全是无人机在50米高度拍的,车辆占画面比例都差不多,等换到100米高度时,模型就大量漏检。第三是正负样本不平衡:实例分割模型也会从图像中采样负样本(背景区域)来学习"什么不是目标",如果采集的图像里目标总是出现在画面的正中间,背景总是同一类场景,模型学到的是"画面中间的那些东西"而不是"目标本身"。

所以,数据采集的第一步不是"拍够数量",而是"设计采集方案"。后面会讲具体的办法。

2. 数据采集没有捷径,但可以结构化:场景规划与拍摄实操

数据采集阶段最忌讳的就是"随便凑一批图就开工"。我建议做任何实例分割项目,在采集前先花半天时间做一个采集规划表。这个表格不需要多复杂,但必须明确几个问题:目标类别是什么、每类目标在真实场景中可能出现的形态变化有哪些、环境变化有哪些、每类至少需要多少张、从哪些渠道获取。

2.1 先定类别清单和采集量级:300张是下限,不是平均线

做实例分割,类别的定义会直接影响标注难度和模型效果。我见过一个项目,把"车辆"定义成一个类,结果里面包含了小轿车、卡车、三轮车、自行车,模型训练出来之后,对小轿车和三轮车的分割是互相污染的状态。MaskRcnn的类别区分能力取决于每个类内的特征一致性,同一类目标的外观差异越大,模型需要学习的特征空间就越大,需要的数据量就越多。建议在定义类别时,把外观差异大的目标拆开,哪怕它们逻辑上都属于"车辆"。

数据量方面,如果用的是预训练权重做迁移学习,每类目标的图像数量最低不要少于300张,这是一个经验下限;如果是从零开始训练,或者目标在图像中的形态特别复杂(比如分割人这种非刚性物体),每类500张以上才比较稳。注意我这里说的是"每类图像数量",不是"每类实例数量"。一张图最好能包含2到5个实例,实例总数越多,对模型越有好处,但标注成本也会上涨。合理的做法是控制每张图的实例数量在3个上下,让总实例数量达到图像数量的两到三倍。

这里需要特别提醒一下:300张只是让模型"能训练"的下限,不是"效果好"的下限。如果你的场景光照变化大、目标形态变化大、或对精度要求高,数据量翻倍并不夸张。给模型多一点样本,通常比花大力气调参收益高得多。

2.2 场景覆盖矩阵:把"环境变化"拆成可执行的拍摄清单

我通常用一张矩阵表来规划采集内容。横轴是环境变量:光照(晴天、阴天、夜间、逆光)、天气(雨雾、灰尘)、季节、背景复杂度(干净背景、杂乱背景)、拍摄角度(俯视、平视、仰视)、拍摄距离(近、中、远)。纵轴是目标类别。凡是矩阵里打勾的场景组合,都应该在采集时尽量覆盖到。

这样做的好处是,你能直观地看到数据覆盖的盲区。比如做工地安全帽检测,如果矩阵里"夜间"是空的,就说明数据里没有夜间场景,模型到了夜间场景大概率失效。补拍或通过图像增强来弥补,就得在这个阶段提上日程。

具体拍摄时,有几点经验:不要总是把目标放在画面中心,要模拟真实监控或巡检的构图;不要总是用同一个设备拍的素材,不同设备的色彩、分辨率差异其实是很好的数据增强;保留一部分真实拍摄中会出现的模糊帧、远距离小目标帧,这些"坏样本"对模型鲁棒性帮助非常大。我见过有项目在数据清洗阶段把这些"坏样本"全删了,结果模型在真实场景中对模糊小目标的检测能力几乎为零,后来重新补齐这部分数据才把指标拉回来。

2.3 图像清洗与内容审核:哪些图必须扔掉,哪些图要小心保留

采集完成后进入清洗环节。自动清洗脚本可以过滤掉完全模糊、纯色、分辨率过低的图像,但语义层面的清洗,还得靠人眼快速过一遍。以下几种图我建议直接弃用:目标被大面积遮挡且无法辨认类别的;目标极小(比如长边小于32像素)的——MaskRcnn的最小检测尺度有限,这么小的目标即使是人也很难标准;图像内容与目标类别严重不一致的(比如标注"行人"的数据里混入了一张人形广告牌)。

还有一类图要小心:多目标极度拥挤的场景。比如人群密集的照片,标注成本极高且容易漏标,如果漏标了,标注的质量审查阶段还要返工,不如一开始就控制这类图的比例。清洗后的图像建议全部统一为JPG格式,分辨率跨度不要太大,我一般会控制在600到2000像素之间,超大分辨率(比如4000像素以上)的图像需要切片处理再进入训练流程,否则显存扛不住。

严格来说,图像采集与清洗只是数据准备的前半程。清洗完毕后,才开始真正的重头戏——标注。

3. Labelme与掩膜标注实操:从安装到逐像素描边

标注工具的选择,直接决定了标注效率和最终数据的可用性。实例分割领域最主流的标注工具是Labelme,它的优点是用多边形标注的方式得到封闭区域,正好对应了MaskRcnn的掩膜概念。相比LabelImg这类只能画矩形框的工具,Labelme生成的JSON文件保留了每个目标的完整轮廓坐标,这是后续生成COCO格式分割数据的基础。

3.1 为什么首选Labelme:多边形标注与掩膜分支的天然契合

Labelme的标注结果是一个JSON文件,里面保存了每个目标的形状信息。标注时,你沿着目标边缘点出一圈关键点,形成一个闭合多边形,这些关键点的坐标就是后续掩膜生成的原始数据。MaskRcnn的掩膜分支最终需要的是每个目标的像素级mask,而多边形本身就是一种高效的掩膜描述方式——描述一个复杂轮廓只需要几十个点,比逐像素存储省太多空间。

对比其他工具,LabelImg适合做目标检测(矩形框),但做不了实例分割;RectLabel是macOS平台的付费工具,功能虽全但扩展性差;VGG Image Annotator(VIA)也能做多边形标注,但它的数据结构不太直观;Scale AI这类商业平台适合大规模外包标注,但难以私有化部署且价格高。对大多数开发者来说,Labelme是平衡了功能、可定制性和开源度之后的最优解。

关于标注工具的选型,再补充一点:尽量不要选"看起来功能很多"的工具,要选"格式你完全看得懂"的工具。标注工具一旦形成自己的私有格式,后面做格式转换和数据校验时,会花掉你大量不必要的时间。Labelme的JSON格式直白到可以手动编辑,这对调试和数据检查特别友好。

3.2 安装与界面核心操作逐项注释

安装Labelme非常简单:

# 使用conda创建独立环境,避免依赖冲突 conda create -n labelme python=3.8 -y conda activate labelme pip install labelme # 启动 labelme

启动后界面比较简洁,核心操作集中在几个地方:

  • Open Dir:打开图像所在文件夹,左侧会列出全部图像。
  • Edit Polygons:进入标注模式,在画布上左键点击,每次点击生成一个多边形顶点。
  • 双击或按回车:结束当前多边形绘制,弹出标签选择/输入窗口,输入类别名称(比如"cat"或"dog")。
  • 右侧的Label List:显示当前图像已经标注的所有目标,可以在这里选中某个多边形进行微调、删除或重命名。

操作层面有几个容易踩的小坑:绘制多边形时,顶点数量不要贪多也不要太少。对于圆弧形目标,20到40个点就能拟合得很好,点太多反而会在训练时产生锯齿状的掩膜边界,模型输出也会跟着出现锯齿。另外,在目标边缘模糊的区域(比如白色物体在白色背景上),标注时要结合上下文判断轮廓位置,宁可稍微往里收一点,也不要标到背景区域去。掩膜外扩带来的危害比稍微内收更大,因为背景像素混入前景会直接污染模型学到的特征。

3.3 标注规范的制定,比标注动作本身更重要

一个人标注还好说,如果是多人团队协作,没有统一标注规范,后期合并数据时会出现各种灾难。我建议任何标注任务开工前,先花二十分钟写一版标注规范,至少包含以下几点:

  • 类别定义:除了"什么是目标",还要写明"什么不算目标"。例如"行人"类别,要写明"远处小于32像素的人不标"、"只露出一只手的人不标"、"广告牌上的人不标"。
  • 遮挡处理:目标之间有重叠时,被遮挡部分要不要标注?我的建议是标注可见部分,不要把看不见的部分脑补出来。因为MaskRcnn是学习"可见像素的轮廓"的,你标注的掩膜应该反映图像中真实可见的区域,而不是完整的物体形状。
  • 边缘处理:目标紧贴图像边界时,边界处的顶点怎么取;目标反光导致边界不清晰时,以什么特征为准。
  • 最小标注尺寸:前面提到的32像素规则要落实成明文规范,代码里最好加一个校验,标注面积小于某个阈值的多边形直接报警。

规范文档写好后,样例图要配好。我会做一个示例文件,里面包含一张"标注得漂亮"的图、一张"标注得不合格"的图,两者对比,让标注人员一眼就懂什么叫合格。

4. 从Labelme JSON到COCO数据集:格式转换的结构化拆解

标注完成后,每一个图像都会对应生成一个同名JSON文件。这个JSON文件不能被训练框架直接使用。主流的MaskRcnn实现(比如Detectron2或MMDetection)采用的数据格式是COCO JSON格式。所以接下来的核心工作,是把分散的Labelme JSON文件合并成一个COCO格式的大JSON文件。这一步是整个数据准备链路里最容易出错、也最需要被认真对待的环节。

4.1 COCO JSON的核心结构:data、categories与annotations

COCO数据格式的核心可以拆解成三个顶层字段:

  • images:一个列表,每个元素描述一张图像,包含id、file_name、width、height等字段。
  • categories:一个列表,每个元素描述一个类别,包含id、name字段。注意id从1开始。
  • annotations:一个列表,每个元素描述一个已标注的实例,包含id、image_id、category_id、segmentation、bbox、area、iscrowd等字段。

其中segmentation字段是最关键的部分。它有两种表达方式:多边形坐标列表(polygon),或RLE压缩编码。实例分割训练时,通常会把多边形转换成RLE做训练输入,但COCO JSON里可以保留多边形形式。Detectron2在数据加载时会自己完成转换,所以提交时用多边形形式即可。

bbox字段是目标的最小外接矩形,用[x, y, width, height]表示,x、y是矩形左上角的坐标。MaskRcnn的训练过程中,会先用RPN生成候选框,再通过RoIAlign在框内做掩膜预测。真实标注框的作用就是监督让候选框回归到正确位置。如果bbox算得不准,后续的候选框回归训练就会收到错误的监督信号。

area字段是实例的面积(以像素为单位),在COCO评估AP时,按面积大小划分了small、medium、large三档。不填这个字段会导致评估报错,很多行列转换脚本里经常漏掉它。

iscrowd字段表示该目标是否属于"拥挤群体"。对MaskRcnn来说,iscrowd=1的标注在训练时会被排除在掩膜监督之外,它通常用于人群、密集物体这类极难逐个标注的场景。大多数人不会用到这个字段,但如果在密集场景拍摄了大量重叠目标,可以试着把部分难标目标设为iscrowd=1,避免它们干扰其他实例的学习。

4.2 转换脚本的完整流程:从多边形坐标到RLE编码

一个可用的转换脚本应该包含以下步骤:

  1. 遍历所有Labelme JSON文件,读取图像的像素尺寸,写入images列表。
  2. 为每个类别分配一个整数id,写入categories列表。
  3. 遍历每个JSON中的多边形标注,把顶点坐标整理成COCO要求的扁平格式([x1, y1, x2, y2, ...])。
  4. 根据多边形计算最小外接矩形作为bbox。
  5. 用多边形计算面积作为area。
  6. 为每个标注分配一个唯一的id。

面积计算有现成的库可以调用。最常用的是pycocotools里的mask工具:

from pycocotools import mask as coco_mask import numpy as np import json def polygon_to_rle(polygon_points, img_h, img_w): # polygon_points: 扁平列表 [x1, y1, x2, y2, ...] poly = np.array(polygon_points, dtype=np.float64).reshape(-1, 2) # 转成pycocotools需要的polygon格式 rle = coco_mask.frPyObjects([poly], img_h, img_w) rle = coco_mask.merge(rle) return rle def calculate_area_and_bbox(rle, img_h, img_w): area = float(coco_mask.area(rle)) bbox = coco_mask.toBbox(rle) # 返回 [x, y, w, h] return area, bbox.tolist()

这段代码的核心逻辑是先把多边形转成RLE(Run-Length Encoding,行程长度编码),然后基于RLE计算面积和最小外接矩形。RLE编码的本质,是把图像逐行扫描、将连续相同像素值的"长度"压缩存储。它比直接存储多边形坐标更省空间,而且求面积、求IOU、做掩膜变形都非常高效。这也是MaskRcnn训练时在数据加载阶段把多边形转成掩膜的标准路径。

4.3 类别ID的映射细节:从train到val要保持一致

很多人在做数据集划分时会踩一个坑:训练集和验证集的类别顺序不一样,导致模型训练时学到的类别ID跟验证时的标注ID对不上。无论你在哪个框架里训练,都要确保整个数据集共享同一个categories列表。我一般的做法是,先扫描所有标注文件,建立类别名到ID的映射表,然后再划分train/val集合,两个集合共用这份ID映射。

还要注意一个问题:Polygon坐标的整数化处理。Labelme标注时坐标通常是浮点数,但COCO格式里图像是像素网格,坐标需要四舍五入到整数吗?我的经验是,保留浮点数精度效果更好。像素网格上的掩膜是通过多边形填充生成的,坐标的浮点表示反而更精确。当你用frPyObjects把它转成RLE时,工具内部会自己处理精度问题。如果你提前取整,反而会丢失信息,让掩膜边缘出现不必要的粗糙。

5. 数据校验三板斧:可视化、指标检查与分集验证

格式转换做完不等于数据就绪,还要经过完整的校验流程。我把它总结为三板斧:可视化检查、指标异常检查、划分一致性检查。这三步做完,数据集才敢交给训练脚本。

5.1 第一板斧:把COCO JSON画回图像上,人眼复核

这一步非常朴素,但极其有效。写一个简单的可视化脚本,读取COCO JSON,把每张图的bbox和mask都画到原图上输出成新图,然后人眼过一遍。你立刻就能发现:有没有多标、漏标、多边形严重偏离目标的标注。如果发现某些图像的标注明显错位,排查的路径通常是:原始Labelme JSON有没有问题,还是转换脚本环节出错了。可视化能帮你把问题定位在那个环节。

当你把mask叠加在原图上时,要特别注意边缘贴合度。用背景色填充的部分如果出现了大面积锯齿,可能是多边形顶点太少;如果mask明显"越界"溢出到背景物体上,可能是标注时标宽了。这些小问题在单张图上不明显,但累加到几千张图上,就足以把模型的边界预测拉偏。

5.2 第二板斧:检查类别实例数、面积分布和加载异常

写一段统计脚本,输出数据集的关键指标:

  • 每个类别的实例总数、图像总数;
  • 每张图的平均实例数;
  • 实例面积的直方图,看small/medium/large的分布;
  • 是否有空标注(有图但没有任何标注);
  • 是否有bbox越过图像边界的异常标注。

我经历过一次惨痛教训:转换脚本在处理某个特殊标注时出现了一个边界bug,导致几十张图的bbox坐标超出了图像尺寸。训练时Detectron2不报错,只是把这些标注当成无效数据跳过,但评估时AP直接掉了一截,排查了好几天才通过指标异常检查发现问题。如果一早就做这种统计检查,根本不会浪费这个时间。

5.3 第三板斧:train/val划分的合法性检查

数据集划分有一个严谨性要求:确保同一张图像的多个标注不会被分到不同集合里。划分应该以图像为最小单位,而不是以实例为最小单位。此外,类别重叠也要检查——如果某个类别只出现在训练集、不出现在验证集,验证时模型在该类别上的表现评估就没有意义。合理的划分是保证每个类别在验证集中至少有5到10个实例。划分完毕后再跑一次前面两板斧,因为划分本身可能引入新的问题。

顺带提醒一点:Convert后的COCO JSON通常有几MB到几十MB大小,在Detectron2或MMDetection里注册数据集时,如果发现加载很慢,可以考虑在第一次加载后序列化一份cache文件,框架会缓存解析结果。但这个优化等遇到慢的问题再做就行,前期不必折腾。

5.4 训练集与验证集的常见比例:80/20是把双刃剑

80/20是常用的划分比例,但它有几个前提:数据量足够大(比如每类超过500张);各类别分布相对均衡;验证集数量和类别覆盖都能满足评估需求。如果数据量只有每类300张,建议改成85/15甚至90/10,保证验证集有足够样本。但如果数据量很大(每类上千张),80/20完全没问题。不要死守某个比例,按类别覆盖优先原则来定。数据量特别少(每类不足100张)时,考虑做交叉验证,用同一批数据多轮划分训练评估,得到的指标会比单次划分更可靠。

6. 常见坑位复盘与分析:一份来自实战的避坑清单

最后把我在几个真实项目里踩过的坑汇总一下,都是那种"不遇到不会相信,遇到了要折腾很久"的问题。

6.1 标注时间分配与"应激性返工"问题

第一个坑是过度迷信标注工具的效率。很多团队买了商业标注工具,结果标注规范没定,标注起来特别放飞自我,最后返工成本远超工具费用。我的建议是:哪怕再着急,开工前也先花半天定规范和做样例。返工一次的成本至少是正常标注的两倍,因为标注人员需要重新审视每一张图。

6.2 清洗阶段删掉的"坏样本",可能是真实场景的主力样本

第二个坑像前面提到过的,清洗时把"模糊、远距离小目标、部分遮挡"全删了。我当时做某仓库盘点项目时,觉得模糊样本没法标,全删,结果真实摄像头拍出来的图像里恰恰有大量这种模糊样本,模型上线后检测率低到吓人。后来又回去重新采集,才把指标慢慢拉回来。数据清洗的目的是去掉"无法有效提供监督信息"的图,而不是把"难度大"的图图省事删掉。建议清洗时设置一个"难例样本池"——对标注困难但可能有用处的图,单独存放,后续评估或补充训练时再用。

6.3 类别不平衡会被MaskRcnn放大,因为掩膜分支需要逐类学习

第三个坑是类别间实例数不平衡。MaskRcnn的掩膜分支对每个类别独立学习,不像分类模型可以靠权重调整平衡类别影响。如果项目里A类有2000个实例,B类只有50个实例,B类的掩膜分支几乎学不到有效特征,最终输出mask质量会很差。缓解的办法包括:给B类额外采集数据,或者对B类做更多样的数据增强,甚至可以对B类的标注做"复制粘贴增强"——把B类目标从原图中抠出来,随机粘贴到其他背景图上,扩充B类样本量。但这个操作有一个前提:粘贴位置不能太违和,否则模型学到的是"某个位置上总有某个目标"的伪特征。

6.4 多人标注的划分方式与合并策略

多人标注时,如果只按文件目录硬分给每个人,容易造成不同人风格不一致。建议按场景或批次划分,让同一个场景的图像尽量由同一个人标注,至少保持批次内的一致性。合并时,对相同的标注判断标准做一致性抽检——抽部分图像让多人各自标注,计算他们之间的mask IOU,如果IOU低于0.9,说明标准不一致,需要重新培训对齐。

有不少团队用半自动标注工具(比如先用一个粗略模型预标注,再人工修正)来提升效率。这个思路本身没问题,但要注意半自动标注的偏差会继承到新训练模型里。如果预标注模型本身就有系统性的偏差(比如对某种类型的目标总是漏检),人工修正是很难全部补干净的。使用半自动标注时,一定要按类别、按面积分层抽检修正结果,别只看总量。

数据采集与标注做完,一个可用的、可复现的实例分割数据集就算真正成型了。回看整条链路,你会发现最有价值的反而不是某一个具体技术点,而是"在动手之前把整个流程想清楚"这件事。标注规范和场景规划花掉的一两个小时,会在之后的每一天里持续给你回报。下一篇我会接着写MaskRcnn训练环境的搭建和实验设计,包括backbone选择、学习率策略和数据增强方案。到时候我们可以继续沿着这条链路往下走。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询