在 MMDetection 中训练自定义模型:以 AugFPN Neck 与 Cityscapes 数据集为例的完整实战指南
2026/9/20 8:35:18 网站建设 项目流程
  • 人工智能
  • 计算机视觉
  • 深度学习
  • 模型评测

【免费下载链接】mmdetection

OpenMMLab Detection Toolbox and Benchmark

项目地址:https://gitcode.com/gh_mirrors/mm/mmdetection
点击查看免费下载

导读

本文是一份面向 MMDetection(OpenMMLab Detection Toolbox)开发者的端到端实战指南,完整讲解"如何在标准数据集上训练、测试与推理自定义检测模型"。全文以 Cityscapes 数据集上训练一个定制版Cascade Mask R-CNN R50为例:用自研 Neck(以AugFPN为示例)替换默认的FPN,并在训练管线中注入Rotate/TranslateX自动数据增强。读完本文,你将掌握四大核心能力:标准数据集的准备与格式转换、基于注册机制自定义模型组件、编写继承式配置文件、以及使用tools/train.pytools/test.py完成完整的训练-测试闭环。

整体流程只需四步:① 准备标准数据集 → ② 编写自定义模型模块 → ③ 编写配置文件 → ④ 训练、测试与推理。下文将结合仓库源码逐一深入。

第一步:准备标准数据集

1.1 数据目录结构

本文以标准 Cityscapes 数据集为例。MMDetection 建议将数据集根目录软链接到$MMDETECTION/data下,目录结构如下:

mmdetection ├── mmdet ├── tools ├── configs ├── data │ ├── coco │ │ ├── annotations │ │ ├── train2017 │ │ ├── val2017 │ │ ├── test2017 │ ├── cityscapes │ │ ├── annotations │ │ ├── leftImg8bit │ │ │ ├── train │ │ │ ├── val │ │ ├── gtFine │ │ │ ├── train │ │ │ ├── val │ ├── VOCdevkit │ │ ├── VOC2007 │ │ ├── VOC2012

如果你的目录结构不同,则需要相应修改配置文件中的路径。另一种更优雅的做法是通过环境变量指定数据根目录,从而完全避免改动配置文件:

export MMDET_DATASETS=$data_root

配置解析时会用$MMDET_DATASETS替换数据根路径,这一机制使得同一份配置可以无缝迁移到不同机器、不同数据挂载点。从仓库中的 基础数据集配置 可以看到,数据根路径默认值即为data/cityscapes/,并通过data_root变量引用:

dataset_type = 'CityscapesDataset' data_root = 'data/cityscapes/'

1.2 将 Cityscapes 标注转换为 COCO 格式

MMDetection 的 Cityscapes 实例分割配置依赖 COCO 格式的标注文件(例如annotations/instancesonly_filtered_gtFine_train.json),因此需要先用仓库自带的转换脚本tools/dataset_converters/cityscapes.py完成格式转换:

pip install cityscapesscripts python tools/dataset_converters/cityscapes.py ./data/cityscapes --nproc 8 --out-dir ./data/cityscapes/annotations
  • ./data/cityscapes:Cityscapes 数据根目录;
  • --nproc 8:使用 8 个进程并行转换,可显著提速;
  • --out-dir ./data/cityscapes/annotations:转换后 JSON 标注的输出目录。

1.3 预训练权重准备

目前configs/cityscapes下的配置文件使用COCO 预训练权重初始化模型。如果网络不可用或较慢,请务必提前手动下载预训练模型,否则训练开始阶段会因为无法加载权重而报错。这一机制在自定义配置中通过load_from字段指定(详见第四步),它只加载权重、不参与模型结构定义。

第二步:编写自定义模型(以 AugFPN Neck 为例)

第二步的核心是"用你自己的模块替换或扩展现有检测器"。这里假设我们要在 Cascade Mask R-CNN R50 检测器中实现一个新的 Neck ——AugFPN,替换默认的FPN

2.1 新建 AugFPN 模块文件

首先在mmdet/models/necks/下新建文件augfpn.py

import torch.nn as nn from mmdet.registry import MODELS @MODELS.register_module() class AugFPN(nn.Module): def __init__(self, in_channels, out_channels, num_outs, start_level=0, end_level=-1, add_extra_convs=False): pass def forward(self, inputs): # implementation is ignored pass

这里最关键的一行是@MODELS.register_module()装饰器。从仓库的 注册表实现 可以看到,MODELS是 MMDetection 提供的 17 个注册节点之一,它是 MMEngine 根注册表的子注册表:

# manage all kinds of modules inheriting `nn.Module` MODELS = Registry('model', parent=MMENGINE_MODELS, locations=['mmdet.models'])

注册机制的本质是建立"字符串类型名 → 类对象"的映射。装饰器把AugFPN类以'AugFPN'为键登记进MODELS注册表,之后配置文件里type='AugFPN'就会被自动解析为这个类。你可以参照仓库中真实的 FPN 实现 来理解一个标准 Neck 的接口约定——其__init__签名恰好包含示例中出现的全部参数:

@MODELS.register_module() class FPN(BaseModule): def __init__( self, in_channels: List[int], out_channels: int, num_outs: int, start_level: int = 0, end_level: int = -1, add_extra_convs: Union[bool, str] = False, ... ) -> None:

从 FPN 的源码实现可以进一步理解这些参数的含义:

  • in_channels:各尺度输入特征图的通道数列表(与 backbone 的out_indices输出一一对应);
  • out_channels:每个输出尺度统一的通道数;
  • num_outs:输出特征金字塔的层数;
  • start_level/end_level:从 backbone 的哪一层开始/结束构建金字塔,end_level=-1表示取到最后一层;
  • add_extra_convs:是否在金字塔之上追加下采样卷积(如 RetinaNet 场景),源码中True会被归一化为'on_input'

FPN 的__init__中还包含长度断言(如num_outs >= num_ins - start_level),新实现的 Neck 建议同样保留这类输入校验,避免配置错误在训练中途才暴露。

2.2 导入模块(两种方式二选一)

方式一:修改mmdet/models/necks/__init__.py

在 necks 包初始化文件 中追加一行导入,并同步加入__all__

from .augfpn import AugFPN

仓库中每个内置 Neck(FPNPAFPNDyHead等)都是通过这种集中导出的方式完成注册的,例如:

from .fpn import FPN ... __all__ = ['FPN', 'BFP', ...]

方式二:在配置文件中使用custom_imports(推荐,零侵入)

不想改动仓库源码时,可以在配置文件里动态导入新模块:

custom_imports = dict( imports=['mmdet.models.necks.augfpn'], allow_failed_imports=False)

custom_imports会在配置加载阶段主动执行指定模块的导入,从而触发@MODELS.register_module()完成注册。这种方式让自定义代码完全独立于仓库本体,便于后续升级、复用与分发。

2.3 在配置中替换 Neck

配置层面只需要把neck字段的type换成'AugFPN'并传入所需参数:

neck=dict( type='AugFPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5)

对比仓库中 Cascade Mask R-CNN 基础配置 里默认的 FPN 配置,可以直观看到两者结构完全一致、仅type不同,这正是注册机制带来的"即插即用"体验:

neck=dict( type='FPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5),

第三步:编写自定义配置文件

3.1 配置继承策略

第三步是为自己的训练设定准备一份完整配置。假设配置文件位于configs/cityscapes/目录下,命名为cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py,通过_base_字段继承三份基础配置,从而只突出必要的修改点:

# The new config inherits the base configs to highlight the necessary modification _base_ = [ '../_base_/models/cascade-mask-rcnn_r50_fpn.py', '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py' ]

三份基础配置分别提供:模型结构(cascade-mask-rcnn_r50_fpn.py)、数据加载与评测(cityscapes_instance.py)、默认运行时设置(default_runtime.py)。

3.2 模型部分:替换 Neck 与调整类别数

model = dict( # set None to avoid loading ImageNet pre-trained backbone, # instead here we set `load_from` to load from COCO pre-trained detectors. backbone=dict(init_cfg=None), # replace neck from defaultly `FPN` to our new implemented module `AugFPN` neck=dict( type='AugFPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5), # We also need to change the num_classes in head from 80 to 8, to match the # cityscapes dataset's annotation. This modification involves `bbox_head` and `mask_head`. roi_head=dict( bbox_head=[ dict( type='Shared2FCBBoxHead', in_channels=256, fc_out_channels=1024, roi_feat_size=7, # change the number of classes from defaultly COCO to cityscapes num_classes=8, bbox_coder=dict( type='DeltaXYWHBBoxCoder', target_means=[0., 0., 0., 0.], target_stds=[0.1, 0.1, 0.2, 0.2]), reg_class_agnostic=True, loss_cls=dict( type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), dict( type='Shared2FCBBoxHead', in_channels=256, fc_out_channels=1024, roi_feat_size=7, # change the number of classes from defaultly COCO to cityscapes num_classes=8, bbox_coder=dict( type='DeltaXYWHBBoxCoder', target_means=[0., 0., 0., 0.], target_stds=[0.05, 0.05, 0.1, 0.1]), reg_class_agnostic=True, loss_cls=dict( type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), dict( type='Shared2FCBBoxHead', in_channels=256, fc_out_channels=1024, roi_feat_size=7, # change the number of classes from defaultly COCO to cityscapes num_classes=8, bbox_coder=dict( type='DeltaXYWHBBoxCoder', target_means=[0., 0., 0., 0.], target_stds=[0.033, 0.033, 0.067, 0.067]), reg_class_agnostic=True, loss_cls=dict( type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) ], mask_head=dict( type='FCNMaskHead', num_convs=4, in_channels=256, conv_out_channels=256, # change the number of classes from default COCO to cityscapes num_classes=8, loss_mask=dict( type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))))

要点解读:

  • backbone=dict(init_cfg=None):显式关闭 ImageNet 预训练主干初始化,改为通过下文load_from加载 COCO 预训练的完整检测器权重,以获得更稳定的训练起点;
  • 三阶段级联bbox_headmask_head全部从 80 类改为 8 类:Cityscapes 实例分割数据集只有 8 个类别,类别数不匹配会直接导致网络输出维度与标注维度不一致。三个Shared2FCBBoxHeadtarget_stds依次递减([0.1,0.1,0.2,0.2][0.05,...][0.033,...]),这是 Cascade R-CNN 由粗到精回归的标准设计,与 基础配置 保持一致,仅类别数不同;
  • 如果只做检测不做分割,可同时考虑将mask_head移除,但本文示例是完整的实例分割模型。

3.3 数据部分:自定义训练管线(AutoAugment)

重写train_pipeline,在原有RandomResize/RandomFlip基础上插入AutoAugment自动增强:

# over-write `train_pipeline` for new added `AutoAugment` training setting train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='LoadAnnotations', with_bbox=True, with_mask=True), dict( type='AutoAugment', policies=[ [dict( type='Rotate', level=5, img_border_value=(124, 116, 104), prob=0.5) ], [dict(type='Rotate', level=7, img_border_value=(124, 116, 104)), dict( type='TranslateX', level=5, prob=0.5, img_border_value=(124, 116, 104)) ], ]), dict( type='RandomResize', scale=[(2048, 800), (2048, 1024)], keep_ratio=True), dict(type='RandomFlip', prob=0.5), dict(type='PackDetInputs'), ] # set batch_size per gpu, and set new training pipeline train_dataloader = dict( batch_size=1, num_workers=3, # over-write `pipeline` with new training pipeline setting dataset=dict(pipeline=train_pipeline))

AutoAugment的底层实现位于 augment_wrappers.py,它继承自RandomChoice:调用时从policies列表中等概率随机挑选一个增强策略作用于图像。其policies参数结构为List[List[dict]]——外层列表的每个元素是一条候选策略,内层是若干按顺序执行的基础变换。这里定义了两条策略:

  • 策略 1:仅Rotate(level=5,概率 0.5);
  • 策略 2:Rotate(level=7)后接TranslateX(level=5,概率 0.5)。

RotateTranslateX注册于 geometric.py,img_border_value=(124, 116, 104)指定旋转/平移后填充的边界颜色(即图像边缘填充色)。所有几何增强都会同步更新gt_bboxesgt_masks等标注,因此无需担心标注错位问题。

3.4 优化器、学习率与训练循环

# Set optimizer optim_wrapper = dict( type='OptimWrapper', optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) # Set customized learning policy param_scheduler = [ dict( type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), dict( type='MultiStepLR', begin=0, end=10, by_epoch=True, milestones=[8], gamma=0.1) ] # train, val, test loop config train_cfg = dict(max_epochs=10, val_interval=1)
  • 优化器:SGD(lr=0.01, momentum=0.9, weight_decay=0.0001),外层用OptimWrapper包装,这是 MMEngine 的标准写法;
  • 学习率策略:先线性热身(前 500 iter 从0.001倍起步线性上升到目标学习率),再使用MultiStepLR在第 8 个 epoch 将学习率衰减为原来的0.1gamma=0.1),共训练 10 个 epoch;
  • 训练循环max_epochs=10,每个 epoch 结束做一次验证(val_interval=1)。

3.5 加载 COCO 预训练权重

# We can use the COCO pre-trained Cascade Mask R-CNN R50 model for a more stable performance initialization load_from = 'https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth'

load_from指定从 OpenMMLab 模型库下载 COCO 上预训练的 Cascade Mask R-CNN R50 权重文件。与init_cfg(结构初始化)不同,load_from属于权重级别的"迁移初始化",只加载参数、跳过结构差异(例如 head 的类别数不同会导致对应层被自动跳过),配合backbone=dict(init_cfg=None)使用即可实现"COCO 检测器 → Cityscapes 微调"的迁移学习范式。

第四步:训练新模型

配置就绪后,一条命令即可启动训练:

python tools/train.py configs/cityscapes/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py

tools/train.py的入口实现位于 tools/train.py,它提供了一系列实用的命令行参数,可在不修改配置文件的情况下调整训练行为:

参数作用
config(位置参数)训练配置文件路径
--work-dir指定日志与模型权重保存目录
--amp开启自动混合精度训练
--auto-scale-lr根据 batch size 自动缩放学习率
--resume断点续训;不指定值时自动从 work-dir 中最近的 checkpoint 恢复
--cfg-optionskey=value形式覆盖任意配置项(支持嵌套与列表值)
--launcher分布式启动器(none/pytorch/slurm/mpi

日志、checkpoint 和可视化结果默认输出到./work_dirs/<config文件名>/目录,训练过程中的验证指标也会按val_interval=1的设定在每个 epoch 结束后打印。

更详细的多卡训练(如tools/dist_train.sh)与调度器用法,可进一步参考仓库的 训练指南。

第五步:测试与推理

使用tools/test.py加载训练好的权重(例如第 10 个 epoch 的 checkpoint)完成测试:

python tools/test.py configs/cityscapes/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py work_dirs/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes/epoch_10.pth

测试将依据配置中继承自 cityscapes_instance.py 的评测器输出结果——该基础配置同时启用了两个评测器:

  • CocoMetric:基于 COCO 格式标注计算bbox(检测)与segm(分割)指标;
  • CityScapesMetric:输出 Cityscapes 官方风格的实例分割评测结果。

tools/test.py同样支持--cfg-options--launcher等参数,且支持--out保存预测结果、--format-only仅格式化输出用于官方评测提交(配置中注释部分给出了test集与format_only的示例写法)。

对于单张图片的推理演示,仓库 demo 目录 提供了image_demo.py等脚本,可用于快速可视化模型效果。更完整的测试与推理细节可参考 测试指南。

深入理解:这套自定义流程背后的机制

注册机制(Registry)是自定义的基石

MMDetection 3.x 的模块化核心是 MMEngine 的注册表体系。mmdet/registry.py 中定义了MODELSDATASETSTRANSFORMSOPTIMIZERSPARAM_SCHEDULERSHOOKSMETRICS等 17 个注册节点,分别管理模型、数据、变换、优化器、学习率调度器、钩子与评测指标。任何被@REGISTRY.register_module()装饰的类,都可以通过配置文件中的type字符串被实例化——这就是为什么自定义 Neck 只需要"写一个类 + 注册 + 在配置里改 type"三步即可无缝接入既有检测器。

训练管线中的每个环节都可替换

本文示例仅自定义了 Neck 与数据增强,但同样的范式可推广到检测器的每个组件:

  • Backbone:新增骨干网络(对应BACKBONESMODELS注册);
  • Head:自定义检测/分割头(如bbox_headmask_head);
  • Loss:自定义损失函数;
  • Optimizer / 学习率调度器 / Hook:自定义训练时运行配置。

详细的组件定制指南见 Customize Models,运行时设置(优化器、梯度裁剪、训练调度、钩子等)的定制见 Customize Runtime Settings。

总结

本文以"Cityscapes + Cascade Mask R-CNN R50 + AugFPN + AutoAugment"为完整案例,走通了 MMDetection 自定义模型开发的全部环节:数据准备与格式转换 → 基于MODELS注册机制实现新 Neck → 通过_base_继承编写配置文件(替换 Neck、调整类别数、注入自动增强、自定义优化与调度)→ 使用tools/train.py训练 → 使用tools/test.py测试与推理。整个过程清晰地体现了 MMDetection 3.x"注册机制 + 配置驱动"的设计哲学:任何组件都可通过"实现一个类 + 注册 + 修改配置"三步完成替换,而无需改动框架核心代码。这套方法论可直接迁移到 backbone、head、loss、optimizer、hook 等所有组件,是二次开发与算法研究的通用起点。

  • 人工智能
  • 计算机视觉
  • 深度学习
  • 模型评测

【免费下载链接】mmdetection

OpenMMLab Detection Toolbox and Benchmark

项目地址:https://gitcode.com/gh_mirrors/mm/mmdetection
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询