很多搞深度学习做物体检测的朋友,应该都听过 mmdetection 这个名字。作为 OpenMMLab 系列里最核心的目标检测工具箱,它把 Faster-RCNN、YOLO、SSD、DETR 这些主流模型都收拢到了一套统一规范的代码框架里,训练、测试、评估、可视化一条龙,几乎不用自己重复造轮子。不少人因为它的安装过程折腾过,有人卡在版本依赖上,有人等了半天编译还没过。这次我以 Faster-RCNN 为例,把从零开始装环境、跑通测试、完成一次训练测试的完整过程捋一遍,包括每一步的底层逻辑和我在实际操作中踩过的坑,希望能帮你少走一段弯路。
这篇内容适合刚接触 mmdetection 的初学者,也适合已经装过但被各种报错打断的老手。不管你是要用现成模型做推理,还是打算在自己的数据集上微调,整条链路都覆盖到了。
1. 项目整体思路与配置选型
1.1 为什么选择 mmdetection 作为检测框架
做物体检测,市面上可选的开源工具其实不少,比如 Detectron2、PaddleDetection、YOLOv5 自带的一套工具链。mmdetection 的优势在于模块化设计,整个框架把数据加载、模型构建、训练策略、评估逻辑拆得非常清楚,每个组件都可以通过配置文件来替换。也就是说,你换模型、换骨干网络、换训练策略,基本不用改主代码,改配置就行。
另一个现实因素是生态。OpenMMLab 系列里的 mmcv、mmdet、mmseg、mmpose 这些工具库共享一套底层风格,你在这个框架里熟悉了配置文件的写法,之后用分割、姿态估计的库会非常顺。对科研和工程落地来说,这种统一性和扩展性是很大的加分项。
Faster-RCNN 本身又是一个非常经典的两阶段检测器,结构上包含骨干网络(如 ResNet)、区域建议网络(RPN)、ROI Head 三个主要部分。拿它来跑通整套流程,能顺便把 mmdetection 的模块组织方式看懂,之后再切到其它模型就只是改配置的事情,收益很高。
1.2 环境版本匹配是最大的隐形成本
mmdetection 安装最核心的问题不是代码本身,而是环境依赖的版本匹配。整个依赖链是 Python -> PyTorch -> CUDA -> mmcv-full -> mmdetection,其中 mmcv-full 又必须和 PyTorch、CUDA 的版本严格对应,这个对应关系在官方文档里是写得很清楚的,但在实际操作里还是容易掉链子。
我建议在动手之前先把环境规划好,不是装到一半发现不兼容再推倒重来。下面这张表是我在 2.x 系列中验证过的一组相对稳妥的版本组合,环境是 Linux + NVIDIA 显卡,具体 CUDA 版本以你机器上 nvidia-smi 显示为准。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Python | 3.7 / 3.8 / 3.9 | 3.8 兼容性最好 |
| PyTorch | 1.10 / 1.11 / 1.12 | 太新或太旧都可能踩坑 |
| CUDA Toolkit | 11.3 / 11.7 | 需与 PyTorch 对应 |
| mmcv-full | 1.5.x / 1.6.x | 必须匹配 PyTorch 和 CUDA |
| mmdetection | 2.25.x | 2.x 版本的最后一个稳定系列 |
| torchvision | 与 PyTorch 版本配套 | 版本不匹配会直接报错 |
有人追求新版本,直接上 PyTorch 2.0 和最新版 mmdetection,结果发现底层 API 变化太大,很多旧教程里的命令已经不适用。新版等于说代码逻辑重写很大一部分,所以如果你是为了跑通流程、做研究验证,用 2.x 这套已经非常成熟的环境就够了。后续稳定了再升级不迟。
2. 安装环境的完整流程与细节
2.1 创建虚拟环境与基础依赖安装
这里我习惯用 conda 来隔离环境,避免把系统自带的 Python 弄乱。以前在入门阶段也试过直接用系统 Python 装,后面不同项目之间的依赖冲突缠到头皮发麻,所以环境隔离这件事别偷懒。
conda create -n openmmlab python=3.8 -y conda activate openmmlab进入环境之后,先装 PyTorch。以 CUDA 11.7 为例,命令是这样:
conda install pytorch==1.12.1 torchvision==0.13.1 cudatoolkit=11.7 -c pytorch -c conda-forge这里有个关键点:PyTorch 的 CUDA 版本和 cudatoolkit 的版本要一致。如果你 nvidia-smi 显示驱动支持的最高 CUDA 是 12.x,不代表 PyTorch 一定要用 12.x,PyTorch 的 CUDA 版本由自身编译时的环境决定,驱动向后兼容。比如 CUDA 11.7 的 PyTorch 在 12.x 驱动的卡上照样能跑,因为驱动本身兼容旧版 CUDA runtime。很多人在这一步误判,把本该用 11.x 的结果装了 12.x,导致后面 mmcv-full 找不到匹配的 wheel。
装完之后验证一下 GPU 是否可用:
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"输出 torch 版本是 1.12.1,同时 cuda.is_available() 是 True,说明环境底层没问题。如果这里返回 False,后面 mmcv-full 编译了也白搭,因为检测不到 GPU。
有个细节值得注意,有些服务器的 CUDA 驱动比较老,比如驱动只支持到 11.4,那你 PyTorch 就得选 11.3 的版本,不能盲目上 11.7。提前执行 nvidia-smi 看一下右上角的 Driver Version 和 CUDA Version,心里有底再装。
2.2 mmcv-full 的安装与踩坑
mmcv-full 是整个框架的底层计算库,负责提供图像变换、卷积算子、分布式训练相关的通用能力。没有它,mmdetection 就像没有地基的房子。
安装方式有两条路线。一种是用预编译的 wheel 包,速度快,适合大多数情况。另一种是从源码编译,性能特化但耗时很长,编译半小时到一小时是常事。
预编译安装的命令格式在官方文档里有明确规则,以 CUDA 11.7 + PyTorch 1.12 为例:
pip install mmcv-full==1.6.2 -f https://download.openmmlab.com/mmcv/dist/cu117/torch1.12/index.html这里有一个我在实战中发现的坑:如果你用国内网络环境,这个 openmmlab 的下载地址可能非常慢,甚至直接超时。解决的思路是加一个国内的镜像源,例如清华源:
pip install mmcv-full==1.6.2 -f https://download.openmmlab.com/mmcv/dist/cu117/torch1.12/index.html -i https://pypi.tuna.tsinghua.edu.cn/simple安装完之后最好验证一下算子是否和本地环境匹配:
python -c "import mmcv; print(mmcv.__version__)"如果导入报错,多半是 CUDA 版本没有匹配上,重新确认 PyTorch 的 CUDA 版本和 mmcv-full 的编译版本是否一致。
顺便说一个选版本的小技巧:不要一概全装最新版。mmcv-full 1.6.2 是我实测下来比较稳的一个版本,和 mmdetection 2.25.0 搭配,基本没什么大毛病。如果你装了最新版 mmcv,可能要求 mmdetection 也升级到 3.x,到时候代码结构都变了,初期成本会高不少。
2.3 mmdetection 源码安装与目录结构说明
mmdetection 的安装我建议用源码方式,因为你后续训练、测试、改配置都需要进入这个目录操作,不是简单 pip install 一个包就能完事。
git clone https://github.com/open-mmlab/mmdetection.git cd mmdetection git checkout v2.25.0 pip install -r requirements/build.txt pip install -v -e .这里解释一下为什么需要git checkout v2.25.0。因为 GitHub 上的最新 master 代码可能和你的 mmcv-full 版本不兼容,切换到 tag 版本能确保配套。pip install -e .的意思是开发模式安装,链接到当前源码目录,你改配置不需要重装,对跑实验的同学来说非常友好。
安装完成后,你会在 mmdetection 目录下看到几个关键子目录,结构大概是:
- mmdet/core:核心组件,包含 anchor、bbox 编解码、优化器、学习率策略等
- mmdet/models:模型定义,backbone、neck、dense_heads、roi_heads 都在这里
- mmdet/datasets:数据集封装和加载逻辑
- configs:各种模型的配置文件模板
- tools:训练、测试、推理的入口脚本
对初学者来说,经常需要接触的是 configs 和 tools 这两个目录,模型的结构配置在 configs 里看,训练和测试命令在 tools 里找。搞清楚这个目录基本盘,后面任何操作都不会迷路。
3. 用 Faster-RCNN 跑通测试流程
3.1 下载预训练模型权重
mmdetection 的训练往往需要从头开始,但在测试阶段,我们可以直接拿官方预训练模型来验证整个环境是否正常工作,顺便体验一下推理效果。
打开配置文件configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py,在文件末尾能看到model相关的配置里有一个init_cfg字段,通常指向某个 url,对应的就是 COCO 数据集上预训练好的 checkpoint 文件。你也可以直接去 OpenMMLab 的模型库页面下载。
我习惯用官方下载脚本一步到位:
python tools/misc/download_dataset.py / # 这个不是必需的 # 更直接的做法是直接用 wget 下载权重 wget https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth权重文件大概 160MB,下载到任意目录都可以。也可以放在 mmdetection 的 checkpoints 目录下,路径自己声明就行。
注意一点:预训练权重是 COCO 80 类的结果,如果你用它做 20 类的数据集训练(比如 VOC),加载权重时最后的类别预测层会出现形状不匹配,mmdetection 会自动跳过不匹配的参数,不用担心。后面讲训练的时候我会补充这部分细节。
3.2 单张图片推理测试
权重下载好之后,用下面的命令跑一张示例图片。mmdetection 官方自带一张 demo 图片,在demo/demo.jpg:
python demo/image_demo.py demo/demo.jpg \ configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py \ checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \ --device cuda:0默认输出图片保存在demo/目录下,文件名是带_bbox后缀的结果图。你可以直接打开看一眼检测框效果,如果画面上能框出人、车等物体,说明整条推理通路没有问题。
这里顺便解释一下为什么 demo 能画出检测框:整个推理链路经过 image 读取、数据预处理(resize、归一化)、模型前向传播、post-processing(NMS 去重)、可视化绘制这几个环节。你看到的框其实是 NMS 处理之后剩下的高置信度结果,每类物体还给不同的颜色区分。
如果这一步报错,常见的是权重路径写错或者 config 和权重版本不匹配,检查一下路径和配置文件里的 checkpoint url 是不是一致的。
3.3 批量测试与精度评估
除了单张图可视化,更严谨的做法是用测试集跑一遍 mAP 评估。这个环节能验证不只前向推理,还包括后处理、评测逻辑整个流程。
假设要评估 COCO 数据集上的效果,先用官方脚本下载一个小的子集数据,或者直接用你已有 COCO 格式数据集的验证集。命令如下:
python tools/test.py \ configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py \ checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \ --eval bbox这里--eval bbox表示用 bbox 检测结果作为评价指标。测试完成后,终端会输出类似这样的评估指标:
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.371 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.560 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.400这些都基于 COCO 标准评测协议算出。Faster-RCNN R50-FPN 在这个结果范围是正常的,说明一切无异常。第一次跑通这个流程,看到这些数字,心里就会踏实很多。
4. 以 Faster-RCNN 为例的训练测试完整流程
4.1 准备自己的数据集并转为 COCO 格式
如果你只是跑通 demo 测试,那已经算完成了验证环节。但 mmdetection 真正的价值是在自己的数据集上训练模型。这里用 VOC 格式还是 COCO 格式,取决于你的原始标注情况。我以 COCO 格式为例来讲,因为这个格式信息最全,mmdetection 支持也最好。
COCO 格式的标注文件是一个 JSON 文件,核心结构大概长这样:
{ "images": [ {"id": 1, "file_name": "000001.jpg", "width": 500, "height": 375} ], "annotations": [ {"id": 1, "image_id": 1, "category_id": 1, "bbox": [100, 100, 200, 150], "area": 30000, "iscrowd": 0} ], "categories": [ {"id": 1, "name": "cat"} ] }这里的 bbox 格式是[x, y, width, height],原点是左上角。很多标注工具导出的是[x1, y1, x2, y2]格式,转换时别忘了把右下角坐标减去左上角坐标得到宽和高。曾经见过一个同学数据集转换时坐标搞混,训练出来的模型框全是偏移的,调了很久才找到原因。
数据目录结构我建议这样组织:
data/ coco/ annotations/ instances_train2017.json instances_val2017.json train2017/ 000001.jpg 000002.jpg ... val2017/ 000001.jpg ...如果你只有一小批数据,不需要严格按照年份目录命名,只要 config 里的 data_root 指向正确即可。mmdetection 对data_root的要求很灵活,annotations 和 images 的相对路径可以在配置里自己改。
4.2 修改 Faster-RCNN 配置文件
mmdetection 的配置设计采用的是继承机制,不必把整个模型配置从头写一遍。官方提供的faster_rcnn_r50_fpn_1x_coco.py已经包含完整结构,我们只需要新建一个自己的配置文件,继承官方的基础配置,再覆盖我们需要的字段。
打开官方配置文件,里面有几个关键部分需要理解:
model字段:定义整个检测模型的网络结构,backbone 是 ResNet50,neck 是 FPN,rpn_head 是区域建议网络,roi_head 是检测头dataset_type和data字段:指定数据集的格式、路径、训练和验证的划分等optimizer和lr_config:定义优化器(通常是 SGD)和动态学习率调整策略runner字段:定义训练的总轮数,max_epochs=12表示 12 个 epoch
如果要把类别从 COCO 的 80 类改成自己的 N 类,必须在model部分做两处修改:一处是rpn_head里的num_classes(通常是 1 或 2,因为 RPN 只判断前景背景),一处是roi_head里的bbox_head的num_classes,这个要改成 N+1(背景也算一类)。具体来说:
model = dict( roi_head=dict( bbox_head=dict( type='Shared2FCBBoxHead', num_classes=20, # 改成自己的类别数+背景 ) ) )只改分类头数字还不够,dataset部分也要同步修改。新建一个配置文件,比如my_faster_rcnn_r50_fpn_1x_coco.py:
_base_ = './faster_rcnn_r50_fpn_1x_coco.py' model = dict( roi_head=dict( bbox_head=dict( num_classes=20 ) ) ) data = dict( samples_per_gpu=2, workers_per_gpu=2, train=dict( classes=('cat', 'dog', ...), # 你的类别列表 ann_file='data/coco/annotations/instances_train2017.json', img_prefix='data/coco/train2017/' ), val=dict( classes=('cat', 'dog', ...), ann_file='data/coco/annotations/instances_val2017.json', img_prefix='data/coco/val2017/' ), test=dict( classes=('cat', 'dog', ...), ann_file='data/coco/annotations/instances_val2017.json', img_prefix='data/coco/val2017/' ) )这里samples_per_gpu表示每张显卡每步的样本数,如果你的显存只有 11GB,建议设成 1 或 2,避免 OOM。workers_per_gpu是每个 GPU 的 dataloader 加载线程数,设置太高有时反而不稳定,根据自己的 CPU 核数调整。
4.3 启动训练与日志观察
配置文件准备好了,就可以跑训练。单卡训练是最省事的,命令:
python tools/train.py my_faster_rcnn_r50_fpn_1x_coco.py --work-dir work_dirs/my_experiment--work-dir指定模型权重和日志输出目录,建议每次实验一个目录,后续对比不同实验方便。如果不指定,默认写在work_dirs/下,以配置文件名为子目录。
训练启动后,终端会先打印模型结构信息、数据集的统计、学习率配置等。然后每个 iteration 会输出一行日志,包含 loss 值、学习率、数据加载时间等。第一次训练时看到类似这样的输出:
2023/05/20 10:00:01 - MMDetection - INFO - Epoch [1][ 50/500] lr: 0.0100, eta: 2:00:00, time: 0.500, data_time: 0.100, memory: 3500, loss_rpn_cls: 0.6920, loss_rpn_bbox: 0.2102, loss_cls: 0.8421, loss_bbox: 0.2311, loss: 1.9754loss 的下降趋势能直接说明模型学得是否正常。正常情况下 loss_rpn_cls 会从接近 0.69(随机初始化的前景概率)逐渐下降,loss_cls 也会稳步走低。如果 loss 不降反升,或者直接 NaN,通常是学习率过大或者数据标注有问题,需要停下来排查。
一个常规技巧是先用小规模数据做一次过拟合验证,只取 10 张图片训练 50 轮,看看训练 loss 能不能降到很低。如果过拟合验证都能通过,再把数据量恢复到完整训练集,这样可以把大部分低级错误提前暴露掉。
4.4 训练后测试与模型评估
训练完成后,checkpoints 目录下会有最新的权重文件。测试命令基本一致,只是把配置文件换成你的实验配置,权重指向训练出来的 best 或 latest:
python tools/test.py \ my_faster_rcnn_r50_fpn_1x_coco.py \ work_dirs/my_experiment/latest.pth \ --eval bbox如果 val 集也是 COCO 格式,会输出 mAP 等一系列指标。这个数字比预训练模型在你数据上的数字重要得多,因为它是针对你的任务专门训练出来的效果。
顺便提醒一下,latest.pth会保留最后一轮的权重,而best_bbox_mAP_epoch_*.pth是验证集上表现最好的权重。实际部署建议用 best 模型,而不是 latest。有时候最后一轮过拟合,表现反而比中间某轮差,这是训练中非常常见的情况。
训练中断了也别慌,mmdetection 支持断点续训。用--resume-from参数指定中断前保存的权重路径即可:
python tools/train.py my_faster_rcnn_r50_fpn_1x_coco.py --resume-from work_dirs/my_experiment/latest.pth这个功能非常实用,特别是训练时间较长的模型,服务器偶尔重启或者 GPU 被你同事占用,不用担心前面的训练白做了。
5. 常见问题与排查技巧实录
5.1 环境安装阶段的典型报错
安装过程中遇到的问题,大部分集中在版本兼容上。这里整理几个我亲身经历、也经常在交流群里看到的典型场景。
问题一:import mmcv 失败,提示找不到指定模块
这个通常有两个来源:一是 mmcv-full 没有真正装上,pip list 里能看到名字但导入失败;二是 mmcv 与 mmcv-full 混装,两个包同时存在于环境中,互相冲突。
解决思路:先卸载干净再装。
pip uninstall mmcv mmcv-full -y pip install mmcv-full==1.6.2 ...问题二:CUDA error: no kernel image is available for execution on the device
这是典型的版本不匹配。PyTorch 编译的 CUDA 版本和你机器实际驱动/硬件支持不一致。比如你用 CUDA 10.2 编的 PyTorch,但显卡是 Ampere 架构,就会出这个问题。
我建议做法:重新创建一个干净环境,按照表格里的版本重新装一遍,别在原环境上反复折腾,时间成本更高。
问题三:编译 mmcv-full 卡在 ninja 阶段
如果是源码编译 mmcv-full,在编译阶段卡顿非常正常,尤其 CPU 核数不多、内存有限的情况下。源码编译本来就不是最优选择,尽量用官方预编译 wheel,又快又省事。如果必须源码编译,可以设置MMCV_WITH_OPS=1 FORCE_CUDA=1环境变量,或者耐心等待,不要中途打断,否则可能要重新开始。
5.2 训练阶段的常见异常与排查
训练过程中除了版本问题,更多的是数据集和超参数相关的问题。
问题四:loss 为 NaN
这个问题的原因比较多。常见的是学习率设得太大,导致梯度爆炸。还有一个很容易忽略的点是配置文件里norm_cfg的type='BN'在 batch size 很小时不稳定,此时可以换成SyncBN或者用GroupNorm。如果训练数据里某些图片全黑或全白,归一化后可能出现极端值,也会引起 NaN。
问题五:CUDA out of memory
显存不足时最简单的手段是减小samples_per_gpu,从 2 改成 1,或者减小输入图像尺寸。mmdetection 的配置里可以通过修改data.train.pipeline中的Resize来缩小长边,例如从 1333 改成 1000,显存需求会下降不少。
实际项目里,我经常用一张 2080Ti(11GB)训练 Faster-RCNN R50-FPN,samples_per_gpu=2刚好打满,如果显存更紧张就设成 1,配合梯度累积效果也不错。梯度累积在 mmdetection 里没有直接给出现成参数,需要自定义optimizer_config=dict(type='GradientCumulativeOptimizerHook', cumulative_iters=2),这块稍微进阶一些,新手不一定要马上用,但知道有这条路就行。
问题六:loss 不下降
如果 loss 长时间在一个水平线上震荡,几乎不动,先检查数据标注里 bbox 的坐标有没有大量为 0 或者超出图像边界。这类脏数据对训练伤害很大。还有一个可能是学习率太低,SGD 的初始学习率一般参考配置的 0.02,但如果你 batch size 减半了,学习率也应该对应调整,经验做法是 batch size 减半,学习率也减半,否则模型收敛会变慢。
5.3 数据与标注相关的隐蔽坑
数据问题是最难排查的,因为它不直接报错,而是模型默默学错。
一个很常见的隐蔽问题是标注类别索引错位。COCO 格式的类别 id 从 1 开始,不同于 PyTorch 的 0 索引,有些人在转换数据时忘记这个规则,导致真实标注类别和配置里的类别对不上,模型学出来的 bbox 位置可能是对的,但类别全乱了。
规避办法很简单,训练之前先做一个可视化检查,把配置里的test_pipeline中的转换开关打开,在数据集上随机画几张图,看看标注框和类别名称是否和原图内容吻合。这一步花不了几分钟,却能省下后面好几天的排查时间。
| 症状 | 可能原因 | 排查建议 |
|---|---|---|
| loss 快速下降到 0 但 mAP 很低 | 数据标注坐标错乱 | 可视化标注是否偏移 |
| 训练 loss 在某个 epoch 突然飙升 | 学习率策略突变 | 检查 lr_config 的分段设置 |
| 测试结果没有框 | 模型权重与配置不匹配 | 重启一个干净的测试流程 |
| 多个类别的框完全重叠 | anchor 尺度设置不合理 | 检查 anchor_generator 的 scale 参数 |
6. 个人经验分享与总结建议
从安装到训练测试完整跑完一遍,你会对 mmdetection 的理解上一个层级。但实际上很多细节值得深入体会。
我个人的体会是,整个流程里花费时间最多的不是训练本身,而是环境和数据的准备。环境问题本质上是一次性的,环境配好之后可以用很久;数据问题则是反复性的,每次换新数据集都可能遇见新的格式坑。
一个小建议:新建一个自己的配置文件仓库,把常用的数据集路径、类别列表、常用 backbone 组合都整理清楚。后面每次做新项目,直接往继承的基础配置上叠加覆盖字段,能省掉大量重复劳动。
再分享一个实用技巧:在训练之前一定要跑一次完整的tools/test.py,哪怕只是拿预训练权重在你的数据上测试一下,这样可以验证数据加载、batch 组装、评估流程是否都通顺。如果你直接训练,跑到一半才发现测试代码跑不通,前面几小时的训练就白费了,这在团队协作时尤其重要,因为队友不一定熟悉你的数据集格式。
刚开始接触 mmdetection 的同学,不用急着把里面每个模块都研究透。先把一条主线跑通,就是"环境 -> 测试 -> 训练 -> 评估"这个闭环,之后再逐步往里面加细节。经验积累是滚雪球的过程,第一次跑通是最难的,后面越走越顺。