简介:这是一份面向计算机视觉入门与进阶学习者的气球实例分割数据集,基于 Mask R-CNN 构建并已完整转换为 COCO 格式,可直接放入最新 MMDetection 框架中训练与测试,免去自行标注和格式转换的繁琐步骤。资源包大小 36.89MB,共包含 76 个文件,其中 74 张 JPG 图片按 train2017 与 val2017 划分,另有 instances_train2017.json 和 instances_val2017.json 两个标注文件;压缩包内目录包含 train2017、val2017 和 annotations,路径结构清晰,可直接对应 COCO 标准数据加载要求。已有 512 人浏览学习该资源,适合用于快速跑通 Mask R-CNN 训练流程、对比不同 backbone 的效果或开展气球检测与分割方向的实验。通过这套数据,可以直观理解实例分割的数据组织方式、标注文件字段含义以及 MMDetection 的配置与调用逻辑,也能借此体验从数据加载、模型训练到 mAP 评估的完整流程,为后续在自定义数据集上复现 Mask R-CNN 提供完整可参考的样例。
1. 做实例分割第一关是数据:这份气球 COCO 数据集让 Mask R-CNN 训练省一半事
做实例分割的都知道,模型结构可以抄,但一份带像素级掩码的标注数据很难凑。网上的开源数据集里全是规规矩矩的 COCO 格式,自己手里的图却往往是散的 JPG 加一片一片的 PNG mask,光写转换脚本就得调一天。第一次拿到这份「气球 Mask R-CNN 转 COCO 格式」的数据集时,最大的感受是它把最脏的活干完了:图片分好 train/val,标注整理成 instances_train2017.json 和 instances_val2017.json,类目只有一个 balloon,解压后直接用 MMDetection 就能训练 Mask R-CNN,甚至测试效果比从 COCO 里随机抠子集要好得多。它适合两类人:一是刚入门实例分割、想用现成数据跑通 Mask R-CNN 基线的新手;二是需要验证某个分割方案、不想把时间耗在标注格式上的算法工程师。这篇文章从 COCO 标注内部结构讲起,一直讲到训练、避坑、自己转换同类数据,最后给一套微调和评估习惯。
2. 先看懂 COCO 标注再动手:数据集的目录、字段与掩码表示
2.1 压缩包内部结构:train2017 / val2017 / annotations 三件套
解压 ballon_mask_rcnn.rar 后,目录是典型的 COCO 布局:
ballon_mask_rcnn/ ├── annotations/ │ ├── instances_train2017.json │ └── instances_val2017.json ├── train2017/ │ ├── 7308740338_591f27b631_k.jpg │ ├── 7178882742_f090f3ce56_k.jpg │ └── ... └── val2017/ ├── 8053085540_a72bd21a64_k.jpg ├── 4581425993_72b9b15fc0_b.jpg └── ...文件名里的数字是 Flickr 图片 ID,不是 COCO 原始编号,这个不重要;重要的是目录名和 JSON 文件名是配套的。MMDetection 的 CocoDataset 默认会根据配置里的ann_file去读 JSON,再根据img_prefix拼图片路径,所以目录起成 train2017/val2017 是为了省事,你也可以改成别的名字,只要配置里对应改就行。
这份资源里还有一个细节:annotations 目录下只有 instances_train2017.json 和 instances_val2017.json,没有 captions 或 stuff 文件,因为 Mask R-CNN 训练只需要实例标注。图片是从开放图片库里挑出来的包含气球的图像,总量不大,正好适合在单卡上做实验,而不是一上来就面对一百多 G 的完整 COCO。
2.2 从 instances_train2017.json 里抽取三个关键数组
先别急着开训,拿 Python 把 JSON 读出来看一遍,能省很多排查时间。COCO 标注 JSON 顶层有三个必填字段:images、annotations、categories。我一般会写个几十行的检查脚本:
import json from collections import Counter ann = json.load(open('annotations/instances_train2017.json')) print('images:', len(ann['images'])) print('annotations:', len(ann['annotations'])) print('categories:', ann['categories']) # 统计每一类别的实例数量 cnt = Counter([a['category_id'] for a in ann['annotations']]) print('annotations per category:', dict(cnt)) # 查看有标注的图片数量 img_with_ann = set(a['image_id'] for a in ann['annotations']) print('images with annotations:', len(img_with_ann)) # 看前两条 annotation 的完整内容 for a in ann['annotations'][:2]: print(a)这段脚本逻辑很简单:先加载 JSON,然后打印各集合数量。关键是要看categories的内容。这份数据集的categories通常长这样:
[{"id": 1, "name": "balloon"}]只有一个类,id 从 1 开始。再看 annotation 字典,能看到segmentation、area、bbox等字段。注意area是掩码真实面积,不是像素数乘上什么系数;bbox是[x, y, width, height]的浮点列表,对应的是该实例外接矩形的左上角坐标和宽高。
这里容易踩的一个概念坑是:COCO 的category_id是 1,但 MMDetection 里如果直接用官方 COCO 预训练模型,它的 80 类是从 1 到 80 的,没有 id=0 的类别。如果你把num_classes设成 1,后台实际做的是把分类分支的输出从 81 变成 2(包括背景类),所以 1 类数据集的num_classes=1是没问题的。真实在配置里写的 1,不是 1+1。
另一个值得做的检查是验证标注框和掩码是否对齐。因为转换脚本偶尔会把 bbox 和 segmentation 来自不同的 mask 源,导致训练时 mask head 的 loss 正常、box head 的 loss 异常。可以用下面的代码做粗粒度校验:
from pycocotools.coco import COCO coco = COCO('annotations/instances_train2017.json') for ann_id in coco.getAnnIds()[:10]: ann = coco.loadAnns(ann_id)[0] mask = coco.annToMask(ann) x, y, w, h = [int(v) for v in ann['bbox']] # mask 里非零像素是否集中在 bbox 范围内 inside = mask[y:y+h, x:x+w].sum() total = mask.sum() if inside < 0.5 * total: print('bbox and mask mismatch:', ann_id)这个脚本用coco.annToMask(ann)把标注转成二值 mask,再判断 bbox 是否包住了大部分正像素。如果 mismatch 比例高,说明数据源本身不可靠,训练前就得止损。
2.3 多边形坐标 vs RLE:这份数据集里掩码长什么样
COCO 的segmentation字段有两种写法:多边形点列表(polygon)和 RLE 游程编码。标注工具导出的通常是 polygon,格式是[x1, y1, x2, y2, ...],点按顺序围出目标轮廓。RLE 则更像压缩后的位图,适合存储大而复杂的掩码。实例分割训练时,MMDetection 的 CocoDataset 会通过 pycocotools 把 polygon 转成 mask,也可以直接使用 RLE。
这份气球数据集在 JSON 里用的是 RLE 还是 polygon,取决于原始转换脚本。无论哪种,训练前最好用 pycocotools 验证一下能不能正确解码:
from pycocotools.coco import COCO coco = COCO('annotations/instances_train2017.json') img_ids = coco.getImgIds()[:5] for img_id in img_ids: ann_ids = coco.getAnnIds(imgIds=img_id) for ann_id in ann_ids: ann = coco.loadAnns(ann_id)[0] mask = coco.annToMask(ann) # 返回 HxW 的 0/1 mask print(ann['image_id'], mask.sum(), ann['bbox'], ann['segmentation'].__class__.__name__)这里的coco.annToMask(ann)是 pycocotools 提供的解码接口,polygon 和 RLE 都能处理。如果mask.sum()是 0,说明标注有问题,需要回到转换脚本里查。看到输出里 bbox 和 mask 面积量级一致,再进入训练环节就不容易出幺蛾子。这个习惯我建议保留,拿到任何 COCO 数据集都先跑一遍。
annotation 字段里还有一个iscrowd,表示该实例是否是一堆密集物体。COCO 官方对 crowd 区域使用 RLE 且不需要多边形;这份气球数据集几乎都是单体气球,iscrowd基本都是 0。如果以后自己转换,记得把iscrowd设 0,否则 MMDetection 在训练时会走另一条 crowd 分支,容易出现奇怪的行为。
下表是这份 annotation 里最常见的字段说明,排查数据时对照着看:
| 字段 | 类型 | 含义 |
|---|---|---|
| id | int | 标注实例的唯一编号 |
| image_id | int | 所属图片在 images 数组中的 id |
| category_id | int | 类别 id,这里为 1 |
| bbox | list[float] | [x, y, width, height],左上角坐标加宽高 |
| area | float | 掩码面积,像素单位 |
| segmentation | dict/list | RLE 字典或多边形点列表 |
| iscrowd | int | 0 表示普通实例,1 表示群体区域 |
2.4 为什么 COCO 格式能直接对接 MMDetection
很多人问:不是有 VOC 格式吗,为什么非转 COCO?关键原因是 MMDetection 的 CocoDataset 在数据加载阶段已经实现了 COCO JSON 的解析、mask 解码、多尺度裁剪拼接这一整套逻辑,你只需要给一个ann_file路径。而 VOC 格式需要额外的 xml 解析,且 mask 通常是以独立 PNG 文件存储,数据加载路径多一层间接性。COCO 的单 JSON 文件把所有实例标注集中在一起,训练前做 cache 也更方便,尤其适合小数据集快速迭代。
也正是因为这份气球数据是标准 COCO 格式,CocoDataset可以拿到annotations后直接做annToMask,不需要再写任何自定义 Dataset 类,下游的 mask head 训练、评估阶段的 mAP 计算都能复用 COCO 官方 API。所以「转成 COCO」不是一种强迫症,是节省工程成本的选择。
3. 直接训练 Mask R-CNN:MMDetection 配置修改与命令行实操
3.1 用官方 mask_rcnn_r50_fpn 做基类,覆盖类别数和数据路径
MMDetection 的配置文件做了很好的继承机制。我们不需要从零写一个 Mask R-CNN,而是在官方提供的mask_rcnn_r50_fpn_1x_coco.py基础上改几处。先建一个独立配置目录,比如configs/balloon/,新建文件mask_rcnn_r50_fpn_1x_coco.py:
_base_ = 'configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py' model = dict( roi_head=dict( bbox_head=dict(num_classes=1), mask_head=dict(num_classes=1) ) ) dataset_type = 'CocoDataset' data_root = 'data/balloon/' classes = ('balloon',) data = dict( samples_per_gpu=2, workers_per_gpu=2, train=dict( classes=classes, ann_file=data_root + 'annotations/instances_train2017.json', img_prefix=data_root + 'train2017/' ), val=dict( classes=classes, ann_file=data_root + 'annotations/instances_val2017.json', img_prefix=data_root + 'val2017/' ), test=dict( classes=classes, ann_file=data_root + 'annotations/instances_val2017.json', img_prefix=data_root + 'val2017/' ) )这段配置做了三件事:第一,告诉模型num_classes=1,覆盖 bbox head 和 mask head 的输出维度;第二,指定数据格式为CocoDataset,并设置classes=('balloon',),让 dataset 内部的类别映射保持正确;第三,把ann_file和img_prefix指到实际路径。注意samples_per_gpu是每块卡上的 batch size,这里设 2,如果你的显卡显存有限,改成 1 也能跑。
为什么num_classes要写 1?因为 Mask R-CNN 的分类头输出是num_classes + 1通道,那个 1 是背景。官方 COCO 配置里写的是 80,但实际上有 80 个目标类,所以这里如果写81就错了,MMDetection 内部不会帮你再加 1。同理,如果数据里有 3 个类,这里就写 3。
如果你用的是最新版 MMDetection 3.x,data这个字段被拆得更细,但核心的ann_file、img_prefix、classes还在,只是包在了train_dataloader、val_dataloader下。建议先按项目实际安装的版本来,如果跑的是 2.x,上面这份配置可以直接用。
3.2 单卡训练:batch size、学习率与 Epoch 怎么定
数据集不大,单卡训练是常态。在资源有限的情况下,我一般先用 2 的 batch size 跑通流程,再考虑调大。学习率方面,MMDetection 默认的 1x 配置是 0.02,那是基于 8 卡、每卡 2 张图的 batch size 16 算出来的。如果只有单卡 2 张图,总 batch size 变成 4,学习率要等比缩放,常见做法是线性缩放:lr_new = lr_base * new_batch / old_batch。这样算出来大约是 0.005。
python tools/train.py configs/balloon/mask_rcnn_r50_fpn_1x_coco.py \ --work-dir work_dirs/balloon \ --cfg-options optimizer.lr=0.005 data.samples_per_gpu=2--cfg-options是 MMDetection 提供的命令行覆盖入口,optimizer.lr会直接改写配置里的优化器学习率。data.samples_per_gpu对应 3.1 配置文件里面设的 2,如果前面已经写在配置里,这里可以不加。注意samples_per_gpu在 3.x 里被移到了train_dataloader.batch_size,但思路一样。
Epoch 数量不要直接照抄官方 12。官方 1x 是指 COCO 的 12 epoch,那个数据量大;这个气球数据总计可能就几十到一百多张图,训练很快收敛。我通常先跑 50 epoch,观察 loss 是否稳定。如果验证集 mAP 在 20 epoch 后就不再上升,说明已经过拟合,可以早停。
如果中途断了,想接着训练,用--resume-from:
python tools/train.py configs/balloon/mask_rcnn_r50_fpn_1x_coco.py \ --resume-from work_dirs/balloon/epoch_30.pthresume-from会同时恢复优化器状态和模型权重,而不是只加载权重。这比load-from更适合断点续训。
3.3 测试与可视化:test.py 参数和结果分析
训练结束后,在验证集上跑评估:
python tools/test.py configs/balloon/mask_rcnn_r50_fpn_1x_coco.py \ work_dirs/balloon/epoch_50.pth \ --eval segm--eval segm是告诉脚本用 COCO mask 评估协议,输出的是 mask AP。如果只做目标检测,可以用--eval bbox,但这里是实例分割,所以重点关注 segm。默认输出结果是一张表的摘要,包括AP、AP_50、AP_75、AP_s、AP_m、AP_l这几项。对于这种单一类别的小目标数据,AP_50通常是最先看的指标,因为 mask 预测的 IoU 阈值在 0.5 时比较稳定。
想要可视化预测结果,可以加--show-dir:
python tools/test.py configs/balloon/mask_rcnn_r50_fpn_1x_coco.py \ work_dirs/balloon/epoch_50.pth \ --eval segm --show-dir work_dirs/balloon/viz/脚本会把每张测试图的预测框、掩码叠加在原图上,保存到viz目录。这一步很关键,因为 mAP 只能告诉你整体好坏,而可视化能让你一眼看出模型是不是把气球区域整个抠出来了,还是只分割了半边。我第一次跑这份数据集时,看 mAP 觉得还能接受,看了可视化才发现模型对深色背景下的暗红气球识别率极低,这才针对性加了更多数据增强。
4. 避坑指南:数据路径、类别数、显存和 RLE 解码
4.1 训练一开始就报 FileNotFoundError:路径拼错导致找不到图片
现象:执行训练脚本后,日志里跳出 FileNotFoundError,提示找不到类似于data/balloon/val2017/8053085540_a72bd21a64_k.jpg的文件,或者报Directory not found。
原因:最常见的是三种:一是data_root写成了绝对路径,但实际数据放在其他盘;二是解压后的目录名与配置里的img_prefix大小写不一致,比如把气球数据解压成了Balloon_mask_rcnn,而配置里用的是全小写;三是在 Windows 上解压后产生的反斜杠路径在某些脚本拼接时出错。
解决:先在命令行里用一条命令确认路径能对上:
find data/balloon -maxdepth 2 -type f | head -5把输出和配置里的ann_file、img_prefix逐段比对。我一般会在配置里用os.path.abspath打印一遍最终路径,写进 debug 脚本里。最省事的方法是直接把data_root改成相对路径,并保证工作目录在mmdetection项目根目录下启动训练。另外检查一下ann_file里面的image字段的file_name是否和train2017目录里的实际文件名一致,因为有些转换脚本会错误地带上子目录前缀,比如train2017/xxx.jpg,而img_prefix又加了一次train2017/,导致重复路径。
4.2 训练直接报 num_classes mismatch:忘了覆盖 bbox head 和 mask head
现象:训练刚开始,在第一次 forward 时报size mismatch或RuntimeError: Error(s) in loading state_dict for RPNHead,报错里提到bbox_pred或mask_fcn的形状对不上,期待维度是 82,实际维度是 2。
原因:这份气球数据集只有 1 个类,但配置里漏写了model.roi_head.bbox_head.num_classes,导致模型仍然按官方 COCO 的 80+1 类输出分类分数。加载预训练权重时,最后一层 FC 的输出维度对不上,直接报错。
解决:回到 3.1 的配置,确认以下两行都存在,并且都改成num_classes=1:
model = dict( roi_head=dict( bbox_head=dict(num_classes=1), mask_head=dict(num_classes=1) ) )如果用了--cfg-options临时覆盖,也要同时覆盖这两个字段:
--cfg-options model.roi_head.bbox_head.num_classes=1 model.roi_head.mask_head.num_classes=1这里有一个很多人翻车的细节:bbox_head和mask_head都要改,只改 bbox head 会让 mask head 仍然按原来的 80 类输出。代码跑起来可能不报错,但训练出来的 mask 分支完全失效。
4.3 显存溢出 OOM:先降 batch size,不要急着换模型
现象:训练刚开始,显存占用直接拉满,日志报CUDA out of memory,程序退出。
原因:Mask R-CNN 本身就吃显存,R50 backbone 加上 FPN,单图分辨率如果偏大,batch size=2 也可能爆卡。尤其是有些图片尺寸很大,训练时 Resize 没有限制最大边,导致显存峰值过高。
解决:先降 batch size 到 1:
data = dict( samples_per_gpu=1, workers_per_gpu=2, ... )如果还是 OOM,检查输入尺寸的配置。可以在data里加上 train pipeline 的Resize限制,把最大边限制在(1333, 800),这是 COCO 训练的默认值。实际这份气球数据里的图片来自 Flickr,长宽比各异,有些原图非常大,建议把img_scale设置在(800, 1333),减少显存压力。还可以用梯度累积来模拟更大的 batch size:
--cfg-options optimizer_config.grad_clip.max_norm=35但梯度累积在 MMDetection 2.x 里没有内置参数,需要改训练循环,不如直接降 batch size 实在。只要模型能塞进显存,单卡 batch size=1 也是能训的,就是收敛慢一点。
4.4 测试结果全黑、没有 mask:score_thr、mask_thr 与 RLE 解码
现象:测试后test.py输出的 mAP 是 0,或者用--show-dir保存的图片只有原图,没有任何颜色覆盖,看起来像模型完全没预测到东西。
原因:可能性很多,最常见的是 score threshold 设得过高。MMDetection 测试时默认score_thr=0.05,如果你手动改高了,比如 0.8,模型输出置信度普遍在 0.5 左右时,所有预测都会被过滤掉。另一种可能是数据标注里iscrowd=1的实例过多,模型把目标都当成 crowd 掩盖了,但这份数据集很少见。
解决:先用默认阈值测试一次,再做可视化时调低阈值:
python tools/test.py configs/balloon/mask_rcnn_r50_fpn_1x_coco.py \ work_dirs/balloon/epoch_50.pth \ --eval segm --show-dir work_dirs/balloon/viz/ \ --cfg-options model.test_cfg.score_thr=0.3model.test_cfg.score_thr是测试阶段后处理的置信度阈值。对单一类别且图像较简单的数据集,0.3 通常能看到部分预测。如果 0.05 下 mAP 仍然为 0,那问题大概率出在数据加载上,回到第 2 章,先确认coco.annToMask()能正确输出非零 mask。RLE 解码失败通常是因为转换脚本没有把counts从 bytes 转成 str,pycocotools 在 Python 3 下会处理这种差异,但某些自定义 Dataset 不会。你在自己写转换脚本时,一定要在maskUtils.encode()后加上rle['counts'] = rle['counts'].decode('ascii')。
5. 数据集从哪来:任意分割标注转 COCO 格式的通用脚本
5.1 从二值掩码到 RLE:推荐 pycocotools 的 mask 编码路径
这份气球数据集已经是 COCO 格式,但你下次拿到的可能是 LabelMe 的 json,或者一堆黑底白色的 PNG 掩码。掌握「二值掩码转 COCO annotation」的思路,才是真正的一劳永逸。转换的核心是先得到二值 mask,然后用 pycocotools 编码成 RLE。
import numpy as np from pycocotools import mask as maskUtils def encode_mask(mask_binary): # mask_binary: HxW 的 0/1 数组,dtype 可以是 bool 或 uint8 mask = np.asfortranarray(mask_binary.astype(np.uint8)) rle = maskUtils.encode(mask) rle['counts'] = rle['counts'].decode('ascii') return rle这段代码里的关键点是np.asfortranarray。COCO 的 RLE 编码内部按列优先处理,必须先把数组转成 Fortran 序,否则编码结果和实际 mask 不一致,解码后会出现掩码错位的现象。rle['counts']在 Python 3 下是 bytes 类型,直接写进 JSON 会序列化失败,所以要先decode('ascii')转成字符串。
如果你手里的是多边形顶点坐标,可以用maskUtils.frPyObjects先转成 RLE:
polygons = [x1, y1, x2, y2, ...] # 扁平列表 rle = maskUtils.frPyObjects([polygons], height, width) rle = maskUtils.merge(rle)frPyObjects的输入需要[polygon_list],即使只有一个多边形也要再包一层列表。它返回一个 RLE 对象,因为 poly 转 RLE 时可能拆成多个小段,所以通常还要merge合并。如果原图目标有孔洞,多边形是外轮廓加内轮廓,frPyObjects也能处理,但要保证轮廓点的顺序正确,内轮廓方向与外轮廓相反。
5.2 构造 annotation 字段:bbox、area、segmentation、iscrowd
拿到 RLE 后,不要手算 bbox 和面积,直接交给 pycocotools:
def build_coco_annotation(ann_id, image_id, category_id, mask_binary): rle = encode_mask(mask_binary) bbox = maskUtils.toBbox(rle).tolist() # [x, y, w, h] area = float(maskUtils.area(rle)) annotation = { "id": ann_id, "image_id": image_id, "category_id": category_id, "bbox": bbox, "area": area, "segmentation": rle, "iscrowd": 0 } return annotationmaskUtils.toBbox(rle)返回的是一个 numpy 数组,所以要用.tolist()转成 Python list。maskUtils.area(rle)返回的是 RLE 对应的真实像素面积,单位是像素。这里不需要自己算最小外接矩形,因为 COCO 的 bbox 定义就是「能包住 mask 的轴对齐矩形」,pycocotools 的实现和评估时用的完全一致,手工算容易在边缘情况下差一两个像素。
iscrowd一般设 0。只有当一张图里有大量重叠目标且无法逐目标标注时,才把整个区域标成 crowd 并设 1。注意 crowd 的segmentation必须是 RLE,不能是 polygon,且训练时 MMDetection 对 crowd 的处理是忽略其 loss,所以普通数据不要乱设。
5.3 组装 images 与 categories:保证 ID 连续并做数据切分
有了 annotation 列表,还要组装images数组。images里的每个元素需要包含id、file_name、width、height,其他字段比如license、date_captured可以省略。
from PIL import Image import os images = [] annotations = [] ann_id = 1 for img_id, filename in enumerate(os.listdir('train_imgs'), start=1): w, h = Image.open(os.path.join('train_imgs', filename)).size images.append({ "id": img_id, "file_name": filename, "width": w, "height": h }) mask_path = os.path.join('train_masks', filename.replace('.jpg', '.png')) mask_binary = np.array(Image.open(mask_path).convert('L')) > 0 annotations.append(build_coco_annotation(ann_id, img_id, 1, mask_binary)) ann_id += 1 categories = [{"id": 1, "name": "balloon"}] coco_json = { "images": images, "annotations": annotations, "categories": categories } with open('instances_train2017.json', 'w') as f: json.dump(coco_json, f)这段脚本里img_id从 1 开始递增,ann_id也从一个独立的计数器递增。注意image_id必须和images数组里的id对应上,不能复用文件名里的数字。file_name只需要写文件名,img_prefix由 MMDetection 配置负责拼接。如果一张图有多个 mask,比如多个气球,你的循环要分别对每个 mask 调用build_coco_annotation,并且image_id相同但ann_id不同。
切分数据时要注意:不能直接按文件顺序随机分,而是按场景分。气球数据集里同一张照片可能连着好几张,如果同一场景的图同时进了 train 和 val,验证集会虚高。这一点很多教程不会提,我自己第一次做类似数据集时就吃过亏,mAP 高得离谱,换一批真实场景图直接掉点。
6. 进阶技巧:用 COCO 预训练权重微调,只看 mask AP 评估效果
这份气球数据量小,从头训练 Backbone 很容易过拟合。正确做法是加载 COCO 预训练权重,只让后面的 head 重新学。在 MMDetection 里,只要在配置里加一行:
load_from = './checkpoints/mask_rcnn_r50_fpn_1x_coco.pth'由于气球只有 1 类,而预训练模型是 80 类,加载时最后一个分类层和 mask 分支的维度不匹配。MMDetection 默认会忽略形状不一致的权重,只加载 backbone 和 FPN 的部分,这正好是我们想要的。如果你希望连 RPN 也复用,直接默认即可,不用额外写代码。
微调时的学习率不宜过大,我一般设 0.001 到 0.002,是正常训练学习率的一半以下。训练 30 epoch 左右就能看到验证集 mAP 稳定。紧接着用第 3 章的测试命令评估,重点看下面这行输出:
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.672 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.912 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.788对单个类别的小数据集,AP_50到 0.9 以上说明模型基本能框住并分割出气球;AP_75也能到 0.7 以上,说明掩码边缘质量不错。如果你的AP_50高但AP_75掉得很惨,大概率是 mask 边缘不够精细,优先检查数据集标注边缘是否平滑,其次再考虑更换更强的 backbone。从那以后,我每次拿到新的分割数据都会先写一个 20 行的检查脚本验证 JSON 解码,再用预训练权重跑 10 个 epoch 做 smoke test,确认 loss 在降、mAP 能过 0.5,才开始全量调参。希望帮到你。
本文还有配套的精品资源,点击获取