目标检测这块,RCNN、DETR、YOLO 是绕不开的三座大山。我最早接触的是 YOLO,那会儿还在用 v3 跑一些简单的车辆检测,后来因为项目需要做高精度的小目标识别,被迫回头啃了 Faster R-CNN 的两阶段逻辑,再后来 DETR 出来,Transformer 直接端到端出框,确实把检测流程简化了一大截。这三条技术路线各有各的脾气,选错了轻则精度上不去,重则训练直接崩掉。下面我按自己的实际使用经验,把这三类算法的核心机制、训练部署要点、以及踩过的坑逐一拆开讲,适合刚入门目标检测的开发者,也适合正在做模型选型的技术负责人参考。
1. 三条技术路线的本质差异
1.1 RCNN 家族:从选择性搜索到区域建议网络
RCNN 系列的核心思路是“先找候选区域,再对每个区域做分类和回归”。最早的 RCNN 用选择性搜索(Selective Search)生成约 2000 个候选框,然后逐个送入 CNN 提取特征,最后用 SVM 分类。这个流程慢得离谱,一张图推理要几十秒,根本没法落地。Fast R-CNN 做了改进,把整张图先过一遍 CNN 得到特征图,再通过 RoI Pooling 从特征图上抠出每个候选区域的特征,分类和回归共用同一个网络,速度提升了一个量级。但候选框的生成仍然依赖外部算法,瓶颈还在。
Faster R-CNN 真正把检测变成了端到端可训练的结构,关键就是 RPN(Region Proposal Network)。RPN 在特征图上用滑动窗口生成锚框(Anchor),每个锚框输出前景/背景二分类和四个偏移量,筛选出约 300 个高质量候选框送给后面的 RoI Head 做精细分类和坐标回归。整个网络可以联合训练,推理速度在 GPU 上能到 5-10 FPS(取决于骨干网络)。我实测过 ResNet-50 骨干的 Faster R-CNN 在 1080p 图像上单卡 T4 大概 7 FPS 左右,精度确实比同期的 YOLO 高,尤其是小目标和密集场景。
RCNN 家族还有一个变体叫 Mask R-CNN,在 Faster R-CNN 基础上加了一个分割分支,可以同时输出检测框和像素级掩码。这个在需要实例分割的场景里非常实用,比如工业质检中要精确测量缺陷面积,Mask R-CNN 就是首选。但它的计算量也更大,训练时显存占用比 Faster R-CNN 高出约 30%。
1.2 DETR:Transformer 带来的端到端革命
DETR(DEtection TRansformer)是 Facebook 在 2020 年提出的,它彻底抛弃了锚框和非极大值抑制(NMS),把检测变成了一个集合预测问题。整体结构很简洁:CNN 骨干提取特征,Transformer 编码器增强特征,解码器用一组可学习的对象查询(Object Queries)去“询问”图像里有什么物体,最后每个查询输出一个框和类别。训练时用匈牙利算法做二分图匹配,把预测框和真实框一一对应,再计算损失。
DETR 最大的优势是流程干净,没有锚框、没有 NMS,超参数少,端到端训练。但它的缺点也很明显:收敛极慢,原始 DETR 需要 500 个 epoch 才能收敛,而 YOLO 通常 100-300 个 epoch 就够了。另外 DETR 对小目标的检测效果一般,因为 Transformer 编码器对高分辨率特征图的计算复杂度是平方级的,通常只能下采样到 1/32 分辨率,小目标信息丢失严重。后来 Deformable DETR 通过可变形注意力机制把收敛速度提升了 10 倍,小目标精度也上来了,算是目前比较实用的 DETR 变体。
DETR 训练自己的数据时,有一个坑特别容易踩:类别不平衡。因为匈牙利匹配是一对一的,每个真实框只能匹配一个预测框,如果某个类别样本很少,模型很容易忽略它。我的做法是在损失里给稀有类别加权,或者用 Focal Loss 的思路调整分类损失。另外 DETR 的学习率需要 warmup,前 10 个 epoch 从 1e-6 线性升到 1e-4,否则训练很容易发散。
1.3 YOLO:回归问题的极致工程化
YOLO 把检测直接建模为回归问题,一张图送入网络,输出就是每个网格的类别概率和边界框坐标。从 v1 到 v8,再到现在的 v11、v12,YOLO 的演进方向一直是“更快更准更易部署”。YOLOv1 的精度一般,但速度极快;YOLOv3 引入 FPN 和多尺度预测,精度大幅提升;YOLOv5 工程化做得最好,代码结构清晰,训练脚本开箱即用;YOLOv8 换成了 Anchor-Free 结构,损失函数用 Task-Aligned Assigner 做正负样本匹配,精度和速度都达到了很好的平衡。
YOLO 的损失函数是核心,不同版本差异很大。YOLOv3 用 MSE 做坐标回归,交叉熵做分类,二值交叉熵做置信度。YOLOv5 改用 CIoU Loss 做坐标回归,分类和置信度用 BCEWithLogits。YOLOv8 进一步用 DFL(Distribution Focal Loss)做框回归,把坐标预测变成分布预测,精度更高。我实测下来,YOLOv8 在 COCO 上比 YOLOv5 的 mAP 高了约 2-3 个点,推理速度基本持平。
YOLO 的部署生态也是最成熟的。TensorRT 加速后,T4 上跑 YOLOv8n 在 640 分辨率下可以到 1000+ FPS,1080p 输入大概 200-300 FPS。如果是多路视频流检测,比如 1080p25 帧的视频,用 TensorRT 加速的 YOLOv8s 在 T4 上大概能支持 8-12 路同时检测,具体取决于模型大小和后处理优化程度。这里有个经验:多路场景下,预处理和后处理往往是瓶颈,用 CUDA 核函数做 letterbox 和 NMS 能显著提升吞吐。
2. 训练自己的数据:从数据准备到模型收敛
2.1 数据标注的格式选择与转换
训练自己的数据,第一步是标注。常用的标注工具是 LabelImg(矩形框)和 LabelMe(多边形分割)。标注格式有 VOC XML、COCO JSON、YOLO TXT 三种。VOC 格式最通用,COCO 格式适合实例分割和多人协作,YOLO 格式最简洁,每行是class_id x_center y_center width height,坐标都归一化到 0-1。
我一般先用 LabelImg 标 VOC 格式,然后用脚本转成 YOLO 或 COCO。转换时最容易出错的是坐标归一化,尤其是图像尺寸不一致时。我的做法是写一个统一的转换脚本,读取每张图的宽高,再计算归一化坐标。另外类别 ID 的映射要固定,不能训练时用一套,推理时用另一套,否则结果全乱。
对于实例分割任务,YOLOv8-seg 需要多边形标注,格式是class_id x1 y1 x2 y2 ... xn yn,坐标同样归一化。标注时要注意多边形必须闭合,且不能自交,否则训练时会报错。我踩过一次坑,标注的多边形有重叠,训练时 loss 一直震荡,排查了半天才发现是标注问题。
2.2 数据增强的策略与陷阱
数据增强是提升模型泛化能力的关键。YOLO 默认开启 Mosaic 增强,把四张图拼成一张,增加小目标和上下文多样性。但 Mosaic 有一个副作用:如果数据集本身标注质量不高,Mosaic 会把错误标注也拼进去,导致模型学到错误特征。我的经验是,前 10 个 epoch 先关掉 Mosaic,让模型先学好基础特征,再开启 Mosaic 做精细调优。
另外 HSV 增强(色调、饱和度、亮度)对颜色敏感的类别要慎用。比如检测交通信号灯,色调变化太大会让红灯变绿灯,模型直接学废。我的做法是对这类类别单独设置增强参数,或者干脆关掉 HSV 增强。随机翻转也要注意,水平翻转对大多数类别没问题,但垂直翻转对“上下”有语义的类别(比如天空、地面)会引入错误标签。
2.3 训练中的 BN 崩溃与学习率调优
YOLO 训练中 BN(Batch Normalization)崩溃是个常见问题,表现为 loss 突然变成 NaN,或者 mAP 骤降到 0。根本原因通常是 batch size 太小,BN 的统计量估计不准。YOLOv8 默认 batch size 是 16,如果显存不够只能降到 8 或 4,这时 BN 就容易出问题。解决方案有两个:一是改用 SyncBN,多卡同步统计量;二是冻结 BN 层,用预训练模型的统计量。我一般优先用 SyncBN,如果只有单卡,就冻结 BN 并降低学习率。
学习率方面,YOLO 用余弦退火调度,初始学习率 0.01,warmup 3 个 epoch。如果训练时 loss 震荡厉害,先把学习率降到 0.001 试试。DETR 的学习率要小得多,初始 1e-4,骨干网络用 1e-5,因为 Transformer 对学习率非常敏感。Faster R-CNN 的学习率通常是 0.02(SGD)或 1e-4(Adam),具体看优化器选择。
3. 部署与推理优化:从 PyTorch 到 TensorRT
3.1 模型导出与格式转换
训练完的 PyTorch 模型要部署,第一步是导出成 ONNX。YOLOv8 用model.export(format='onnx')一行搞定,但要注意 opset 版本,建议用 12 或 13,太低不支持某些算子,太高 TensorRT 可能不认。导出后可以用 Netron 可视化检查结构,确认输入输出节点名称。
ONNX 转 TensorRT 用trtexec工具,命令大概是:
trtexec --onnx=yolov8s.onnx --saveEngine=yolov8s.engine --fp16 --workspace=4096--fp16开启半精度,速度能提升 1.5-2 倍,精度损失通常小于 1 个点。--workspace是显存上限,单位 MB,设太小会转换失败。我一般设 4096 或 8192,看模型大小。
DETR 导出 ONNX 稍微麻烦一点,因为 Transformer 的动态形状支持不好。我的做法是固定输入尺寸,比如 800x1333,导出静态图。Faster R-CNN 的 ONNX 导出更复杂,RPN 和 RoI Align 算子在不同 opset 下行为不一致,建议用 mmdeploy 或 torchvision 自带的导出脚本。
3.2 多路视频流检测的吞吐优化
多路视频流检测是实际项目中最常见的需求,比如监控场景要同时处理 8 路 1080p 摄像头。瓶颈通常在三个地方:解码、预处理、后处理。解码用 NVDEC 硬件加速,预处理用 CUDA 核函数做 letterbox 和归一化,后处理用 GPU 版的 NMS。TensorRT 只负责推理,前后处理如果还在 CPU 上跑,整体吞吐会被拖垮。
我实测过 T4 上跑 YOLOv8s,640 分辨率,FP16 精度,纯推理能到 800 FPS 左右。但加上解码和前后处理,单路 1080p25 帧的视频大概占用 80-100 FPS 的推理能力,所以理论上能支持 8 路左右。如果换成 YOLOv8n,能支持 15 路以上。这里的关键是批处理(Batch Inference),把多路视频的帧拼成一个 batch 一起推理,能显著提升 GPU 利用率。但 batch size 太大会增加延迟,需要根据实际场景权衡。
3.3 一键部署脚本的编写要点
一键部署脚本能省很多事,但写的时候要注意环境隔离。我一般用 Docker 封装,基础镜像选nvidia/cuda:11.8-cudnn8-runtime-ubuntu22.04,然后装 TensorRT、OpenCV、PyTorch。Dockerfile 里要固定版本号,不能写latest,否则下次构建可能就挂了。
脚本的核心步骤是:检查 GPU 驱动、安装依赖、下载模型权重、导出 ONNX、转换 TensorRT 引擎、启动推理服务。每一步都要有错误处理和日志输出,方便排查。我习惯在脚本里加一个--dry-run参数,只打印步骤不实际执行,方便调试。
4. 模型选型的决策框架与实战建议
4.1 精度优先还是速度优先
选模型之前先问自己三个问题:精度要求多高?速度要求多快?部署环境是什么?如果精度优先,比如医疗影像或工业质检,Faster R-CNN 或 Mask R-CNN 是稳妥选择,mAP 通常比 YOLO 高 3-5 个点。如果速度优先,比如实时监控或自动驾驶,YOLOv8 是首选,TensorRT 加速后轻松过百帧。如果两者都要兼顾,可以考虑 Deformable DETR 或 YOLOv8x,但需要更强的 GPU。
我做过一个对比测试,在自建的电力红外数据集上(约 5000 张图,5 个类别),Faster R-CNN ResNet-50 的 mAP@0.5 是 0.82,YOLOv8s 是 0.79,DETR 是 0.76。但推理速度上,YOLOv8s 是 200 FPS,Faster R-CNN 是 8 FPS,DETR 是 15 FPS。所以如果场景允许 10 FPS 左右的推理速度,Faster R-CNN 的精度优势值得考虑;如果要实时,YOLO 是唯一选择。
4.2 小目标检测的针对性优化
小目标检测是三大算法的共同难点。YOLO 的解决方案是多尺度预测,在 P3、P4、P5 三个尺度上分别检测小、中、大目标。但 P3 特征图的分辨率是 80x80(640 输入),每个格子对应 8x8 像素,如果目标小于 8 像素,基本检测不到。我的做法是增加 P2 层(160x160),或者用切片推理(SAHI),把大图切成小图分别检测再合并。
Faster R-CNN 对小目标的处理更好,因为 RPN 生成的锚框可以设置很小的尺寸,比如 16x16。但锚框太小会导致正样本太少,训练困难。我的经验是锚框尺寸要根据数据集统计来设,用 K-means 聚类真实框的宽高,选 9 个聚类中心作为锚框尺寸。
DETR 的小目标检测最弱,因为编码器下采样到 1/32,小目标特征几乎丢失。Deformable DETR 通过多尺度可变形注意力缓解了这个问题,但计算量也上去了。如果小目标很多,建议直接用 YOLO 或 Faster R-CNN,别跟 DETR 死磕。
4.3 模型蒸馏与剪枝的实用技巧
模型太大跑不动,蒸馏和剪枝是两个常用手段。蒸馏是用大模型(教师)指导小模型(学生)训练,让学生模型逼近教师模型的输出分布。YOLO 蒸馏我试过用 YOLOv8x 教 YOLOv8n,mAP 提升了约 2 个点,推理速度不变。关键是损失函数的设计,除了硬标签损失,还要加软标签损失(KL 散度),温度参数 T 一般设 3-5。
剪枝是去掉冗余的通道或层,减少计算量。结构化剪枝对 YOLO 比较友好,因为卷积层通道数多,剪掉 30% 的通道,速度能提升 20-30%,精度损失控制在 1 个点以内。但剪枝后要微调,否则精度掉得厉害。我一般用 L1 范数排序通道重要性,剪掉最小的 20-30%,然后微调 50 个 epoch。
5. 常见训练故障的排查链路
5.1 混淆矩阵不唯一:标注与匹配的隐藏问题
YOLO 训练完看混淆矩阵,有时候会发现同一个类别在不同运行中结果不一致,或者矩阵总和跟验证集样本数对不上。这个问题我排查了很久,最后定位到两个原因:一是数据加载器的 shuffle 和随机增强导致每次验证的样本顺序不同,如果验证集有重复图片或标注错误,结果就会波动;二是 NMS 的 IoU 阈值设置不当,导致某些框被错误抑制或保留。
解决方案是固定验证集的随机种子,关掉验证时的数据增强,然后检查标注文件是否有重复或漏标。另外混淆矩阵的总和应该等于验证集的真实框总数,如果对不上,说明有框被漏检或误检到其他类别。我一般会导出所有预测框和真实框,用脚本逐张对比,找出差异最大的图片重点检查。
5.2 训练中 loss 突然飙升的应急处理
训练到一半 loss 突然飙升,甚至变成 NaN,这是最让人头疼的问题。常见原因有:学习率太大、梯度爆炸、数据中有脏样本、BN 统计量崩溃。我的排查顺序是:先看 loss 曲线,如果是缓慢上升,大概率是学习率问题,降学习率重启训练;如果是突然飙升,检查最近加入的数据是否有标注错误;如果 loss 直接 NaN,检查梯度范数,加梯度裁剪(gradient clipping)。
梯度裁剪在 YOLO 和 DETR 训练中都很重要,我一般设 max_norm=10.0。另外混合精度训练(AMP)有时会导致 NaN,可以先用 FP32 跑几个 epoch 确认模型没问题,再开 AMP。如果 AMP 下 NaN,试试把 loss scale 调小,或者用动态 loss scaling。
5.3 推理结果与训练指标不一致的排查
训练时 mAP 很高,推理时结果却很差,这种问题通常是预处理或后处理不一致导致的。训练时的图像归一化参数(均值、标准差)必须和推理时完全一致,否则输入分布变了,模型输出全乱。YOLO 默认用 0-1 归一化,不减去 ImageNet 均值,但有些自定义模型会减,这个要仔细核对。
另一个常见问题是 NMS 阈值。训练时验证用的 NMS 阈值和推理时不一致,会导致框的数量和位置不同。我一般固定 NMS IoU 阈值为 0.45(YOLO 默认),置信度阈值 0.25。如果推理时框太多,先调高置信度阈值;如果框太少,调低置信度阈值或 NMS 阈值。
6. 从工程落地反推技术选型
6.1 数据量少的时候怎么选
数据量少于 1000 张时,Faster R-CNN 和 DETR 都容易过拟合,YOLO 相对好一些,因为它的数据增强更激进。但更好的做法是用预训练模型微调,冻结骨干网络,只训练检测头。YOLOv8 支持freeze参数,可以冻结前 N 层。我一般冻结前 10 层,学习率设 0.001,训练 50-100 个 epoch。
如果数据量极少(少于 200 张),可以考虑少样本检测方法,比如用 CLIP 做零样本检测,或者用 DETR 的变体(如 Conditional DETR)做微调。但说实话,数据太少什么模型都难,最好的办法还是多标数据。
6.2 边缘设备部署的模型选择
边缘设备(如 Jetson Nano、树莓派)算力有限,模型必须又小又快。YOLOv8n 是最优选,参数量只有 3.2M,TensorRT 加速后在 Jetson Xavier NX 上能跑 30 FPS 左右。Faster R-CNN 和 DETR 在边缘设备上基本跑不动,除非用 MobileNet 骨干,但精度会掉很多。
如果边缘设备支持 NPU(如瑞芯微 RK3588),可以用 RKNN 工具链转换 YOLO 模型,INT8 量化后速度能提升 2-3 倍。但量化会掉精度,需要做量化感知训练(QAT)来补偿。我实测 RK3588 上 YOLOv8n INT8 量化后,mAP 掉了约 2 个点,但速度从 15 FPS 提升到 40 FPS,性价比很高。
6.3 持续迭代与版本管理
模型上线不是终点,持续迭代才是。我一般用 MLflow 或 WandB 做实验管理,记录每次训练的超参数、数据集版本、评估指标。模型权重用 DVC 或 Git LFS 管理,确保每次部署的模型可追溯。数据集的版本也要管,新增标注后要重新划分训练集和验证集,避免数据泄露。
迭代节奏上,我一般每两周做一次全量训练,每周做一次增量训练。增量训练只在新标注的数据上微调,学习率设小一点(0.0001),训练 20-30 个 epoch。如果新数据分布和旧数据差异大,就做全量训练,避免灾难性遗忘。
踩了这么多坑,我最大的体会是:没有最好的模型,只有最合适的模型。RCNN 精度高但慢,DETR 流程干净但难训,YOLO 快且工程化好但精度上限有限。选型时先明确场景的精度和速度底线,再根据数据量、部署环境、团队熟悉度做权衡。另外,训练前的数据清洗和标注检查,比调参重要十倍,我见过太多项目因为标注错误导致模型学废,回头返工的成本极高。