ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

目标检测框架选型指南:YOLO、Faster RCNN与DETR实战对比

目标检测框架选型指南:YOLO、Faster RCNN与DETR实战对比

1. 三大目标检测框架的演进脉络与选型逻辑

目标检测这个领域,过去十年基本就是一条主线:从“怎么把框画准”到“怎么把框画快”,再到“怎么不用手工画框”。RCNN、DETR、YOLO这三条技术路线,恰好代表了三种截然不同的解题哲学。我最早接触的是YOLO v1,那会儿还在用Darknet框架,配置文件写起来跟写汇编似的,但推理速度确实惊艳。后来做项目遇到密集小目标场景,YOLO早期版本漏检严重,才回头去啃Faster RCNN的RPN结构。再后来DETR出来,一看论文觉得“这不就是Transformer套检测吗”,但真正跑起来才发现收敛慢得让人抓狂。这三个框架没有绝对的优劣,只有场景匹配度的问题。

1.1 从两阶段到单阶段再到端到端:核心思路差异

RCNN系列走的是“先找候选区域,再分类回归”的两阶段路线。Faster RCNN用RPN网络替代了Selective Search,把候选框生成也纳入了神经网络,这是它成为经典分水岭的原因。RPN本质上是在特征图上滑动窗口,每个位置预设9个不同尺度和长宽比的锚框,然后判断“这个框里有没有物体”以及“框该怎么微调”。这种设计的优势在于召回率高,因为锚框密集覆盖了各种可能的位置和形状。但缺点也很明显:两阶段意味着推理时要先跑RPN再跑分类头,速度天然受限。我实测过,在T4上用Faster RCNN跑1080p视频,帧率大概在8-12帧,远达不到实时。

YOLO的思路完全反过来:把整张图划分成S×S的网格,每个网格直接预测边界框和类别概率,一次前向传播就出结果。YOLO v1的论文标题就叫“You Only Look Once”,强调的就是这种单阶段的高效性。但早期YOLO有个致命问题:每个网格只能预测两个框,遇到密集小目标或者重叠物体就歇菜。直到YOLO v3引入FPN多尺度预测,v4加入Mosaic数据增强和CSPDarknet,v5工程化封装,v8把Anchor-Free和解耦头做成标配,才真正在精度和速度之间找到了平衡点。

DETR则是另一条路:用Transformer的Encoder-Decoder结构,把目标检测变成“集合预测”问题。它彻底抛弃了锚框和非极大值抑制,直接输出固定数量的预测框(比如100个),每个框通过二分图匹配和真实框对应。这种设计的理论优雅程度最高,但实际训练时收敛极慢,因为二分图匹配在训练初期非常不稳定。DETR论文里提到需要500个epoch才能收敛,而YOLO通常300个epoch以内就能出不错的结果。后来Deformable DETR通过可变形注意力机制把收敛速度提上来了,但工程复杂度也跟着上去了。

1.2 不同业务场景下的选型决策树

选哪个框架,我一般先问三个问题:第一,你的场景对实时性要求多高?第二,你的数据集里小目标占比多少?第三,你的标注数据量有多大?

如果是安防监控、自动驾驶这类需要实时处理的场景,YOLO系列基本是默认选项。YOLOv8在T4上用TensorRT加速后,640分辨率下跑1080p视频,单路可以做到60帧以上,多路并发时根据显存和计算资源线性下降。我实测过T4上跑YOLOv8s模型,FP16精度,batch size设为8,单帧推理耗时约12ms,理论上可以支持4-6路1080p25帧的视频流。但要注意,这是纯推理耗时,实际部署还要算上解码、预处理和后处理的时间。

如果是医学影像、遥感图像这类小目标密集且精度要求极高的场景,Faster RCNN或者它的变体(如Cascade RCNN)仍然有优势。因为RPN生成的候选框足够密集,配合FPN多尺度特征,小目标召回率明显高于YOLO。但代价就是速度慢,通常需要配合TensorRT或者ONNX Runtime做推理优化,即便如此也很难做到实时。

DETR适合什么场景?我的经验是:标注数据充足、目标数量相对固定、对后处理复杂度敏感的场景。比如文档版面分析、试卷题目切割这类任务,目标数量通常不会太多,而且DETR不需要NMS后处理,输出直接就是最终结果,部署链路更简洁。但如果你只有几千张标注图,DETR大概率训不起来,这时候还是老老实实用YOLO。

1.3 三条路线的核心组件对比

维度Faster RCNNDETRYOLO v8
检测范式两阶段端到端集合预测单阶段密集预测
锚框机制有(RPN锚框)无无(Anchor-Free)
后处理NMS无NMS
损失函数分类+回归二分图匹配+分类+回归分类+回归+DFL
收敛速度中等极慢快
小目标性能优中等良(依赖FPN)
推理速度慢中等快
部署复杂度高低低

这张表是我自己在多个项目里踩坑后总结的,不一定绝对准确,但大方向不会错。比如DETR的部署复杂度低,是因为它不需要NMS,但它的推理速度其实不差,只是训练太慢。YOLO的小目标性能标“良”而不是“优”,是因为YOLOv8虽然有多尺度预测,但在极端密集小目标场景下,召回率还是不如Faster RCNN。

2. YOLO实战:从环境搭建到自定义数据集训练

YOLO是这三个框架里工程化做得最好的,没有之一。Ultralytics把训练、验证、推理、导出全流程封装成了几行Python代码,新手也能快速跑通。但“能跑通”和“跑得好”之间隔着大量细节,我见过太多人卡在环境配置、数据格式、损失函数不收敛这些问题上。

2.1 环境搭建的坑与一键部署脚本

YOLOv8的官方推荐环境是Python 3.8+、PyTorch 1.8+、CUDA 11.8。但实际安装时,最大的坑是PyTorch版本和CUDA版本的匹配。我试过在Ubuntu 20.04上装CUDA 12.1 + PyTorch 2.0,结果ultralytics安装后跑推理直接报错,提示CUDA内核不兼容。后来退回CUDA 11.8 + PyTorch 2.0.1才稳定。

如果你不想折腾环境,可以用Docker。Ultralytics官方提供了Docker镜像,但国内拉取速度堪忧。我的做法是自己写一个Dockerfile,基于nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04,然后pip install ultralytics。这样构建出来的镜像大概6GB,比官方镜像小一半。

FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y python3-pip python3-dev RUN pip3 install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 RUN pip3 install ultralytics opencv-python-headless WORKDIR /workspace

这个Dockerfile我用了大半年,没出过问题。注意opencv-python-headless是必须的,因为容器里没有GUI,装完整版opencv会报错。

2.2 自定义数据集准备:从VOC到YOLO格式的转换

YOLO训练需要的数据格式是每张图对应一个txt文件,每行格式为:类别id 中心x 中心y 宽度 高度,所有坐标都归一化到0-1之间。如果你手头是VOC格式的XML标注,转换脚本网上很多,但大部分没处理边界情况。比如有些标注框的xmax小于xmin,或者坐标超出图像边界,直接转换会导致训练时损失函数爆炸。

我写了一个转换脚本,加了边界检查和坐标裁剪:

import xml.etree.ElementTree as ET import os from PIL import Image def voc_to_yolo(xml_path, img_path, class_map, output_dir): tree = ET.parse(xml_path) root = tree.getroot() img = Image.open(img_path) w, h = img.size with open(os.path.join(output_dir, os.path.basename(xml_path).replace('.xml', '.txt')), 'w') as f: for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in class_map: continue cls_id = class_map[cls_name] bbox = obj.find('bndbox') xmin = max(0, float(bbox.find('xmin').text)) ymin = max(0, float(bbox.find('ymin').text)) xmax = min(w, float(bbox.find('xmax').text)) ymax = min(h, float(bbox.find('ymax').text)) if xmax <= xmin or ymax <= ymin: continue cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h f.write(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")

这个脚本的关键在于max(0, ...)和min(w, ...)的边界裁剪,以及跳过无效框的判断。我见过有人直接转换,结果训练时loss变成NaN,排查半天才发现是标注框坐标越界。

2.3 训练参数配置与损失函数解析

YOLOv8的损失函数由三部分组成:分类损失(BCE Loss)、回归损失(CIoU Loss + DFL Loss)。DFL是Distribution Focal Loss,把边界框回归从“预测一个确定值”变成“预测一个分布”,这是YOLOv8相比v5的一个重要改进。DFL的好处是让模型对边界模糊的目标更鲁棒,但代价是输出通道数增加,推理时稍微慢一点。

训练参数方面,我一般这样设置:

model: yolov8s.pt data: custom_dataset.yaml epochs: 300 batch: 16 imgsz: 640 lr0: 0.01 lrf: 0.01 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 warmup_momentum: 0.8 box: 7.5 cls: 0.5 dfl: 1.5

lr0是初始学习率,lrf是最终学习率因子,实际最终学习率是lr0 * lrf。box、cls、dfl是三个损失分量的权重,默认值分别是7.5、0.5、1.5。如果你的数据集类别不平衡严重,可以适当调高cls;如果边界框回归不准,调高box。

注意:YOLOv8默认使用余弦退火学习率调度,lrf设为0.01意味着学习率从0.01降到0.0001。如果你训练轮数很少(比如50轮),建议把lrf调到0.1,否则后期学习率太低,模型基本不更新。

2.4 训练中BN崩溃与混淆矩阵不唯一的排查

“yolo训练中bn崩溃”是社区里高频出现的问题,表现是训练几个epoch后loss突然变成NaN,或者BN层的running_mean变成NaN。根本原因通常是batch size太小。BN层在batch size小于8时,统计量估计非常不稳定,尤其是当输入图像分辨率高、特征图通道数多的时候。

解决办法有三个:第一,增大batch size,至少到16;第二,改用SyncBN,但YOLOv8默认不支持,需要改源码;第三,冻结BN层,用预训练模型的统计量。我一般优先选第一个,如果显存不够,就降低输入分辨率或者用更小的模型。

“yolo混淆矩阵总合不唯一”这个问题,通常是因为验证集里有些图片没有标注框,但模型预测出了框,这些预测被计入混淆矩阵后,导致矩阵行和列的总和不等于验证集真实框总数。这不是bug,是评估逻辑的正常表现。如果你想让混淆矩阵更干净,可以在验证时设置conf阈值高一点,过滤掉低置信度的误检。

3. DETR训练自己数据的完整流程与收敛技巧

DETR的训练和YOLO完全是两个体验。YOLO是你跑起来就能看到loss下降,DETR是你跑起来loss纹丝不动,甚至先上升再下降。我第一次训DETR的时候,前50个epoch loss一直在震荡,差点以为代码写错了。后来查了论文和社区讨论,才知道这是二分图匹配的正常现象。

3.1 DETR论文核心思想与二分图匹配原理

DETR论文的核心创新在于把目标检测建模成“集合预测”问题。传统检测器需要生成大量候选框,然后通过NMS去重。DETR直接预测N个框(N通常设为100),然后通过匈牙利算法找到预测框和真实框的最优一一匹配。匹配完成后,只对匹配上的框计算分类损失和回归损失,没匹配上的框只计算“无物体”分类损失。

这种设计的优雅之处在于:不需要锚框,不需要NMS,输出直接就是最终结果。但问题也很明显:匈牙利匹配在训练初期非常不稳定,因为预测框和真实框的匹配关系每个epoch都在变,模型需要很长时间才能学会稳定的匹配策略。DETR论文里提到需要500个epoch才能收敛,而且用了AdamW优化器和非常低的学习率(1e-4)。

3.2 自定义数据集格式与训练配置

DETR官方代码库支持COCO格式的数据集。如果你手头是VOC格式,需要先转成COCO。转换脚本网上很多,但要注意COCO的标注文件是一个大的JSON,包含images、annotations、categories三个主要字段。

训练配置方面,DETR的默认学习率是1e-4,backbone的学习率是1e-5。batch size通常设为2或4,因为DETR模型很大(ResNet50 backbone + Transformer),显存占用高。在V100上,batch size=2,输入分辨率800×1333,大概占用12GB显存。

# DETR训练配置示例 lr = 1e-4 lr_backbone = 1e-5 batch_size = 2 epochs = 300 weight_decay = 1e-4 clip_max_norm = 0.1

clip_max_norm是梯度裁剪,DETR训练时梯度容易爆炸,这个参数必须设。我试过不设梯度裁剪,训练到第30个epoch左右loss直接变成NaN。

3.3 收敛慢的应对策略与预训练权重使用

DETR收敛慢是出了名的,但有几个技巧可以加速:

第一,使用预训练权重。DETR官方提供了在COCO上训练好的模型,加载backbone和Transformer的权重,可以节省大量训练时间。但要注意,如果你自己的数据集类别数和COCO不一样,分类头的权重需要重新初始化。

第二,增大batch size。DETR论文里用的是batch size=64,但那是用了16块V100。普通人没这个条件,但可以尝试用梯度累积来模拟大batch。比如batch size=2,累积8次,等效batch size=16。

第三,调整二分图匹配的代价函数。DETR默认的匹配代价是分类概率+边界框L1距离+GIoU距离。如果你的数据集里小目标多,可以调高L1距离的权重,让匹配更关注位置精度。

第四,用Deformable DETR替代原始DETR。Deformable DETR通过可变形注意力机制,只关注参考点周围的少量采样点,收敛速度比原始DETR快10倍左右。我实测过,Deformable DETR在自定义数据集上50个epoch就能达到原始DETR 200个epoch的效果。

3.4 DETR部署的简化链路与注意事项

DETR部署的最大优势是不需要NMS后处理,推理链路非常简洁:输入图像→预处理→模型推理→输出框和类别。但有几个坑要注意:

第一,DETR的输出是固定数量的框(比如100个),其中大部分是“无物体”。部署时需要根据分类置信度过滤,通常阈值设0.5以上。

第二,DETR的输入分辨率是固定的(通常800×1333),如果你的实际图像分辨率差异很大,需要做padding或者resize。padding的好处是不变形,但会增加计算量;resize的好处是快,但可能丢失小目标。

第三,DETR的推理速度在V100上大概15-20 FPS(800×1333分辨率),比YOLO慢不少。如果对实时性要求高,还是得用YOLO。

4. 部署与性能优化:从TensorRT到多路视频流

模型训练只是第一步,真正落地时部署和性能优化才是大头。我做过一个项目,客户要求在一个T4上跑4路1080p25帧的监控视频,用YOLOv8做目标检测。T4的算力是8.1 TFLOPS(FP32),65 TFLOPS(FP16),理论上够用,但实际部署时各种瓶颈。

4.1 TensorRT加速YOLO的完整流程

TensorRT是NVIDIA的推理优化库,核心优化手段包括:层融合、精度校准(FP16/INT8)、内核自动调优、动态批处理。YOLOv8导出TensorRT引擎的流程很简单:

from ultralytics import YOLO model = YOLO('yolov8s.pt') model.export(format='engine', half=True, device=0, workspace=4)

half=True表示用FP16精度,workspace=4表示分配4GB显存用于优化。导出后的engine文件可以直接用TensorRT的Python API加载推理。

但要注意,TensorRT引擎是硬件相关的。你在T4上导出的engine,拿到V100上跑不了,必须重新导出。而且TensorRT版本和CUDA版本也要匹配,否则加载engine时会报错。

4.2 T4上多路1080p视频流的性能估算

回到那个问题:T4上跑YOLOv8 640分辨率,1080p25帧的视频能支持多少路?

先算单帧推理耗时。YOLOv8s在T4上用TensorRT FP16,640×640输入,batch size=1,推理耗时约8-10ms。加上预处理(resize、归一化)约2ms,后处理(NMS)约3ms,单帧总耗时约13-15ms。理论上单路可以跑到60-70 FPS。

但多路并发时,GPU的计算资源是共享的。T4有40个SM单元,2560个CUDA核心。如果每路视频都需要独立推理,GPU会通过时间片轮转来调度。实际测试中,4路1080p25帧并发时,每路的端到端延迟约40-50ms,帧率稳定在25帧,GPU利用率约85%。如果增加到6路,延迟会上升到60-70ms,部分帧会丢。

所以我的结论是:T4上跑YOLOv8s 640分辨率,稳定支持4路1080p25帧,极限可以到6路但会有丢帧。如果换成YOLOv8n(nano版本),推理耗时降到5ms左右,可以支持8路。

4.3 RTSP拉流与实时监控的工程实现

监控视频拉流通常用RTSP协议。OpenCV的VideoCapture支持RTSP,但有个坑:如果网络抖动导致丢包,VideoCapture会阻塞,整个推理管线卡住。解决办法是用FFmpeg拉流,通过subprocess管道传给Python,或者用GStreamer。

我一般用PyAV库,它底层是FFmpeg,支持异步解码,不会阻塞主线程:

import av import cv2 container = av.open('rtsp://your_camera_stream') stream = container.streams.video[0] for frame in container.decode(stream): img = frame.to_ndarray(format='bgr24') results = model(img) # 处理检测结果

PyAV的好处是解码和推理可以流水线并行,解码线程和推理线程互不阻塞。但要注意,RTSP流如果断线,PyAV会抛异常,需要加重连逻辑。

4.4 模型蒸馏与剪枝在边缘设备上的应用

如果要在边缘设备(如Jetson Nano、树莓派)上部署,YOLOv8s可能还是太大。这时候可以考虑模型蒸馏或者剪枝。

蒸馏的思路是用一个大模型(教师)指导一个小模型(学生)训练。YOLO的蒸馏通常蒸馏中间层特征和最终输出。我试过用YOLOv8m蒸馏YOLOv8n,在自定义数据集上mAP提升了3个点,但推理速度不变。

剪枝则是直接去掉模型中不重要的通道。Ultralytics官方不支持剪枝,需要用第三方工具如NNI或者Torch-Pruning。剪枝后模型体积可以缩小50%,但精度会掉2-5个点,需要微调恢复。

5. 常见问题速查与避坑经验

这一节整理了我这些年遇到的高频问题和解决方法,大部分是社区里反复出现的,也有一些是我自己踩过的坑。

5.1 训练与推理中的典型报错与解决

问题现象可能原因解决方法
训练loss变成NaN学习率过高、BN崩溃、标注框越界降低学习率、增大batch size、检查标注
推理速度远低于预期没用TensorRT、没开FP16、后处理耗时高导出TensorRT引擎、开half精度、优化NMS
mAP很低但loss正常验证集标注格式错误、类别映射不对检查data.yaml、可视化验证集标注
显存溢出batch size太大、输入分辨率太高降低batch size、用梯度累积、减小imgsz
RTSP拉流卡顿网络抖动、解码阻塞用PyAV异步解码、加重连逻辑
TensorRT引擎加载失败硬件不匹配、版本不匹配重新导出引擎、检查CUDA和TensorRT版本

5.2 数据集标注的独家避坑技巧

标注质量直接决定模型上限。我见过太多项目,模型结构调来调去,最后发现是标注问题。几个关键点:

第一,标注框要贴紧目标边缘,不要留太多背景。YOLO对边界框回归很敏感,标注框松了,模型学到的就是“大框”,推理时框也会偏大。

第二,类别定义要互斥。我见过一个项目,把“汽车”和“轿车”分成两个类,结果模型完全分不清。类别定义要尽量粗粒度,除非业务确实需要细分。

第三,难例要标注。有些目标被遮挡、模糊,标注员可能直接跳过。但这些难例恰恰是模型提升的关键。我一般要求标注员对难例打上“difficult”标签,训练时单独处理。

第四,定期做标注一致性检查。同一个目标,不同标注员画的框可能差很多。我通常随机抽100张图,让两个人独立标注,然后计算IoU,如果平均IoU低于0.7,说明标注标准需要统一。

5.3 模型选型的决策清单

最后给一个我常用的选型决策清单,按优先级排序:

  1. 实时性要求>30 FPS:选YOLO,优先YOLOv8n/s,配合TensorRT。
  2. 小目标密集且精度要求高:选Faster RCNN或Cascade RCNN,配合FPN。
  3. 标注数据>10万张且目标数量固定:可以尝试DETR或Deformable DETR。
  4. 边缘设备部署:选YOLOv8n,配合剪枝和INT8量化。
  5. 需要实例分割:选YOLOv8-seg,它支持检测+分割联合输出。
  6. 需要快速验证想法:选YOLOv8,因为训练和部署链路最短。

这个清单不是绝对的,但能帮你快速缩小选择范围。实际项目中,我通常会先用YOLOv8跑一个baseline,如果精度不达标,再考虑换Faster RCNN或者DETR。毕竟YOLO的工程化程度最高,试错成本最低。

我在实际项目里最大的体会是:不要迷信论文里的SOTA指标。论文里的mAP是在COCO上测的,你的数据集分布、目标尺度、光照条件可能完全不同。我见过YOLOv8在自定义数据集上吊打Faster RCNN的情况,也见过Faster RCNN在密集小目标场景下比YOLO高10个点mAP的情况。选型之前,先花半天时间用YOLOv8跑个baseline,比看十篇论文都管用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进