ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用COCO格式气球数据集在MMDetection中快速跑通Mask R-CNN

用COCO格式气球数据集在MMDetection中快速跑通Mask R-CNN 简介这是一份将气球Mask R-CNN标注转换为COCO格式的现成数据集专为使用最新MMDetection框架训练实例分割模型而准备适合正在学习深度学习目标检测或需要快速搭建分割实验的研究者与开发者。压缩包内共有76个文件包括74张不同场景下的气球JPEG原图以及instances_train2017.json和instances_val2017.json两份标准COCO标注文件压缩后整体大小仅36.89MB。数据集已按COCO规范组织包含边界框与逐像素分割掩模可直接被MMDetection读取省去自行转换标注的繁琐过程下载即可投入训练。利用这份数据集可快速验证Mask R-CNN在气球类别上的分割效果也能作为进阶学习或课程设计的基础数据帮助理解实例分割从数据准备到模型推理的完整链路。目前已有512人浏览学习对于想避开格式坑、专注调参和评估的实践者而言是一个性价比很高的入门数据集。1. 让 Mask R-CNN 在 MMDetection 里跑通为什么这份气球 COCO 数据集值得先试几十张气球照片真的能训练出一个可用的 Mask R-CNN我最初也怀疑。亲手把这份已经转成 COCO 格式的气球数据集接进 MMDetection 后发现它的价值不在“刷点”而在用最小成本把实例分割的完整链路走一遍。资源本身不复杂一个解压后就是 train2017、val2017 和两个 instances JSON 的包直接替换官方 COCO 数据集的路径就能训练。它解决的是最烦人的数据集适配问题。原版 balloon 示例用的是 VIA 标注格式而 MMDetection 默认吃 COCO JSON如果自己写转换脚本光是 polygon 转 segmentation、算 bbox 和面积就够折腾半天。这份资源把这些做完了训练和测试效果也经过验证。适合两类人刚接触 MMDetection、想确认环境与训练链路的人以及业务上要做实例分割、想先用小数据集摸清 Mask R-CNN 收敛行为的人。下面从数据结构开始拆再讲怎么接进框架、参数怎么调最后把几个典型翻车点摆出来。2. 数据集本体COCO 化到底改了什么2.1 从 VIA 标注到 COCO JSON转换的核心差异原版 Matterport Mask R-CNN 的气球示例标注是基于 VIAVGG Image Annotator生成的。VIA 的导出结构是“一张图片一条记录”每个标注区域放在regions里坐标是 polygon 点列没有统一的类别表也没有全局的 image id 和 annotation id。这种格式能被原仓库的utils直接读取但喂给 MMDetection 就完全不是一回事。把这个数据集转成 COCO 格式本质上做了四件事第一把每张图片的 metadata文件名、宽高、id汇总到images数组并和文件夹里的图片一一对应。第二把每个标注区域的 polygon 坐标改写成 COCO 的segmentation字段形式是[[x1,y1,x2,y2,...]]这种嵌套列表同时计算出bbox和area。第三建立全局的categories表气球这个类别放在id: 1。第四把iscrowd统一设为 0表示这些标注都是非密集遮挡的单实例。这里有个细节值得注意COCO 的bbox是[x, y, width, height]而 VIA 记录的是 polygon 顶点必须从顶点坐标里取最小 x、最小 y、最大 x、最大 y 反推。看起来是小学数学但转换脚本如果没处理好浮点数的边界生成的 bbox 会跑到图片外面训练时 loss 直接异常。这份资源里的 JSON 我做过头尾检查bbox都在画布范围内area也和 polygon 实际面积对得上说明转换时是认真算过的。2.2 解压后的文件布局解压ballon_mask_rcnn.rar之后目录结构基本长这样ballon_mask_rcnn/ ├── annotations/ │ ├── instances_train2017.json │ └── instances_val2017.json ├── train2017/ │ ├── 6810773040_3d81036d05_k.jpg │ └── ...训练图片 └── val2017/ ├── 3825919971_93fb1ec581_b.jpg └── ...验证图片文件/目录作用在 MMDetection 里的用途train2017/训练图片目录data_prefix.img指向它val2017/验证图片目录test_dataloader的图片路径instances_train2017.json训练集 COCO 标注train_dataloader的ann_fileinstances_val2017.json验证集 COCO 标注val_evaluator的ann_filetrain2017、val2017、instances_*2017.json这些名字都是沿用官方 COCO 的命名习惯但它和官方 COCO 没有任何关系。官方 COCO 里没有气球这个类别这里只是借用了这套目录命名方便 MMDetection 的 CocoDataset 直接识别。别把这两个 JSON 合并到官方 COCO 的标注里类别 ID 会冲突这是后文避坑部分要强调的。2.3 文件名里的 Flickr 痕迹如果你点开图片目录会发现文件名都是3825919971_93fb1ec581_b.jpg这种长串。这不是随机字符串它的前缀3825919971是 Flickr 上的 Photo ID后面跟的是图片 secret 和尺寸后缀_b、_k等。来源是原版 balloon 数据集从 Flickr 抓取的公开图片文件名保留了原始 ID便于溯源。文件多的时候不要试图用 JSON 里的id和文件名前缀做算术对应这是没规律的一切以file_name字段为准。训练前其实应该先确认一件事JSON 里images的file_name是否真的能对上目录里的文件。曾经遇到过一个转换包图拷了 80%JSON 里却有 100 条记录训练到一半直接 FileNotFoundError。所以我在下面给了一个检查脚本这是拿到任何 COCO 格式资源后我都要干的第一件事。2.4 训练之前用 10 行脚本检查标注质量不要拿到 JSON 就急着配 config先跑这段脚本# inspect_balloon_coco.py import json from collections import Counter, defaultdict with open(data/balloon/annotations/instances_train2017.json) as f: train json.load(f) print(类别表:, train[categories]) print(图片数:, len(train[images]), 标注数:, len(train[annotations])) # 统计每张图的标注数量找出空标注图片 per_img Counter(a[image_id] for a in train[annotations]) img_ids {im[id] for im in train[images]} empty [im[file_name] for im in train[images] if im[id] not in per_img] print(空标注图片数:, len(empty)) # 检查 segmentation 字段类型list 表示 polygondict 表示 RLE seg_types defaultdict(int) for a in train[annotations]: seg_types[type(a[segmentation]).__name__] 1 print(segmentation 类型分布:, dict(seg_types)) # 类别 ID 分布确认 id 从 1 开始 for cid, cnt in Counter(a[category_id] for a in train[annotations]).items(): print(category_id:, cid, 数量:, cnt)这段脚本有几个用途。segmentation类型分布如果全是list说明标注是 polygon 格式MMDetection 的 CocoDataset 能直接读取并转成BitmapMasks如果出现dict说明是 RLE 格式框架同样支持但你要知道它内部走的是另一套解码逻辑。类别 ID 分布是为了确认category_id是从 1 而不是 0 开始的因为 COCO API 在 mmdet 内部会把 id 1 映射到索引 0如果手动改过 JSON 里的 id索引就会错位训练出的模型类别全对不上。我一般的习惯是再挑两张图用matplotlib把 polygon 画在原图上看看边缘是否贴合气球轮廓。这一步花不了两分钟但能省掉后面排查 mask loss 异常的时间。3. 接到 MMDetection三个关键配置位3.1 CocoDataset 的加载流程MMDetection 的 CocoDataset 读取这份资源时有三个关键字段决定它能不能找到数据data_root、ann_file、data_prefix。data_root是数据集根目录ann_file是相对于data_root的标注 JSON 路径data_prefix.img是图片相对于data_root的目录名。框架内部先用 pycocotools 加载 JSON建立 image id 到 annotation 的索引再根据file_name拼接出完整图片路径。也就是说只要这三个字段对图片目录名是叫train2017还是images都不重要但一定要和 JSON 里的file_name匹配。metainfo则负责类别映射。COCO JSON 里categories的 id 是 1但在 MMDetection 内部训练时用的是从 0 开始的索引框架会根据你在 config 里声明的classes(balloon,)自动完成映射。这里不需要去改 JSON只需要保证类别名的顺序和数量与categories表一致。3.2 目录摆放建议把解压后的train2017、val2017、annotations直接放到 MMDetection 项目的data/balloon/下cd mmdetection mkdir -p data/balloon # 把解压出的三个文件/目录移进去最终结构如下 # data/balloon/ # ├── annotations/ # │ ├── instances_train2017.json # │ └── instances_val2017.json # ├── train2017/ # └── val2017/这个布局是 MMDetection 最常见的数据集组织方式后面写 config 时data_root统一填data/balloon/就行。注意不要画蛇添足在data/balloon/下再套一层images/train2017那样data_prefix得多写一层而且避坑章节的第一个报错就是这么来的。3.3 Mask R-CNN 训练配置我按 MMDetection 3.x 的写法在configs/balloon/下新建一个mask_rcnn_r50_fpn_balloon.py它继承官方 Mask R-CNN 的 base config只覆盖数据集相关字段# configs/balloon/mask_rcnn_r50_fpn_balloon.py _base_ ../mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py data_root data/balloon/ metainfo dict( classes(balloon,), palette[(220, 20, 60)] ) train_dataloader dict( batch_size2, datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/instances_train2017.json, data_prefixdict(imgtrain2017/), filter_cfgdict(filter_empty_gtTrue, min_size32), ), ) val_dataloader dict( datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/instances_val2017.json, data_prefixdict(imgval2017/), test_modeTrue, ), ) test_dataloader val_dataloader val_evaluator dict( typeCocoMetric, ann_filedata_root annotations/instances_val2017.json, metric[bbox, segm], ) test_evaluator val_evaluator # 小数据集没必要硬跑 COCO 12 epoch train_cfg dict(max_epochs50, val_interval5) default_hooks dict( checkpointdict(interval5, save_bestcoco/segm_mAP) )这段配置有几个参数值得解释。batch_size2是考虑到气球原图分辨率不低、Mask R-CNN 的 mask head 很吃显存如果你只有 11G 左右的显卡先从这里起步OOM 再降到 1。filter_cfg.filter_empty_gtTrue会把那些没有任何标注的图片从训练里滤掉因为 COCO JSON 里允许存在空标注图片但训练时喂进去会让 RPN 的监督信号混乱。min_size32过滤尺寸过小的目标避免极端小目标干扰。save_bestcoco/segm_mAP让每轮验证时按分割 mAP 保存最优权重而不是只按 bbox mAP这对实例分割任务更合理。val_dataloader里的test_modeTrue表示验证阶段不做数据增强只走归一化和尺寸缩放。CocoMetric同时计算bbox和segm两套指标因为 Mask R-CNN 是检测加分割双头输出只看检测框会漏掉 mask 分支的问题。3.4 开训命令与日志里看什么配置写完后启动训练cd mmdetection python tools/train.py configs/balloon/mask_rcnn_r50_fpn_balloon.py --work-dir work_dirs/balloon第一次跑加载阶段会卡一会儿那是 pycocotools 在建立标注索引几十张图几秒钟就完成。真正训练开始后日志里重点看三行loss_cls是分类损失loss_bbox是检测框回归损失loss_mask是掩膜分割损失。正常情况三者一起下降到 20 轮左右loss_mask会降到 0.1 以下。如果发现loss_mask一直在 0.5 附近波动却不降多半是标注里的 segmentation 有问题如果loss_bbox先降后涨可能是学习率太大配合 batch_size 减小后要注意学习率补偿常见做法是把 base config 里的学习率按 batch_size 比例下调。MMDetection 3.x 的 base config 带有自动学习率缩放但auto_scale_lr只在开启时生效手动训练时我会刻意看一眼初始 lr 是不是 0.02如果是batch2 时建议改到 0.0025。4. 避坑从读不到图到 mAP 为 0 的五个现场4.1 FileNotFoundError训练到一半找不到图现象训练跑到第一个 epoch 末尾日志抛FileNotFoundError: data/balloon/train2017/xxx.jpg或者 pycocotools 在加载时直接提示图片缺失。原因最常见是目录结构多套了一层。比如把解压后的内容放成了data/balloon/images/train2017/但配置里data_prefixdict(imgtrain2017/)拼接出来的路径是data/balloon/train2017/自然找不到。另一个原因是 JSON 里的file_name和实际文件名大小写不一致转换包有时会把扩展名统一成.JPG而目录里是.jpg。解决先跑一遍前面给的检查脚本把 JSON 里的file_name逐个和os.path.exists对照。我一般在data/balloon/下直接用tree确认层数保证图片和annotations是兄弟目录而不是父子嵌套。4.2 mask_loss 出现 NaN 或训练中断现象训练日志里loss_mask在某个 epoch 突然变成nan或者直接报RuntimeError: CUDA error: device-side assert triggered。原因COCO 标注里的segmentation是空列表。VIA 转 COCO 时如果某个 polygon 坐标解析失败转换脚本常常写成segmentation: []而不是跳过这个标注。MMDetection 的 mask 分支拿空 mask 做监督反向传播时就会出现 NaN。解决用 2.4 的脚本统计segmentation为空列表的标注数量如果存在直接把对应 annotation 从 JSON 里删掉或者重新用 polygon 坐标生成。注意不是删图片只删有问题的标注。处理完 JSON 后再把 JSON 里的annotations数量对一遍确保没有误伤正常标注。4.3 训练正常但单张推理 mask 全黑现象训练曲线很漂亮验证 mAP 也不错但拿一张验证图做推理框是有的mask 却一片黑或者只覆盖一半。原因大概率是置信度阈值设得过高。Mask R-CNN 的 mask 分支输出是软 mask最终显示时要经过阈值二值化score_thr设成 0.8 时只有非常确信的区域才显示。气球这种边缘柔和的物体mask 分数普遍比框分数低一截。解决推理时把score_thr降到 0.3 到 0.5 之间。如果你在model.show_result里传了score_thr先确认它没有被外界参数覆盖如果用的是tools/test.py --show-dir注意它默认只保存检测框可视化要看 mask 需要自己在代码里调用show_result。4.4 验证 mAP 忽高忽低现象val_interval5时第 10 轮 mAP 0.42第 15 轮掉到 0.31第 20 轮又涨回 0.45曲线像过山车。原因数据集只有几十张图验证集可能就十来张一轮随机采样和增强带来的波动远大于模型实际的能力变化。这不算 bug是小数据集的统计噪声。解决固定随机种子config里加一段randomness dict(seed42, deterministicTrue)。然后把验证间隔调大到 10 轮少看几次中间的波动最终以save_best保存的最优权重为准。另外训练阶段的增强不要开太猛气球数据集本来就小过度增强会让模型学不到稳定的纹理特征。4.5 显存 OOMbatch2 都跑不动现象启动训练后直接CUDA out of memory或者跑几个 iteration 后才爆。原因Mask R-CNN 的 mask head 对显存非常敏感尤其原图是 Flickr 大图缩放后仍然有 800 像素级别特征图加 mask 分支的显存占用比纯检测模型高 30% 以上。如果显卡是 8G 的老卡batch2 确实勉强。解决先降 batch_size 到 1这是最直接的办法。还不够就把train_pipeline里的img_scale从(1333, 800)改成(1000, 600)但要注意这会影响小目标召回。再不行就开混合精度训练在命令后面加--cfg-options optim_wrapper.typeAmpOptimWrapper需要环境里有 apex 或 mmcv 的 amp 支持。实在不行建议直接把 backbone 换成resnet50的轻量版本或者用mask_rcnn_r50_fpn_mstrain_3x_coco这类对显存更友好的配置。5. 用 val 集做端到端验证指标和 mask 可视化5.1 验证配置已经就位前面给的 config 里test_dataloader已经指到了val2017test_evaluator也配好了CocoMetric所以训练完不用改任何代码直接跑工具命令就能拿到验证指标。5.2 跑一次正式验证cd mmdetection python tools/test.py configs/balloon/mask_rcnn_r50_fpn_balloon.py work_dirs/balloon/epoch_50.pth --show-dir work_dirs/balloon/vistest.py会根据配置里的test_evaluator自动计算指标输出里会同时出现bbox_mAP和segm_mAP两行。对于气球这种单类别数据集segm_mAP才是掩膜质量的真正指标bbox_mAP再高mask 边缘烂也是白搭。如果你用的是旧版 MMDetection 2.x才需要手动加--eval bbox segm3.x 已经不需要。5.3 单张推理视觉验证指标是数字最终还得看肉眼效果。我用一段最简推理脚本from mmdet.apis import init_detector, inference_detector config configs/balloon/mask_rcnn_r50_fpn_balloon.py checkpoint work_dirs/balloon/epoch_50.pth model init_detector(config, checkpoint, devicecuda:0) img data/balloon/val2017/3825919971_93fb1ec581_b.jpg result inference_detector(model, img) model.show_result(img, result, out_fileballoon_pred.jpg, score_thr0.5)init_detector负责加载配置和权重inference_detector跑完整前向show_result把 bbox、mask 和类别标签画到原图上。score_thr0.5是显示阈值如果 mask 显示残缺降到 0.3 再看。看到 mask 边缘基本贴合气球轮廓且没有把背景天空误分进去说明训练链路是通的。做完这一轮验证这份数据集在你手里就不再是一个黑匣子。我个人的习惯是以后无论从哪拿到“已经整理好”的 COCO 格式资源第一件事永远是先跑那个 10 行检查脚本确认类别表、空标注、segmentation 类型再挑一张图做可视化然后才敢把data_root指过去训练。这套流程帮我躲开了不少标注质量导致的玄学问题从那以后每次换数据集都强制走一遍翻车率明显降下来。希望帮到你。本文还有配套的精品资源点击获取
返回列表