
做图像分割的这两年大家应该都听说过一个名字Mask2Former。它算得上是把语义分割、实例分割、全景分割统一到一个框架里的代表作而对我来说更难得的是它把“训练自己的数据集”这件事从“只有实验室调参师能做”变成了一个普通工程师也能上手的项目。这篇文章我打算用一篇完整实操记录的形式把从零标注数据、整理成COCO格式、修改配置文件、跑通训练到排查各种报错的整个过程都讲一遍全程照着做基本能复现。如果你是第一次碰Mask2Former也没关系。我默认你用过PyTorch、跑过分类型模型但没接触过Transformer分割模型。文章里所有关键参数我都会解释为什么这么设而不是扔给你一串配置让你瞎抄。我也把自己在训练中碰到的显存溢出、类别不平衡、loss不下降这几个经典问题整理成了排查清单方便你日后遇到问题时直接对照。1. 先搞明白Mask2Former在做什么训练才不容易跑偏1.1 为什么一个模型能同时干三种分割任务传统做法里语义分割用FCN或DeepLab这类逐像素分类模型实例分割用Mask R-CNN这类“先检测再抠图”的两阶段模型全景分割则需要另外设计网络结构把两者输出融合起来。你换一个任务基本就要换一套网络头数据格式也跟着变维护成本很高。Mask2Former的思路是把分割任务统一成“mask class”的预测问题。模型先学习N个二值mask每个mask代表图像中某个目标的区域然后对每个mask输出一个类别标签。这种设计绕开了“像素分类”和“边框检测”之间的鸿沟语义、实例、全景三种任务只是监督信号不同、query数量不同而已。这背后依赖的是Transformer的query机制。你可以把模型的每个query理解成一个“有目标意识的候选区域生成器”它迭代地调整自己关注的图像区域最终每个query要么收敛到某个真实目标上要么被预测为空类。训练自己的数据集时你只需要提供“目标区域”和“类别标签”其余都交给模型自己去学习和匹配。实际训练时模型预测的mask数量是固定的比如100个或300个而你的图像里目标数量是不固定的。Mask2Former通过二分图匹配把预测mask和真实mask做最优配对然后用mask loss和分类loss监督。这一点和DETR一脉相承也是它和传统分割模型最本质的区别。我刚接触这个概念时觉得它挺抽象后来想通了一个生活类比Mask2Former就像你带了一个团队的画师每个画师先试着画一个物体区域画完之后你拿这些画和真实物体一一比对告诉每个画师“你画的是不是对的”最后训练稳定后每个画师就自动分工有的负责大楼、有的负责天空。实际训练中query确实是会“分化”的你甚至能通过可视化query看到这种自动分工的现象。1.2 masked attention机制它是怎么做到比前辈快的Mask2Former相比前代工作MaskFormer最大的改进在于把Transformer decoder里的cross attention替换成了masked attention。普通attention会让每个query去看整张特征图的每个位置计算量随分辨率指数上涨而且容易让模型分心去看无关区域。Masked attention的思路是上一轮迭代已经粗略预测了一个mask区域这一轮就只让query关注这个mask内部的特征。这样既大幅减少了计算量也相当于给模型加了一个注意力先验——它应该关注目标内部而不是背景。这里有个容易忽略的前提训练初期mask预测得不准masked attention岂不是直接学坏了Mask2Former的解决办法是把预测mask和真实mask做一个加权融合训练初期真实mask比例较高后来逐渐降低加上mask loss本身在反向传播整个系统是稳定收敛的。这个细节我们在改配置的时候不需要碰但理解它有助于你调试loss曲线异常的问题。还有一个我实际体验很深的点Mask2Former是一个非常吃backbone也非常吃层次的模型。官方默认配置的backbone大多是基于ImageNet预训练的ResNet或Swin编辑器里可选。你如果想在自定义数据集上获得好效果大概率瓶颈不在head设计而在backbone能否提取到足够丰富的语义特征这一点到后面聊精度优化时还会展开。2. 环境配置一次说到能用的版本组合2.1 版本匹配是新手第一个大坑Mask2Former官方代码是建立在detectron2之上的。我自己的环境踩过很多次坑拆开来看主要就三个部分CUDA、PyTorch、detectron2或mmcv。你下载任何组件时先确认三者的版本能互相匹配再动手。这里给出一个我实测稳定跑通的组合Ubuntu 20.04显卡建议显存不低于16GB训练前中期我用的是单张3090CUDA 11.8对应的驱动版本建议≥520Python 3.9PyTorch 1.13.1torchviton 0.14.1detectron2从源码编译commit选择官方最新稳定release其他依赖opencv-python、timm、panopticapi等如果机器上已有PyTorch命名冲突通常集中在detectron2和mmcv之间建议同一台机器上不要同时源码安装两个框架来跑Mask2Former要么走detectron2分支要么用mmdetection分支不要混。安装detectron2时最常用的是源码编译方式。本人当年编译踩了一次gcc版本太老的问题detectron2对C标准有要求升级gcc版本到9.3以上即可解决。如果你只是要运行和训练不需要二次开发C算子建议直接用官方发布的预编译wheel按你机器CUDA和torch版本选对应文件能省大量时间。2.2 安装步骤速览创建虚拟环境是必须的不要让这些实验依赖污染你的主环境。我习惯把项目都放在/opt/projects或~/workspace下结构如下conda create -n mask2former python3.9 -y conda activate mask2former pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python timm scipy pip install githttps://github.com/facebookresearch/detectron2.git安装完detectron2后再把Mask2Former官方仓库拉下来并把其中的mask2former包路径加入PYTHONPATH就可以开始准备数据了。git clone https://github.com/facebookresearch/Mask2Former.git cd Mask2Former pip install -r requirements.txt # 里面主要是panopticapi等小依赖 export PYTHONPATH$PYTHONPATH:$(pwd)提示如果你是在服务器上跑一定确认好用户的CUDA环境变量。在.hcrc里硬编码CUDA路径会比每次手动export省心很多但切记不要把系统自带的某个老版本cuda写死否则后续装轮子会一脸懵。3. 数据准备标注工具和COCO格式拆解3.1 我先说结论统一转成COCO格式Mask2Former官方训练脚本读的是COCO格式的实例分割/全景分割标注。网上也有人直接用某个框架的自定义格式但在换模型、换框架、做评测时都会很痛苦。我的建议是无论你用什么标注工具最后统一处理成COCO JSON。标注工具我这些年用过Labelme、CVAT、X-AnyLabeling。三选一的话我评估优先级是数据量小几百张用X-AnyLabeling它支持多边形、Mask、关键点还有交互式分割辅助标注效率比纯手动高很多数据量中等几千张团队协作建议用CVAT它支持多人标注、自动标注SAM作为后端、导出COCO格式只需要单机单人、马上就开工Labelme最简单标注结果转COCO也容易不管用哪款标注时请牢记一个原则一个目标一个多边形多个目标不要合并成一个物体。很多人图省事把远处的小目标忽略掉、把粘连的同类物体画成一个大区域后续训练出来的模型边界会很糊AP也会掉得不明不白。3.2 COCO JSON里到底有哪些字段标注完拿到的是标注软件自己的格式你需要写一个转换脚本。COCO格式在实例分割路径下包含images、annotations、categories三大块每个字段都要对得上{ images: [ { id: 1, file_name: 000001.jpg, width: 1280, height: 720 } ], annotations: [ { id: 1, image_id: 1, category_id: 2, segmentation: [[510.2, 315.0, 512.0, 316.5, ...]], area: 12543.7, bbox: [500.0, 300.0, 40.0, 60.0], iscrowd: 0 } ], categories: [ {id: 1, name: person}, {id: 2, name: car} ] }segmentation里的多边形坐标按[x1, y1, x2, y2, ...]顺序排列必须是绝对像素坐标。area和bbox可以从多边形计算iscrowd在实例分割数据集里一般设为0。如果你用detectron2做数据加载它还会通过image_id去关联到图片路径所以images数组里的id和annotations里的image_id要一一对应。我平时还喜欢用官方工具里的panopticapi把普通instance格式转成panoptic格式用于全景分割训练但在训自定义实例数据集时不需要转直接吃instance JSON就行。3.3 目录怎么组织最顺手推荐目录结构如下datasets/ ├── custom/ │ ├── train/ │ │ ├── images/ │ │ └── annotations.json │ ├── val/ │ │ ├── images/ │ │ └── annotations.json │ └── test/ │ ├── images/ │ └── annotations.json训练集、验证集、测试集划分建议按6:2:2或8:1:1来。尤其注意同一个目标出现在多张图片中的不同视角一定要保证这些图片只划分到同一个集合里否则会数据泄漏验证指标虚高实际落地效果暴跌。我遇到过一个输电塔巡检数据集同一个塔在不同角度拍了二十几张有人直接随机划分训练指标高了3个点但现场测试完全不行原因就是这个。3.4 标注质量检查清单训练前务必写点代码统计几个东西每张图的标注物体数、每个类别的实例数、多边形框是否越界、是否存在空标注图片。越界坐标很常见很多标注工具导出时尾部坐标会超出图像边界必须在转换时做clip。空标注图片也要剔掉否则训练时会出现没有匹配的gt的情况轻则loss波动重则训练崩溃。我习惯在转换脚本里顺手输出一个统计报告list一下每类的实例数和图片数这个报告能帮你很快发现标注错乱的问题。4. 训练一个自定义模型从配置文件到首次跑通的完整流程4.1 配置文件里最关键的参数Mask2Former官方仓库用detectron2的LazyConfig或YACS配置。对新手来说最好从官方提供的configs/coco/instance-segmentation/maskformer2_R50_bs16_50ep.yaml开始改只改自己要改的地方。首要参数是MODEL.SEM_SEG_HEAD.NUM_CLASSES。在detectron2的实现里这个配置控制的是每个mask预测的类别数不包括背景类所以如果你有5个类别就设成5。不少人把它设成“类别数1”导致训练时类索引对不上这是一个低级但常见的错误。其次需要注意MODEL.MASK_FORMER.NUM_OBJECT_QUERIES。默认值是100也就是模型最多预测100个目标区域。如果你的场景里单张图目标很多比如密集的人群、密集的货架商品100可能不够建议增加到200或300。反过来如果单张图通常只有几个目标保持100即可更小的query数量能加快收敛。训练超参数集中在SOLVER里。默认的base_lr是0.0001配合bs16。如果显存不够降到bs4就需要把learning rate也降到一个合适的值或保持线性缩放经验。经验公式lr_new lr_base * (bs_new / bs_base)也就是说bs从16降到8lr应该从1e-4降到5e-5左右才能保持相近的优化轨迹。还有一个容易被忽略但影响很大的参数MODEL.MASK_FORMER.TEST.SEMANTIC_ON、INSTANCE_ON、PANOPTIC_ON。如果你只想做实例分割就把INSTANCE_ON设成True其余设成False否则推理时可能额外跑很多分支拖慢速度甚至爆显存。4.2 注册自己的数据集detectron2需要你把自定义数据注册进去。官方文档推荐注册函数方式我在脚本里这样写的from detectron2.data import DatasetCatalog, MetadataCatalog def get_my_dataset(): return load_coco_json(/path/to/datasets/custom/train/annotations.json, /path/to/datasets/custom/train/images, train) DatasetCatalog.register(custom_train, get_my_dataset) MetadataCatalog.get(custom_train).set(thing_classes[person, car])验证集同理注册一个custom_val。注意load_coco_json是detectron2自带的数据加载器能直接解析COCO格式不需要重新造轮子。如果你用的是mmdetection的那个配置方式逻辑也类似只是把数据配置写进dataset字段。原理都一样告诉框架“数据集叫什么名字、标注文件在哪、图片目录在哪、类别名有哪些”。4.3 启动训练并看懂第一条日志一切就绪后启动训练命令python train_net.py \ --config-file configs/coco/instance-segmentation/maskformer2_R50_bs16_50ep.yaml \ --num-gpus 1 \ --resume \ OUTPUT_DIR ./output/custom_mask2former \ DATASETS.TRAIN (custom_train,) \ DATASETS.TEST (custom_val,) \ MODEL.SEM_SEG_HEAD.NUM_CLASSES 2 \ MODEL.MASK_FORMER.NUM_OBJECT_QUERIES 100第一步先看输出是否创建成功、能否读到图片。第一张图读出来后loss会在最初几十步内剧烈震荡这很正常因为二分图匹配还没稳定下来。正常情况下50-100步之后loss会开始缓慢下降而不是直接发散。如果loss在第一个500步内就NaN多半是数据里有非法标注去检查坐标或segmentation区域是否为空。我习惯同时观察mask loss曲线、dice loss曲线和分类loss曲线。mask loss负责找到目标的区域位置dice loss负责边界精细程度分类loss决定它认得类别。某条曲线异常指向的修复方向不一样。比如mask loss正常下降但dice loss不降通常是目标边界复杂或者标注不精细这时可以考虑增加训练轮数或调整loss权重。4.4 类别不平衡怎么办自定义数据集经常遇到一种情况某个类别出现频率特别低。Mask2Former的query数量固定为100训练时低频类别很难被匹配到足够的query最后模型几乎不认识它。我试过错采样、基于sigmoid focal loss等方案最直接有效的方法是增加数据集里低频类别的图像占比。你可以直接按类别进行图像级抽样低频类别的图像重复采样2-3轮。如果实在不想动数据也可以增大分类分支loss的权重让分类任务对低频类别更敏感但这种做法效果通常有限。训练过程中可以定期用小脚本统计预测出来的类别分布如果某一个query总是空转或者某一类完全不出现基本可以判断模型崩了或者数据严重不平衡不要急着跑完整个50个epoch再调中途就要停下来修改数据分布。4.5 训练时间和预期效果用单张3090512分辨率输入ResNet-50 backbonebs8训练10000张图的数据集50个epoch大概需要2-3天。训练早期几乎每个epoch都有肉眼可见的提升越到后面越平缓。我一般会在验证集做mAP评估当mAP连续10个epoch不再上涨时不再继续等直接截断。训练完成后你得到的是一个.pth模型文件。推理脚本官方也提供了demo在这里只需要设置MODEL.WEIGHTS指向你的checkpoint输入一张测试图片即可。我在实际项目里常用的方式是把它导成ONNX然后走服务化推理但那是另一个话题本次先不多说。5. 训练踩坑实录与排查清单5.1 最常见的四个报错训练过程中我整理了一个高频报错表基本覆盖了绝大部分自定义数据集训练会遇到的问题。现象可能原因解决动作训练刚开始就报“KeyError: image_id”COCO JSON里annotations缺字段检查转换脚本给每个annotation补上image_id、area、bboxCUDA out of memory显存不够或输入分辨率太大降低batch size、降低分辨率、开启gradient checkpoint或换更大显存卡loss为NaN标注segmentation为空或坐标非法检查多边形坐标剔除空标注检查是否有未clip的越界坐标验证结果全为0类名配置和类别数不匹配核对Metadata的thing_classes数量、NUM_CLASSES、categories的id映射第一个报错特别多见通常是手动拼JSON时漏了字段。detectron2的COCO loader对字段要求较严格缺一个就报错调试时会比mmdetection直白一些但是也别慌看traceback指向哪一行基本能定位到具体字段。5.2 显存优化一代更比一代忙Mask2Former的显存占用比很多分割模型高因为Transformer decoder中每个层级的特征图都存在GPU上你还有多个分辨率级别的特征参与loss计算。实测下来需要注意几个点输入分辨率不要一上来就用1024或2048先用512跑通流程再视效果和显存逐步提升batch size设成1-2时使用梯度累积模拟更大的batch比如bs2累积8步等效于bs16这样精度不会明显下降但能大幅降低显存需求开启detectron2的MODEL.MASK_FORMER.CHECKPOINT_DETECTRON2允许backbone用冻结权重对显存没有帮助但能降低部分前期不稳定最实用的一项技巧是开启torch.cuda.amp混合精度。detectron2的默认配置里其实自带AMP选项改一下SOLVER.AMP.ENABLED为True即可。混合精度下显存占用大约能降30%同时训练速度还有提升我跑自定义数据基本都是开AMP的。5.3 提升精度的几个方向如果你跑通了一遍基础训练想进一步涨点我建议按以下顺序尝试最简单的是加长训练轮数和做数据增强。Mask2Former对数据增强比较稳健随机翻转、随机缩放、颜色抖动都能用。升级backbone。从ResNet-50换成ResNet-101或换成Swin-T通常都有明显提升。这是性价比最高的涨点方式。调整为多尺度训练。detectron2支持在配置中开启多尺度训练让模型适应不同尺度的目标。最后才是调loss权重。大部分情况下默认loss权重已经足够好盲目调权反而容易过拟合。5.4 我自己的体会训过几次自定义数据集之后我越来越倾向于把Mask2Former当作一个“通用目标抽取器”来用而不是单纯的一个分割模型。它的query机制天生就能把前景目标从背景中区分出来哪怕某些类别你标注得很少它也能凭特征相似度给你圈出可能的目标。但这也意味着你的标注一致性非常重要类别定义含糊、目标大小差异过大、边界画得不仔细都会影响最终效果。尤其提醒一下不要以为“标注数量多”就等于“质量高”。我见过一万张图的数据集效果不如别人两千张的原因就是标注里带了大量的小到看不见的目标和画到边界外的多边形。Mask2Former对监督信号是敏感的Dataset质量决定模型上限网络结构只负责逼近这个上限。动手训练前一定把数据检查一遍你会在后面省十倍的调试时间。这个项目如果继续扩展我比较建议下一步尝试把Mask2Former和SAM结合用SAM的自动标注结果辅助扩充新类别的数据再用Mask2Former训练一个更小更快的专用模型这也是我现在在项目里用的路线效率比纯手工标注高很多。