1. 从零认识 Detectron2:它到底解决什么问题
如果你做过目标检测相关的项目,大概率听过 Detectron2 这个名字。它是 Meta(原 Facebook)AI 研究院开源的一个计算机视觉算法库,基于 PyTorch 构建,专门用来做目标检测、实例分割、全景分割、关键点检测这几大类任务。我第一次接触它是在一个工业质检的项目里,当时需要在一批产品图像上定位缺陷区域并分类,试过自己从零搭 Faster R-CNN,光是数据加载和 anchor 生成就写了一周,后来换成 Detectron2,两天就跑通了 baseline。这个效率差距让我决定认真研究它。
Detectron2 的核心价值在于:它把目标检测领域最常用的模型架构(Faster R-CNN、RetinaNet、Mask R-CNN、Cascade R-CNN 等)、数据增强策略、学习率调度、分布式训练、模型导出部署这些环节全部封装好了。你不需要自己去实现 ROI Align、FPN、RPN 这些模块,只需要准备好数据、选一个模型配置、调几个参数就能开始训练。对于刚入门计算机视觉的开发者来说,这大幅降低了从“看懂论文”到“跑出结果”之间的门槛。
但这里有个常见的误解需要提前说清楚:Detectron2 不是那种“一行代码搞定一切”的框架。它的配置系统基于 YAML 文件,模型结构通过注册机制管理,数据集需要按照特定格式注册。如果你不理解它的设计哲学,很容易在配置环节卡住。我见过不少人在 GitHub 上提问“为什么我的训练 loss 不下降”,结果发现是学习率配置写错了位置,或者数据集的类别标签没有对齐。所以这篇文章不只是讲怎么装、怎么跑,更重要的是把配置逻辑和常见坑点讲透。
这篇文章适合三类人:第一类是有一定 PyTorch 基础、想快速上手目标检测的开发者;第二类是用过 Detectron 一代或者其他检测框架、想迁移到 Detectron2 的人;第三类是在实际项目中遇到了配置问题、需要系统梳理排查思路的工程师。我会从环境搭建讲起,然后深入配置系统的设计逻辑,接着用一个小型数据集走完训练和推理的全流程,最后分享一些我在实际项目中踩过的坑和优化经验。
2. 环境搭建:版本匹配比安装步骤更重要
2.1 硬件与基础软件的前置检查
在动手安装之前,有几件事必须先确认。首先是 GPU 环境,Detectron2 虽然支持 CPU 训练,但实际项目中几乎不可能用 CPU 跑目标检测,速度差距在几十倍以上。你需要确认 CUDA 驱动版本和 PyTorch 版本的对应关系。我个人的经验是:先确定你要用的 PyTorch 版本,然后去 PyTorch 官网查对应的 CUDA 版本,最后确认你的显卡驱动是否支持这个 CUDA 版本。这个顺序不能反,否则很容易出现“装完了但 torch.cuda.is_available() 返回 False”的情况。
其次是 Python 版本。Detectron2 对 Python 3.7 到 3.10 的支持比较稳定,3.11 以上在某些依赖包上可能会有兼容性问题。我建议用 conda 创建一个独立环境,避免和系统 Python 或者其他项目的依赖冲突。具体操作如下:
conda create -n detectron2 python=3.9 -y conda activate detectron2创建完环境后,先安装 PyTorch。这里要注意,不要直接用 pip install torch,而是去 PyTorch 官网复制对应的安装命令。比如你需要 CUDA 11.8 的版本,命令大概是这样的:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118安装完成后,用一行代码验证:
import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果第二行输出 True,第三行能打印出你的显卡型号,说明基础环境没问题。如果输出 False,先别急着装 Detectron2,回去检查 CUDA 和 PyTorch 的版本匹配。
2.2 Detectron2 的安装方式选择
Detectron2 的安装有两种方式:pip 安装预编译包和从源码编译。pip 安装最简单:
pip install detectron2 --extra-index-url https://dl.fbaipublicfiles.com/detectron2/wheels/cu118/torch2.0/index.html但这里有个关键点:pip 预编译包只覆盖特定的 PyTorch 和 CUDA 组合。如果你的版本不在列表里,pip 会找不到对应的包。这时候就需要从源码编译:
git clone https://github.com/facebookresearch/detectron2.git cd detectron2 pip install -e .从源码编译的好处是兼容性更好,而且如果你后续需要修改 Detectron2 的源码(比如自定义一些层),直接改就行,不需要重新安装。缺点是编译时间较长,大概需要五到十分钟,取决于机器性能。
我个人的建议是:如果你的 PyTorch 和 CUDA 版本正好在预编译包列表里,优先用 pip 安装,省时间。如果不在,或者你打算深入定制,就从源码编译。另外,从源码编译时如果遇到 “nvcc not found” 的错误,说明 CUDA 工具包没有装或者环境变量没配好,需要先解决这个问题。
2.3 验证安装是否成功
安装完成后,不要急着跑训练,先做一个简单的验证。Detectron2 自带了一些 demo 和测试脚本,可以快速确认核心功能是否正常:
import detectron2 from detectron2 import model_zoo from detectron2.engine import DefaultPredictor from detectron2.config import get_cfg print(detectron2.__version__)如果这行能正常打印版本号,说明基本导入没问题。接下来可以试着加载一个预训练模型:
cfg = get_cfg() cfg.merge_from_file(model_zoo.get_config_file("COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml")) cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml") predictor = DefaultPredictor(cfg) print("模型加载成功")这一步会下载预训练权重,大概几百兆。如果网络环境不好,可能会卡住或者超时。我的做法是提前用下载工具把权重文件下好,然后通过本地路径加载,这样更可控。
注意:Detectron2 的预训练模型权重文件比较大,建议提前规划好磁盘空间和下载方式。如果多次下载失败,可以尝试用命令行工具分段下载。
3. 配置系统的设计逻辑:为什么 YAML 文件这么重要
3.1 配置驱动的设计哲学
Detectron2 最核心的设计理念就是“配置驱动”。整个框架的行为——用什么模型、用什么数据集、学习率多少、训练多少轮、要不要做数据增强——全部由一个 YAML 配置文件控制。这种设计的好处是实验可复现:你只要保存了配置文件,就能精确复现一次训练。坏处是配置项非常多,新手容易迷失。
我刚开始用的时候,面对一个几百行的 YAML 文件完全不知道从哪里下手。后来我总结了一个方法:把配置项分成四层来理解。第一层是模型架构(MODEL 字段),第二层是数据集(DATASETS 字段),第三层是训练策略(SOLVER 字段),第四层是输入输出(INPUT、OUTPUT 字段)。大部分时候你只需要改这四层里的十几个关键参数,其他的保持默认就行。
Detectron2 的配置系统还有一个“继承”机制。你可以先加载一个基础配置(比如 COCO 上预训练的 Faster R-CNN 配置),然后用 merge_from_file 或者直接赋值的方式覆盖特定字段。这样你不需要从零写一个完整的配置文件,只需要写差异部分。这个机制在实际项目中非常实用,因为大部分实验都是在同一个基础配置上做微调。
3.2 关键配置项逐个拆解
下面我用一个实际的例子来说明关键配置项的含义。假设你要在一个自定义数据集上训练 Faster R-CNN:
from detectron2.config import get_cfg from detectron2 import model_zoo cfg = get_cfg() cfg.merge_from_file(model_zoo.get_config_file("COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml")) cfg.DATASETS.TRAIN = ("my_dataset_train",) cfg.DATASETS.TEST = ("my_dataset_val",) cfg.DATALOADER.NUM_WORKERS = 4 cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml") cfg.SOLVER.IMS_PER_BATCH = 4 cfg.SOLVER.BASE_LR = 0.001 cfg.SOLVER.MAX_ITER = 10000 cfg.SOLVER.STEPS = (7000, 9000) cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128 cfg.MODEL.ROI_HEADS.NUM_CLASSES = 5这里有几个参数需要重点解释。SOLVER.IMS_PER_BATCH是总 batch size,注意它和MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE是联动的。前者决定一次迭代处理多少张图,后者决定每张图里采样多少个 ROI 参与损失计算。如果显存不够,优先降低 IMS_PER_BATCH,而不是 BATCH_SIZE_PER_IMAGE,因为后者太低会导致训练不稳定。
SOLVER.BASE_LR是基础学习率,Detectron2 默认会根据 batch size 自动缩放学习率。如果你把 IMS_PER_BATCH 从 16 降到 4,学习率也应该相应降低,否则容易发散。我一般用线性缩放规则:新学习率 = 基础学习率 × (新 batch size / 基础 batch size)。
SOLVER.STEPS是学习率衰减的节点。比如 (7000, 9000) 表示在第 7000 和第 9000 次迭代时把学习率乘以 0.1。这个参数需要根据 MAX_ITER 来设置,一般是 MAX_ITER 的 70% 和 90% 位置。
MODEL.ROI_HEADS.NUM_CLASSES必须和你的数据集类别数完全一致。这里有个容易犯的错误:如果你用的是 COCO 预训练权重,而你的数据集只有 5 个类别,加载权重时 Detectron2 会自动忽略类别数不匹配的层,但你必须显式设置 NUM_CLASSES 为 5,否则会报错或者训练出错误的结果。
3.3 配置文件与代码的分离实践
在实际项目中,我强烈建议把配置写在 YAML 文件里,而不是全部写在 Python 代码中。这样做的好处是:第一,实验记录更清晰,每个实验对应一个 YAML 文件,方便对比;第二,不需要改代码就能调整参数;第三,团队协作时配置可以独立管理。
一个典型的项目结构是这样的:
project/ ├── configs/ │ ├── base.yaml │ ├── experiment_001.yaml │ └── experiment_002.yaml ├── datasets/ │ ├── register.py │ └── data/ ├── train.py └── predict.pybase.yaml 放通用配置,experiment_xxx.yaml 放差异部分。训练脚本里用 merge_from_file 依次加载。这样当你需要跑一组对比实验时,只需要复制 YAML 文件改几个参数就行,代码完全不用动。
提示:Detectron2 的配置系统支持从命令行覆盖参数,格式是
python train.py --config-file configs/exp.yaml SOLVER.MAX_ITER 5000。这个功能在快速调试时非常方便,但正式实验还是建议把参数写进 YAML 文件,保证可复现性。
4. 数据集注册:格式转换与标注处理的实操细节
4.1 Detectron2 要求的数据格式
Detectron2 支持多种数据集格式,但最常用的是 COCO 格式。如果你的数据是其他格式(比如 VOC XML、YOLO txt),需要先转换成 COCO JSON。COCO 格式的核心结构包括 images、annotations、categories 三个数组。images 里记录每张图的文件名、宽高;annotations 里记录每个标注的 bbox、category_id、image_id;categories 里记录类别 id 和名称。
我见过很多人卡在这一步,主要是因为坐标格式搞混了。COCO 的 bbox 格式是 [x, y, width, height],而有些标注工具导出的是 [x1, y1, x2, y2]。如果不转换,训练时框的位置会完全错乱。另外,category_id 必须从 1 开始连续编号,不能有跳号,否则 Detectron2 在计算类别数时会出错。
下面是一个从 VOC 格式转 COCO 格式的简化示例:
import xml.etree.ElementTree as ET import json import os def voc_to_coco(voc_dir, output_json): categories = [{"id": 1, "name": "defect"}, {"id": 2, "name": "scratch"}] images = [] annotations = [] ann_id = 1 for idx, xml_file in enumerate(os.listdir(voc_dir)): tree = ET.parse(os.path.join(voc_dir, xml_file)) root = tree.getroot() filename = root.find("filename").text size = root.find("size") width = int(size.find("width").text) height = int(size.find("height").text) images.append({ "id": idx, "file_name": filename, "width": width, "height": height }) for obj in root.findall("object"): name = obj.find("name").text bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) cat_id = next(c["id"] for c in categories if c["name"] == name) annotations.append({ "id": ann_id, "image_id": idx, "category_id": cat_id, "bbox": [xmin, ymin, xmax - xmin, ymax - ymin], "area": (xmax - xmin) * (ymax - ymin), "iscrowd": 0 }) ann_id += 1 coco_data = { "images": images, "annotations": annotations, "categories": categories } with open(output_json, "w") as f: json.dump(coco_data, f)这段代码的关键点在于 bbox 的转换:COCO 要的是 [x, y, width, height],所以 xmax 要减去 xmin,ymax 要减去 ymin。另外 area 字段虽然不直接影响训练,但某些评估指标会用到,建议填上。
4.2 在 Detectron2 中注册数据集
转换完格式后,需要在代码里注册数据集。Detectron2 提供了 register_coco_instances 函数,用起来很简单:
from detectron2.data.datasets import register_coco_instances register_coco_instances( "my_dataset_train", {}, "datasets/train_annotations.json", "datasets/train_images" ) register_coco_instances( "my_dataset_val", {}, "datasets/val_annotations.json", "datasets/val_images" )注册完成后,可以通过 MetadataCatalog 查看数据集信息:
from detectron2.data import MetadataCatalog metadata = MetadataCatalog.get("my_dataset_train") print(metadata.thing_classes)如果 thing_classes 打印出来是空的或者不对,说明 categories 字段有问题。这时候要回去检查 JSON 文件里的 categories 数组是否正确。
4.3 数据增强的配置与注意事项
Detectron2 内置了多种数据增强策略,通过 DATALOADER 和 INPUT 字段配置。常用的包括随机翻转、随机裁剪、亮度对比度调整、马赛克增强等。我一般会开启随机翻转和亮度调整,这两个对大多数场景都有帮助,而且不会引入太多计算开销。
cfg.INPUT.RANDOM_FLIP = "horizontal" cfg.INPUT.BRIGHTNESS = 0.2 cfg.INPUT.CONTRAST = 0.2 cfg.INPUT.SATURATION = 0.2但要注意,如果你的数据里有方向敏感的类别(比如文字检测),水平翻转可能会把文字变成镜像,反而有害。这种情况下应该关闭翻转或者只做垂直翻转。另外,马赛克增强虽然能提升小目标检测效果,但会显著增加训练时间,建议在数据量不足时再考虑。
还有一个容易被忽略的点:Detectron2 的默认数据增强是在 GPU 上做的,所以 DATALOADER.NUM_WORKERS 不需要设太大,一般 2 到 4 就够了。设太大反而会增加 CPU 和 GPU 之间的数据传输开销。
5. 训练与推理全流程:从启动到出结果
5.1 训练脚本的编写与启动
Detectron2 提供了 DefaultTrainer 类,封装了训练循环、日志记录、模型保存、评估等逻辑。一个最简训练脚本大概长这样:
from detectron2.engine import DefaultTrainer from detectron2.config import get_cfg from detectron2.data.datasets import register_coco_instances import os register_coco_instances("my_train", {}, "train.json", "train_images") register_coco_instances("my_val", {}, "val.json", "val_images") cfg = get_cfg() cfg.merge_from_file("configs/base.yaml") cfg.DATASETS.TRAIN = ("my_train",) cfg.DATASETS.TEST = ("my_val",) cfg.OUTPUT_DIR = "./output" os.makedirs(cfg.OUTPUT_DIR, exist_ok=True) trainer = DefaultTrainer(cfg) trainer.resume_or_load(resume=False) trainer.train()启动训练后,控制台会输出每次迭代的 loss、学习率、耗时等信息。我一般会关注几个指标:total_loss 是否稳定下降、各分支 loss(分类、回归、RPN)是否平衡、学习率是否按预期衰减。如果 total_loss 震荡很大,可能是学习率太高;如果某个分支 loss 一直不降,可能是该分支的权重初始化有问题。
训练过程中,Detectron2 会定期在验证集上跑评估,输出 AP、AP50、AP75 等指标。这些指标比 loss 更能反映模型的实际效果。我通常会在训练结束后,把不同实验的 AP 曲线画在一起对比,这样能直观看出哪个配置更好。
5.2 推理与结果可视化
训练完成后,用 DefaultPredictor 做推理:
from detectron2.engine import DefaultPredictor import cv2 cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "model_final.pth") cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5 predictor = DefaultPredictor(cfg) image = cv2.imread("test.jpg") outputs = predictor(image) print(outputs["instances"].pred_boxes) print(outputs["instances"].scores) print(outputs["instances"].pred_classes)SCORE_THRESH_TEST 是推理时的置信度阈值,默认是 0.05,实际使用时一般调到 0.5 左右。阈值太低会输出大量误检框,太高会漏掉一些目标。我建议先在验证集上画 PR 曲线,找到合适的阈值再固定下来。
可视化可以用 Detectron2 自带的 Visualizer:
from detectron2.utils.visualizer import Visualizer from detectron2.data import MetadataCatalog metadata = MetadataCatalog.get("my_val") visualizer = Visualizer(image[:, :, ::-1], metadata=metadata, scale=1.2) vis_output = visualizer.draw_instance_predictions(outputs["instances"].to("cpu")) cv2.imwrite("result.jpg", vis_output.get_image()[:, :, ::-1])这里注意 image 的通道顺序:OpenCV 读进来是 BGR,Visualizer 期望的是 RGB,所以要转换。另外 outputs["instances"] 在 GPU 上,画图前要 .to("cpu"),否则会报错。
5.3 模型导出与部署准备
如果要把模型部署到生产环境,Detectron2 支持导出为 TorchScript 格式:
from detectron2.export import export_torchscript model = predictor.model model.eval() torchscript_model = export_torchscript(model, image) torchscript_model.save("model.ts")导出后的模型可以用 C++ 或者 Python 的 TorchScript 运行时加载,不依赖 Detectron2 的代码。但要注意,导出后的模型只包含前向推理逻辑,数据预处理(比如 resize、归一化)需要自己实现,并且要和训练时保持一致。我见过有人导出后效果变差,就是因为预处理没对齐。
6. 实际项目中踩过的坑与优化经验
6.1 显存不足的排查与解决
显存不足是训练目标检测模型时最常见的问题。报错信息通常是 “CUDA out of memory”。解决思路有几个层次:第一,降低 IMS_PER_BATCH,这是最直接有效的;第二,降低 ROI_HEADS.BATCH_SIZE_PER_IMAGE,但不要低于 64,否则训练会不稳定;第三,开启梯度累积,用时间换空间;第四,使用混合精度训练,Detectron2 支持 AMP,可以省大约 30% 显存。
混合精度训练的开启方式:
from detectron2.engine import DefaultTrainer from detectron2.config import CfgNode cfg.SOLVER.AMP.ENABLED = True但要注意,AMP 在某些模型结构上可能会导致数值不稳定,如果开启后 loss 出现 NaN,先关掉 AMP 排查其他问题。
6.2 小目标检测效果差的改进思路
小目标检测是目标检测里的经典难题。Detectron2 默认的 FPN 结构对小目标有一定支持,但如果你的数据里小目标占比很高,可能需要额外优化。我试过几个有效的方法:第一,增大输入图像分辨率,比如从 800 调到 1200,但会显著增加显存和计算量;第二,调整 anchor 尺寸,把默认的 anchor 改小,匹配小目标的尺度;第三,使用 Cascade R-CNN,它通过多阶段检测能提升小目标的召回率。
调整 anchor 的配置:
cfg.MODEL.ANCHOR_GENERATOR.SIZES = [[16], [32], [64], [128], [256]] cfg.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.5, 1.0, 2.0]]这里的 SIZES 是每个 FPN 层级的 anchor 尺寸,默认是 [[32], [64], [128], [256], [512]]。如果你的目标普遍偏小,可以把最小的 anchor 从 32 降到 16。
6.3 训练不收敛的常见原因
训练不收敛的表现有很多种:loss 一直很高不降、loss 震荡剧烈、loss 变成 NaN、评估指标始终为 0。我总结了几类常见原因和排查方法。
第一类是数据问题。标注格式错误、类别标签不连续、图片路径不对,都会导致模型学不到东西。排查方法是先用少量数据过拟合,如果连几十张图都过拟合不了,那肯定是数据或配置有问题。
第二类是学习率问题。学习率太大会导致 loss 震荡或发散,太小会导致收敛极慢。我一般会先用一个较小的学习率跑几百次迭代,确认 loss 在下降,然后再逐步调大。
第三类是预训练权重问题。如果加载的预训练权重和模型结构不匹配,Detectron2 会打印警告信息。这时候要检查 MODEL.WEIGHTS 是否指向了正确的文件,以及 NUM_CLASSES 是否设置正确。
第四类是 batch normalization 的问题。当 batch size 很小时,BN 层的统计量不准确,会导致训练不稳定。Detectron2 默认使用 GroupNorm 或者 FrozenBN 来缓解这个问题,但如果你手动改了模型结构,可能需要重新考虑归一化方式。
6.4 推理速度优化的几个手段
在实际部署中,推理速度往往比精度更重要。我试过几个有效的优化手段:第一,降低输入分辨率,这是最直接的,但会牺牲小目标检测能力;第二,使用更轻量的 backbone,比如 ResNet-18 或者 MobileNet;第三,开启 TorchScript 或者 ONNX 导出,利用图优化加速;第四,使用 TensorRT 进一步优化,但这需要额外的转换步骤。
我个人的经验是:如果对精度要求不是极致,ResNet-50 + FPN 在 800 分辨率下,单张图推理时间大约 50 到 80 毫秒(取决于 GPU 型号)。换成 ResNet-18 可以降到 30 毫秒左右,但 AP 会下降 3 到 5 个点。具体怎么取舍,要看你的业务场景对精度和速度的要求。
提示:推理速度测试时要注意 warm-up。第一次推理通常比较慢,因为要初始化 CUDA 内核。建议先跑 10 次预热,再测 100 次的平均时间,这样数据才准确。
7. 从 Detectron2 出发的进阶方向
Detectron2 本身是一个基础框架,围绕它可以做很多扩展。比如自定义新的模型结构,只需要继承 GeneralizedRCNN 或者 RetinaNet 类,然后注册到框架里。再比如自定义数据增强,可以实现一个新的 Augmentation 类,然后在配置里引用。这些扩展能力让 Detectron2 不只是一个“开箱即用”的工具,而是一个可以深度定制的平台。
我在实际项目里做过一个自定义的 ROI Head,用来处理多标签分类的场景。做法是继承 StandardROIHeads,重写 forward 方法,在分类分支上改用 BCE Loss 而不是 CrossEntropy。整个过程不需要改框架源码,只需要在项目里写一个子类,然后通过配置注册进去。这种灵活性是 Detectron2 相比其他检测框架的一个明显优势。
另外,Detectron2 的模型动物园(Model Zoo)提供了大量在 COCO 上预训练的模型,涵盖不同的 backbone 和检测头组合。这些预训练权重可以直接用来做迁移学习,也可以作为 benchmark 对比自己实现的模型。我建议在开始一个新项目时,先从 Model Zoo 里选一个和任务最接近的模型跑 baseline,然后再逐步调整。
最后分享一个我在多个项目中验证过的经验:不要一上来就追求最高的 AP。先把整个流程跑通,确保数据、训练、评估、推理各个环节都没有问题,然后再逐步优化模型和参数。我见过太多人在配置阶段就卡住,花了一两周还没跑出一个能用的模型,最后失去了耐心。Detectron2 的学习曲线确实有点陡,但一旦理解了它的配置逻辑和注册机制,后面的效率会非常高。