目标检测这块,RCNN、DETR、YOLO 这三个名字几乎是绕不开的。不管你是刚入门想搞清楚该从哪下手,还是已经跑过几个项目想回头把知识体系捋顺,这三条技术路线代表的是三种截然不同的设计哲学。RCNN 系列是两阶段检测的开山鼻祖,DETR 把 Transformer 引入检测领域做端到端预测,YOLO 则一路把单阶段实时检测推到今天仍然是最主流的工程选择。我这些年从 YOLOv3 一路跟到 v8、v11,中间也踩过 DETR 训练不收敛、RCNN 环境配到崩溃的坑,这篇文章就把这三条线的核心逻辑、实操要点和我自己踩过的坑一次性讲清楚。
1. 三条技术路线的本质分歧在哪
1.1 从“怎么找目标”这个问题说起
目标检测要解决的核心问题就两个:目标在哪(定位),目标是什么(分类)。三条路线最大的分歧,其实在于它们回答“目标在哪”这个问题的思路完全不同。
RCNN 系列的思路是“先粗筛再精修”。它先想办法在图上生成一大堆可能包含目标的候选区域(Region Proposal),然后对每个候选区域单独做分类和边框回归。这个思路非常符合人的直觉——先扫一眼哪里可能有东西,再凑近了仔细看。但代价就是慢,因为每个候选框都要过一次网络。
YOLO 的思路是“一眼定乾坤”。它把整张图切成网格,每个网格直接预测自己负责的区域里有没有目标、是什么、框在哪。一次前向传播就出结果,所以快。代价是早期版本对小目标和密集目标的效果不如两阶段方法,不过后面几代已经把这个差距补得差不多了。
DETR 的思路最“另类”——它把检测当成一个集合预测问题。用 Transformer 的编码器-解码器结构,直接输出一组固定数量的预测结果,然后通过二分图匹配(Hungarian Matching)把预测和真实标注对应起来。没有锚框,没有 NMS 后处理,真正意义上的端到端。听起来很优雅,但训练收敛慢是它最大的痛点。
1.2 一张表看清三者的设计取舍
| 维度 | RCNN 系列(Faster RCNN) | DETR | YOLO 系列 |
|---|---|---|---|
| 检测范式 | 两阶段 | 端到端集合预测 | 单阶段 |
| 是否需要锚框 | 是(RPN 生成) | 否 | 是(v8 起有 Anchor-Free 分支) |
| 是否需要 NMS | 是 | 否 | 是(部分版本可省略) |
| 推理速度 | 慢 | 中等 | 快 |
| 小目标效果 | 好 | 一般(需多尺度改进) | 中上(v8 以后明显提升) |
| 训练收敛 | 较快 | 很慢(500 epoch 起步) | 快 |
| 部署生态 | 一般 | 一般 | 极其成熟 |
| 适合场景 | 精度优先、离线处理 | 研究、需要端到端 pipeline | 实时检测、工程落地 |
这张表不是让你背的,而是帮你在选型的时候有个判断依据。我见过太多人上来就问“哪个最好”,这个问题本身就不对——没有最好,只有最合适。
1.3 为什么工程落地几乎都选 YOLO
说句实在话,如果你是要把检测模型部署到实际产品里,YOLO 系列基本是默认选项。原因不复杂:部署工具链成熟(TensorRT、ONNX、OpenVINO 都有现成方案)、社区活跃(遇到问题搜得到答案)、预训练模型丰富(COCO 上直接拿来 fine-tune 就行)、推理速度快到可以满足绝大多数实时场景。
Faster RCNN 不是不好,它在精度上仍然有优势,尤其是小目标和遮挡场景。但推理速度摆在那里,1080p 图像在 V100 上跑 Faster RCNN 大概也就十几 FPS,而 YOLOv8 同等条件下轻松上百 FPS。DETR 就更不用说了,原始版本推理速度比 Faster RCNN 还慢,虽然后续有 Deformable DETR、DINO 等改进版本,但工程部署的便利性还是不如 YOLO。
2. RCNN 家族:两阶段检测的进化链条
2.1 从 RCNN 到 Faster RCNN 解决了什么
RCNN 的原始版本流程特别笨重:先用 Selective Search 在图上生成约 2000 个候选区域,然后对每个区域单独跑 CNN 提取特征,最后用 SVM 分类。一张图要跑 2000 次 CNN,速度慢到没法实用。
Fast RCNN 做了第一个关键改进:不再对每个候选区域单独跑 CNN,而是先对整张图跑一次 CNN 得到特征图,然后通过 RoI Pooling 把候选区域映射到特征图上。这样一张图只需要一次 CNN 前向,速度快了很多。但它仍然依赖 Selective Search 生成候选区域,这部分在 CPU 上跑,成了新的瓶颈。
Faster RCNN 的贡献就是用一个可学习的 RPN(Region Proposal Network)替代了 Selective Search。RPN 本身也是卷积网络,和检测头共享特征提取的 backbone,所以整个流程可以在 GPU 上端到端训练。这一步真正让两阶段检测变得实用了。
2.2 RPN 的工作机制拆解
RPN 的核心是在特征图的每个位置上预设一组锚框(Anchor),通常每个位置有 9 个锚框(3 种尺度 × 3 种长宽比)。对每个锚框,RPN 做两件事:
- 判断这个锚框里有没有目标(二分类:前景/背景)
- 回归锚框到真实框的偏移量(dx, dy, dw, dh)
训练时,和真实框 IoU 大于 0.7 的锚框标为正样本,小于 0.3 的标为负样本,中间的忽略。推理时,RPN 输出一堆候选框,经过 NMS 筛选后取前 N 个送给后面的 RoI Head 做精细分类和回归。
这里有个实操中容易忽略的点:锚框的尺度和长宽比需要根据你的数据集调整。如果你检测的都是小目标(比如试卷上的文字区域),默认的锚框尺度可能偏大,需要调小。这个参数在 Detectron2 里是ANCHOR_GENERATOR.SIZES,在 MMDetection 里是anchor_generator的配置。
2.3 Faster RCNN 训练自己数据的实操要点
以 MMDetection 为例,训练自己数据的流程大致是这样:
# 配置文件的核心修改点 model = dict( type='FasterRCNN', backbone=dict( type='ResNet50', depth=50, init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), neck=dict( type='FPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5), rpn_head=dict( type='RPNHead', in_channels=256, feat_channels=256, anchor_generator=dict( type='AnchorGenerator', scales=[4, 8, 16], # 根据目标大小调整 ratios=[0.5, 1.0, 2.0], strides=[4, 8, 16, 32, 64])), roi_head=dict( type='StandardRoIHead', bbox_head=dict( type='Shared2FCBBoxHead', num_classes=你的类别数, # 这里改成实际类别数 reg_class_agnostic=False)))数据集按照 COCO 格式组织,然后改data_root和ann_file路径就行。训练命令:
python tools/train.py configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py有几个坑我踩过:一是学习率不要照搬 COCO 的配置,自己的数据集小的话学习率要降;二是如果类别不均衡严重,分类损失会被多数类主导,可以考虑用 Focal Loss 或者对损失加权;三是数据增强不要开太猛,尤其是 Mosaic 这种,两阶段检测对增强的敏感度和 YOLO 不一样。
3. DETR:端到端检测的理想与现实
3.1 DETR 到底优雅在哪
DETR 的论文我第一次读的时候确实觉得设计很漂亮。它完全抛弃了锚框和 NMS,用 Transformer 的编码器-解码器结构直接输出预测集合。具体来说:
- CNN backbone 提取特征图,展平后加上位置编码送入 Transformer 编码器
- 解码器接收一组可学习的 object queries(通常是 100 个),通过交叉注意力从编码器输出中提取信息
- 每个 query 经过 FFN 输出一个预测(类别 + 边框),多余的 query 预测为“无目标”
训练时的核心是二分图匹配:用 Hungarian 算法把预测和真实标注做最优匹配,然后只对匹配上的对计算损失。这样就避免了 NMS 后处理,也避免了锚框带来的重复预测问题。
3.2 为什么 DETR 训练这么慢
DETR 最被人诟病的就是收敛慢。原始论文在 COCO 上要训 500 个 epoch 才能达到 Faster RCNN 训 100 个 epoch 的效果。原因主要有两个:
一是二分图匹配的不稳定性。训练初期预测结果很乱,Hungarian 匹配出来的对应关系每次都在变,导致优化目标不稳定。二是 Transformer 的交叉注意力需要很长时间才能学会关注到正确的区域。object queries 初始是随机可学习的,它们要学会“各自负责图像的不同区域”需要大量迭代。
Deformable DETR 针对这个问题做了改进,用可变形注意力(Deformable Attention)只关注参考点周围的少量采样点,而不是全局注意力。这样收敛速度提升了 10 倍左右,小目标效果也好了很多。如果你要用 DETR 做实际项目,我建议直接从 Deformable DETR 或者 DINO 开始,不要用原始版本。
3.3 DETR 训练自己数据的完整流程
以 Deformable DETR 为例,训练自己数据的步骤:
第一步,准备 COCO 格式的数据集。DETR 系列对数据格式要求比较严格,标注文件必须是标准的 COCO JSON 格式。
第二步,修改配置文件中的类别数:
# 在 config 中修改 num_classes = 你的类别数 # 注意不包括背景类第三步,调整学习率和训练轮数。DETR 系列通常用 AdamW 优化器,backbone 的学习率要比 Transformer 部分低一个数量级:
optimizer = dict( type='AdamW', lr=2e-4, # Transformer 部分 weight_decay=1e-4, paramwise_cfg=dict( custom_keys={ 'backbone': dict(lr_mult=0.1), # backbone 用 1/10 学习率 }))第四步,训练。Deformable DETR 通常 50 个 epoch 就能收敛得不错,但如果你数据量小,可能需要更多轮。
我实际训练的时候遇到过一个典型问题:loss 震荡特别厉害,尤其是分类 loss。后来发现是 object queries 数量设置不合理——默认 100 个 query,但我数据集里一张图最多就十几个目标,多余的 query 一直在学“预测无目标”,反而干扰了有效 query 的学习。把 query 数量降到 30 之后稳定了很多。
4. YOLO 系列:工程落地的首选方案
4.1 YOLO 的核心设计哲学
YOLO 的核心思想就一句话:把检测当成回归问题,一次前向传播直接输出所有目标的位置和类别。这个思路在 2015 年提出的时候是颠覆性的,因为之前大家都觉得检测必须分两步走。
YOLOv1 把图像分成 7×7 的网格,每个网格预测 2 个边界框和置信度,加上 20 个类别的条件概率。虽然精度不如当时的 Faster RCNN,但速度达到了 45 FPS,碾压级别的快。
后续版本的演进主线很清晰:v2 引入 Anchor Box 和 Batch Normalization,v3 引入 FPN 多尺度预测和更好的 backbone(Darknet-53),v4 堆了一堆技巧(Mosaic 增强、CIoU Loss、DropBlock 等),v5 工程化做得最好(自适应锚框、自动学习边界框回归),v8 回归 Anchor-Free 并统一了检测/分割/分类框架。
4.2 YOLOv8 的训练与部署实操
YOLOv8 是目前工程上最推荐的版本,Ultralytics 的 API 设计得非常简洁。训练自己数据的流程:
from ultralytics import YOLO # 加载预训练模型 model = YOLO('yolov8n.pt') # n/s/m/l/x 可选 # 训练 results = model.train( data='mydata.yaml', # 数据集配置 epochs=100, imgsz=640, batch=16, lr0=0.01, patience=20, # 早停 device=0)数据集配置文件mydata.yaml的格式:
path: /path/to/dataset train: images/train val: images/val nc: 3 # 类别数 names: ['cat', 'dog', 'person'] # 类别名标注格式用 YOLO 格式(每行:类别 x_center y_center width height,都是归一化后的值)。如果你手里是 VOC 格式的标注,可以用官方提供的转换脚本:
# VOC 转 YOLO python tools/voc2yolo.py --voc_dir /path/to/VOC --output_dir /path/to/yolo4.3 YOLO 损失函数的设计逻辑
YOLO 的损失函数是理解它为什么 work 的关键。以 v8 为例,损失由三部分组成:
分类损失用的是 BCE Loss(Binary Cross Entropy),每个类别独立计算二分类损失。这和 Softmax 不同,BCE 允许一个目标同时属于多个类别(多标签场景),也更适合类别不均衡的情况。
边界框回归损失用的是 CIoU Loss(Complete IoU)。相比原始的 MSE 直接回归坐标,CIoU 考虑了三个因素:重叠面积、中心点距离、长宽比一致性。这样梯度更合理,收敛更快。
置信度损失(Objectness)也是 BCE,判断这个位置有没有目标。
# YOLOv8 损失的核心逻辑(简化版) loss_cls = BCE(pred_cls, target_cls) loss_box = CIoU(pred_box, target_box) loss_dfl = DFL(pred_dist, target_dist) # Distribution Focal Loss total_loss = loss_cls + loss_box + loss_dfl这里有个实操经验:如果你的数据集里小目标特别多,可以适当增大 box loss 的权重;如果类别极不均衡,可以给分类损失加权重或者用 Focal Loss 替代 BCE。
4.4 YOLO 部署:从 PyTorch 到 TensorRT
训练完的模型要部署到生产环境,通常需要转成 TensorRT 来加速。以 YOLOv8 为例:
# 导出 ONNX yolo export model=best.pt format=onnx opset=12 # 导出 TensorRT Engine yolo export model=best.pt format=engine half=True device=0导出 TensorRT 的时候有几个参数很关键:
half=True:启用 FP16 精度,速度提升明显,精度损失通常在 1% 以内imgsz=640:输入分辨率,要和训练时一致batch:根据你的 GPU 显存调整workspace:TensorRT 构建时的最大工作空间,默认 4GB,复杂模型可能需要调大
关于推理速度,有个经常被问到的问题:T4 上 1080p 25 帧的视频,用 TensorRT 跑 YOLO 640 分辨率检测能支持多少路?这个要算一下。T4 上 YOLOv8n 在 640 分辨率下 FP16 推理大概 2-3ms 一帧,加上前后处理大概 5ms。25 FPS 意味着每帧 40ms 的预算,理论上单路只占 5ms,那 8 路以内问题不大。但实际还要考虑视频解码、内存拷贝、后处理 NMS 的开销,保守估计 4-6 路比较稳。如果是 YOLOv8m 或更大的模型,路数要相应减少。
4.5 YOLO 实例分割与检测的区别
YOLOv8 除了检测还支持实例分割(yolov8n-seg.pt)。分割和检测的区别在于,分割除了输出边界框和类别,还要输出每个目标的像素级掩码。
分割的训练流程和检测几乎一样,只是标注格式不同——需要多边形标注而不是矩形框。损失函数多了一个掩码损失(通常是 BCE),训练时间会比纯检测长一些。
实际用下来,YOLOv8-seg 的分割精度对于大多数应用场景够用了,但如果你需要非常精细的边缘(比如医学图像分割),还是得上专门的分割模型(如 SAM、Mask2Former)。
5. 训练中那些让人抓狂的问题
5.1 BN 层崩溃到底怎么回事
YOLO 训练中 BN(Batch Normalization)崩溃是个高频问题,表现是 loss 突然变成 NaN,或者 mAP 骤降到 0。根本原因通常是某个 batch 内的统计量异常(方差为 0 或者极大值),导致归一化后的值爆炸。
触发 BN 崩溃的常见原因:
- 学习率太大:梯度爆炸传导到 BN 层,统计量被污染
- batch size 太小:BN 依赖 batch 内统计量,batch 太小统计量不稳定
- 数据异常:输入图像有极端值(全黑、全白、损坏文件)
- 混合精度训练:FP16 的数值范围有限,容易溢出
解决方案我试过有效的几个:
- 降低学习率,尤其是 warmup 阶段要足够长
- 增大 batch size,或者用梯度累积模拟大 batch
- 检查数据集,剔除异常样本
- 用
torch.nn.SyncBatchNorm多卡同步 BN - 混合精度训练时开启
loss_scale动态缩放
5.2 混淆矩阵总合不唯一的排查思路
YOLO 训练完看混淆矩阵,有时候会发现行和列的总和对不上。这个问题通常不是 bug,而是理解上的偏差。
混淆矩阵的行表示真实类别,列表示预测类别。对角线是正确预测,非对角线是误判。行总和 = 该类别的真实样本数,列总和 = 被预测为该类别的样本数。两者不相等是正常的,因为存在漏检(真实有但没预测出来)和误检(预测有但真实没有)。
如果你发现某个类别的行总和远大于列总和,说明这个类别漏检严重;反过来列总和远大于行总和,说明误检多。还有一种情况是背景类(background)的处理方式不同导致的总和不一致,这个要看具体实现。
5.3 小目标检测效果差的系统性解法
小目标检测是目标检测里的老大难问题。YOLO 在小目标上表现不好的原因主要是下采样导致小目标的特征信息丢失严重。一个 16×16 的小目标,经过 32 倍下采样后在特征图上就剩 0.5 个像素了。
我总结的解法按优先级排列:
- 提高输入分辨率:从 640 提到 1280,小目标像素数翻 4 倍,效果立竿见影,代价是速度下降
- 用 P2 层特征:YOLOv8 默认用 P3-P5,加一个 P2( stride=4)检测头专门管小目标
- 数据增强:Mosaic、MixUp 增加小目标出现频率,Copy-Paste 直接复制小目标到不同位置
- 切片推理(SAHI):把大图切成小块分别检测再合并,对超大分辨率图像特别有效
- 调整锚框尺度:如果用了 Anchor-Based 的版本,把最小锚框尺度调小
6. 选型建议与学习路径
6.1 什么场景选什么模型
这个问题我被问过无数次,直接给结论:
- 实时检测、工程部署:YOLOv8 或 YOLOv11,没有悬念
- 精度优先、离线处理:Faster RCNN(Detectron2 版)或 DINO
- 需要端到端无后处理:Deformable DETR 或 DINO
- 小目标密集场景:Faster RCNN + FPN,或者 YOLO + SAHI
- 实例分割:YOLOv8-seg(速度优先)或 Mask2Former(精度优先)
- 研究/发论文:DETR 系列改进空间大,YOLO 创新点难找
6.2 入门学习路径
如果你是刚入门,我建议的学习顺序是:
先跑通 YOLOv8 的官方教程,用自己的数据训一个模型出来,感受一下完整流程。然后回头理解 YOLO 的损失函数和网络结构,搞清楚每个模块在干什么。接着看 Faster RCNN 的论文和代码,理解两阶段检测的设计思路。最后再看 DETR 的论文,理解 Transformer 怎么做检测。
这个顺序的好处是先建立工程直觉,再补理论深度。反过来先啃 DETR 论文容易劝退,因为涉及的东西太多(Transformer、二分图匹配、集合预测),没有检测基础很难理解它到底在解决什么问题。
6.3 预训练模型下载与使用
YOLOv8 的预训练模型在 Ultralytics 的 GitHub Release 页面都能下到,也可以直接用 API 自动下载:
from ultralytics import YOLO model = YOLO('yolov8m.pt') # 自动下载Faster RCNN 的预训练模型在 Detectron2 的 Model Zoo 和 MMDetection 的 Model Zoo 都有,直接下对应的权重文件加载就行。
DETR 系列的预训练模型在 Facebook Research 的 DETR 仓库和 Deformable DETR 仓库有提供,但注意这些模型是在 COCO 上训的,类别是 COCO 的 80 类,用自己数据的时候需要 fine-tune。
最后说一个我自己的体会:这三个模型看起来是竞争关系,实际上在实际项目中经常是互补的。我做过一个电力红外数据集的项目,用 YOLOv8 做实时筛查,对置信度低的区域再用 Faster RCNN 做二次确认,最后 mAP 比单用任何一个都高了 3 个点。所以不要纠结选哪个,先把一个跑通,再根据实际需求组合使用。