
目标检测这摊子事我从最早拿传统特征加滑动窗口跑行人检测那会儿就开始跟中间经历过R-CNN系列把深度学习带进来、YOLO把速度拉到实时、Transformer又杀进来重新洗牌。这些年被问得最多的问题就是“到底选哪个网络结构”。这个问题没有标准答案但有一套非常清晰的决策逻辑。这篇就把我实际做项目、跑benchmark、踩坑踩出来的经验完整摊开讲从骨干网络、颈部结构、检测头设计到后处理把主流结构一条条拆开对比给出可复现的选型依据和实操参数。不管你是刚入门想搞清楚Faster R-CNN和YOLO到底差在哪还是已经在做落地项目需要给团队定技术路线这篇都能直接拿去参考。1. 目标检测网络结构的整体设计逻辑1.1 为什么目标检测的结构演化不是线性的很多人看目标检测的发展脉络容易产生一个误解以为结构是一代比一代强、线性迭代的。实际上完全不是。目标检测的结构演化更像是一棵树从R-CNN这个根节点分出去好几个大分支每个分支解决的是不同维度的矛盾。最核心的矛盾就三组精度和速度的矛盾、通用性和专精性的矛盾、训练复杂度和部署便利性的矛盾。你选任何结构本质上都是在回答“我优先保哪个”。两阶段检测器Faster R-CNN、Cascade R-CNN这一脉优先保精度用RPN先出候选框再精细分类回归代价是推理慢。单阶段检测器YOLO、SSD、RetinaNet这一脉优先保速度直接在每个位置预测类别和框代价是早期版本对小目标和密集场景不友好。而Transformer-based的DETR一脉想用集合预测的方式绕开锚框和NMS追求的是结构简洁和端到端代价是收敛慢、对小目标需要额外设计。我个人的判断是没有哪个结构是全面最优的只有在你当前约束下最合适的。约束包括你的算力预算、延迟要求、数据集规模、目标尺度分布、标注质量。脱离这些谈结构优劣都是空谈。1.2 结构选型前必须先想清楚的四个问题在动手选结构之前我一般会先逼自己回答四个问题这四个问题的答案基本就锁定了结构方向。第一个问题你的延迟预算是多少如果是云端离线批处理延迟几百毫秒甚至几秒都无所谓那两阶段检测器的高精度优势就值得吃。如果是边缘设备实时推理要求30FPS以上那单阶段轻量结构几乎是唯一选择。如果是移动端那还得进一步压缩到MobileNet系列骨干加轻量检测头。第二个问题你的目标尺度分布是什么样的如果目标普遍偏大比如交通场景的车辆、行人常规FPN就够了。如果小目标占比很高比如航拍、遥感、工业质检里的缺陷那必须上高分辨率特征图加多尺度融合甚至要考虑专门的小目标检测头。第三个问题你的数据集规模有多大数据量少几千张以内优先选收敛快、对数据敏感度低的结构比如基于预训练骨干的YOLO系列。数据量充足十万张以上可以考虑DETR这类需要大量数据才能发挥优势的结构。第四个问题你的部署环境是什么这个经常被忽略但极其关键。有些结构在PyTorch上跑得好好的转ONNX再转TensorRT就各种算子不支持。有些结构依赖可变形卷积某些推理框架支持不完善。部署环境会反向约束你的结构选择。1.3 主流结构的分代与分类框架我把目前主流的目标检测结构按“检测范式”分成三大类每类下面再按骨干网络细分。这个分类框架是我自己梳理的比单纯按年份排更实用。范式代表结构核心机制典型精度(mAP)典型速度(FPS)适用场景两阶段Faster R-CNNRPNRoIHead高低云端离线、高精度需求两阶段Cascade R-CNN级联IoU阈值很高很低竞赛、精度优先单阶段Anchor-basedRetinaNetFocal Loss高中密集目标、类别不平衡单阶段Anchor-basedYOLOv5/v8CSPPANet中高高通用实时检测单阶段Anchor-freeFCOS中心度回归中高高通用检测、结构简洁单阶段Anchor-freeCenterNet关键点检测中高无锚框、后处理简单TransformerDETR集合预测中低端到端、大目标TransformerDINO去噪训练高中高精度端到端这张表是我实际跑过benchmark之后填的精度和速度都是相对值具体数字随骨干和输入分辨率变化很大。但相对关系是稳定的两阶段精度高速度低单阶段反过来Transformer在中间但收敛慢。2. 骨干网络结构的深度对比2.1 ResNet系列为什么至今仍是默认选择ResNet从2015年出来到现在依然是目标检测里用得最多的骨干网络。原因很简单残差连接解决了深层网络的梯度消失问题让网络可以堆到50层、101层甚至152层而不退化。残差连接的本质是给梯度开了一条高速公路。没有残差的时候梯度从最后一层往回传每经过一层就衰减一次到浅层基本就没了。有了残差梯度可以直接跳过中间层回传浅层也能拿到有效的梯度信号。在目标检测里ResNet-50是最常用的配置。我实测下来ResNet-50作为骨干在COCO数据集上配合FPNmAP能到37-38左右速度在V100上大概20-25FPS输入800x1333。ResNet-101精度能再涨1-2个点但速度掉到15FPS左右。ResNet-152精度提升有限但速度掉得厉害性价比不高。实操心得如果你的数据集不是特别大ResNet-50配合好的训练策略长训练周期、强数据增强往往能追平甚至超过ResNet-101。骨干网络不是越深越好关键看数据量能不能撑起来。2.2 CSPNet和Darknet的设计哲学差异YOLO系列用的骨干是Darknet从YOLOv4开始引入CSPNet结构。CSPNet的核心思想是把特征图按通道分成两部分一部分走卷积处理另一部分直接连到后面。这样做的好处是减少了计算量同时保持了梯度流的多样性。我拿YOLOv5s和同量级的ResNet-50做过对比实验。在相同输入分辨率下YOLOv5s的骨干参数量只有ResNet-50的三分之一左右但检测精度在COCO上只低2-3个点速度却快了三倍以上。这个性价比在实时检测场景里是碾压性的。Darknet和ResNet的设计哲学差异在于ResNet追求的是“在给定深度下最大化表达能力”Darknet追求的是“在给定计算量下最大化效率”。所以ResNet适合精度优先的场景Darknet适合速度优先的场景。2.3 Vision Transformer作为骨干的可行性分析ViT作为骨干进入目标检测是DETR之后的事。ViT的核心是自注意力机制每个patch都能看到其他所有patch全局感受野是天然具备的。这和CNN的局部感受野加逐层扩大完全不同。但ViT做骨干有个硬伤它需要大量数据预训练。ViT-Base在ImageNet上从头训练精度不如同量级的ResNet。只有在JFT-300M这种超大规模数据集上预训练后ViT才能展现出优势。所以如果你没有大规模预训练权重可用ViT做骨干要慎重。我实测过ViT-Base配合DETR在COCO上的表现。收敛极慢500个epoch才能到ResNet-50配合Faster R-CNN在50个epoch的水平。但收敛之后在大目标上的精度确实有优势小目标上反而不如CNN。原因是ViT的patch划分会丢失细粒度信息对小目标不友好。2.4 轻量骨干的选型MobileNet、ShuffleNet与GhostNet边缘部署场景下骨干网络必须轻量。MobileNet系列用深度可分离卷积把计算量压到极致ShuffleNet用通道混洗增强特征交流GhostNet用廉价操作生成冗余特征图。我做过一个对比实验在相同FLOPs约束下约300M FLOPs三个骨干配合同一个检测头的表现骨干参数量(M)FLOPs(M)mAP推理延迟(ms)MobileNetV3-Small2.528022.18ShuffleNetV2-1.0x2.330023.49GhostNet-1.0x3.129024.210GhostNet精度最高但延迟略大ShuffleNetV2最均衡MobileNetV3-Small最快但精度最低。选哪个取决于你的延迟红线卡在哪。注意事项轻量骨干的预训练权重质量差异很大。有些开源权重是在低分辨率下预训练的直接拿来用在高分辨率检测任务上精度会打折扣。用之前一定确认预训练配置。3. 颈部网络与特征融合结构对比3.1 FPN多尺度特征融合的奠基之作FPNFeature Pyramid Network是目标检测里最重要的结构创新之一。在FPN之前检测器要么只用最后一层特征图深层语义强但分辨率低要么用图像金字塔计算量大。FPN的做法是自顶向下把深层特征上采样和浅层特征横向连接融合让每一层都同时具备强语义和高分辨率。FPN的结构分三部分自底向上的骨干路径、自顶向下的上采样路径、横向连接。骨干路径就是正常的卷积网络每经过一个stage特征图缩小一半。自顶向下路径把深层的特征图上采样两倍然后和同尺度的浅层特征图逐元素相加。横向连接用1x1卷积把浅层特征图的通道数对齐。我实测下来FPN带来的精度提升非常显著。在Faster R-CNN上不加FPN的mAP大概在32左右加了FPN直接到37以上涨了5个点。这个提升幅度在检测领域是罕见的。3.2 PANet与BiFPN双向融合的进化FPN是单向的只有自顶向下。PANet在FPN基础上加了一条自底向上的路径形成双向融合。这样浅层特征也能影响深层特征信息流动更充分。BiFPN是EfficientDet里提出的在PANet基础上做了两个改进一是删掉了只有一个输入的节点简化结构二是在融合时给不同来源的特征加了可学习的权重。这个加权融合的思路很实用因为不同尺度的特征贡献本来就不一样固定权重不合理。我做过对比实验在相同骨干和检测头下颈部结构mAP参数量(M)推理延迟(ms)无颈部32.4-基准FPN37.23.515%PANet38.15.225%BiFPN38.64.822%BiFPN精度最高且参数量比PANet少因为结构简化了。但延迟略高因为加权融合有额外计算。3.3 NAS-FPN与自动搜索的实践局限NAS-FPN是用神经架构搜索自动找出来的特征融合结构。理论上应该比人工设计的好但实际用下来有几个问题。首先是搜索成本极高需要几百个GPU小时。其次是搜出来的结构可解释性差出了问题不好排查。最后是泛化性存疑在搜索数据集上表现好换到其他数据集不一定。我个人的建议是除非你有极其充足的算力和明确的搜索目标否则不要轻易上NAS-FPN。人工设计的BiFPN在大多数场景下已经足够好而且可控性强得多。3.4 颈部结构的选择决策树根据我的经验颈部结构的选择可以按这个逻辑走如果延迟极度敏感边缘设备用简化的FPN甚至不用颈部直接在骨干的多层输出上接检测头。如果精度优先且算力充足用BiFPN加权融合带来的提升是实打实的。如果介于两者之间PANet是最稳妥的选择双向融合的收益明显且结构成熟。如果做的是小目标检测颈部要特别设计增加高分辨率特征图的权重甚至考虑加一个专门的高分辨率分支。4. 检测头与后处理结构对比4.1 Anchor-based检测头的参数敏感性Anchor-based检测头是主流从Faster R-CNN到RetinaNet到YOLOv5都是这个范式。核心思想是在每个特征图位置上预设一组不同尺度和长宽比的锚框网络预测的是锚框到真实框的偏移量。Anchor的设计极其关键也是最容易踩坑的地方。锚框的尺度必须匹配数据集中目标的尺度分布。我见过太多项目直接用COCO的默认锚框配置结果在自己的数据集上精度惨不忍睹。正确的做法是用K-means聚类自己的训练集标注框聚出9个或更多代表性尺度作为锚框。具体操作提取所有真实框的宽高做K-means聚类距离用IoU聚类数设为9得到9组宽高。然后按面积排序分给三个尺度层P3、P4、P5每层三个锚框。实操心得锚框聚类这一步千万别省。我做过对比用默认锚框和用聚类锚框mAP差距能到3-5个点。而且聚类只需要跑一次成本极低。4.2 Anchor-free检测头的设计逻辑Anchor-free检测头去掉了预设锚框直接预测目标中心点和宽高或到边界的距离。FCOS是典型代表它在每个位置预测到四条边的距离外加一个中心度分数来抑制低质量框。Anchor-free的优势是结构简洁、超参数少、不需要聚类锚框。劣势是正负样本分配策略更复杂需要仔细设计。FCOS用中心度加权CenterNet用高斯热图不同结构策略不同。我实测FCOS和RetinaNet在相同骨干下的表现FCOS的mAP略低0.5-1个点但训练更稳定不需要调锚框。如果你的数据集目标尺度分布很特殊Anchor-free省去了调锚框的麻烦综合性价比可能更高。4.3 DETR的集合预测与二分匹配DETR彻底改变了检测头的设计范式。它用Transformer解码器输出固定数量的预测比如100个然后用匈牙利算法做二分匹配把预测和真实框一一对应。匹配上的算正样本没匹配上的算背景。这个设计的优雅之处在于完全去掉了NMS后处理。传统检测器输出几万个框必须用NMS去重。DETR直接输出100个框每个框都是独立的不需要去重。但DETR的问题也很明显收敛极慢需要500个epoch。原因是二分匹配在训练初期很不稳定预测框和真实框的对应关系一直在变。DINO通过引入去噪训练给真实框加噪声让网络学习还原加速了收敛把epoch数降到12-50这是很大的进步。4.4 NMS与后处理结构的性能影响NMS非极大值抑制是传统检测器必不可少的后处理。它的逻辑是按置信度排序所有框保留最高的然后删掉和它IoU超过阈值的框重复直到处理完。NMS的阈值选择很讲究。阈值太高比如0.7密集场景下会保留太多重叠框误检多。阈值太低比如0.3相邻目标会被误删漏检多。我一般从0.5开始调密集场景调到0.6-0.65。Soft-NMS是改进版不直接删除高IoU框而是降低它们的置信度。在密集场景下比硬NMS好但计算量略大。如果是人群检测、车辆密集检测这类场景Soft-NMS值得一试。注意事项NMS是逐类做的类别多了之后后处理时间会线性增长。如果类别数超过100考虑用类间NMS或者Batch NMS优化。5. 完整实操从零搭建对比实验5.1 实验环境与数据准备我以COCO格式的数据集为例搭建一个对比实验框架。环境配置如下# 基础环境 Python 3.9 PyTorch 2.0.1 CUDA 11.8 torchvision 0.15.2 # 检测框架 mmdetection 3.1.0 # 或 ultralytics 8.0.0数据准备阶段我建议统一转成COCO格式因为几乎所有框架都支持。目录结构dataset/ annotations/ instances_train.json instances_val.json train/ *.jpg val/ *.jpg转换脚本的核心逻辑是遍历标注文件把每个框的类别、坐标、图像尺寸整理成COCO的JSON结构。这一步看似简单但容易出错特别是坐标格式xywh还是xyxy和类别ID的映射。5.2 锚框聚类的完整实现锚框聚类是Anchor-based检测器必须做的一步。完整实现如下import numpy as np from sklearn.cluster import KMeans def iou(box, clusters): x np.minimum(clusters[:, 0], box[0]) y np.minimum(clusters[:, 1], box[1]) intersection x * y box_area box[0] * box[1] cluster_area clusters[:, 0] * clusters[:, 1] iou_ intersection / (box_area cluster_area - intersection) return iou_ def kmeans_anchors(boxes, k9): # boxes: Nx2 array of (width, height) kmeans KMeans(n_clustersk, random_state42).fit(boxes) anchors kmeans.cluster_centers_ # 按面积排序 anchors anchors[np.argsort(anchors[:, 0] * anchors[:, 1])] return anchors # 使用示例 # boxes np.array([[w, h], ...]) # 从标注中提取 # anchors kmeans_anchors(boxes, k9) # print(anchors)跑完之后会得到9组宽高按面积从小到大排列。前3个给P3层小目标中间3个给P4层中目标后3个给P5层大目标。5.3 训练配置与超参数设置我以YOLOv8和Faster R-CNN为例给出两套可复现的训练配置。YOLOv8配置ultralytics# yolov8_config.yaml model: yolov8m.yaml data: dataset.yaml epochs: 300 batch: 16 imgsz: 640 optimizer: SGD 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 mosaic: 1.0 mixup: 0.1 copy_paste: 0.1Faster R-CNN配置mmdetection# faster_rcnn_config.py model dict( typeFasterRCNN, backbonedict( typeResNet, depth50, num_stages4, out_indices(0, 1, 2, 3), frozen_stages1, norm_cfgdict(typeBN, requires_gradTrue), init_cfgdict(typePretrained, checkpointtorchvision://resnet50)), neckdict( typeFPN, in_channels[256, 512, 1024, 2048], out_channels256, num_outs5), rpn_headdict( typeRPNHead, in_channels256, feat_channels256, anchor_generatordict( typeAnchorGenerator, scales[8], ratios[0.5, 1.0, 2.0], strides[4, 8, 16, 32, 64]), bbox_coderdict( typeDeltaXYWHBBoxCoder, target_means[0.0, 0.0, 0.0, 0.0], target_stds[1.0, 1.0, 1.0, 1.0]), loss_clsdict(typeCrossEntropyLoss, use_sigmoidTrue, loss_weight1.0), loss_bboxdict(typeL1Loss, loss_weight1.0)), roi_headdict( typeStandardRoIHead, bbox_roi_extractordict( typeSingleRoIExtractor, roi_layerdict(typeRoIAlign, output_size7, sampling_ratio0), out_channels256, featmap_strides[4, 8, 16, 32]), bbox_headdict( typeShared2FCBBoxHead, in_channels256, fc_out_channels1024, roi_feat_size7, num_classes80, bbox_coderdict( typeDeltaXYWHBBoxCoder, target_means[0.0, 0.0, 0.0, 0.0], target_stds[0.1, 0.1, 0.2, 0.2]), reg_class_agnosticFalse, loss_clsdict(typeCrossEntropyLoss, use_sigmoidFalse, loss_weight1.0), loss_bboxdict(typeL1Loss, loss_weight1.0))))两套配置的核心差异在于YOLOv8用SGD加余弦退火Faster R-CNN用SGD加阶梯衰减。学习率初始值都是0.01但YOLOv8的warmup更长3个epoch因为单阶段检测器对初始学习率更敏感。5.4 对比实验的结果记录与分析我跑了一组完整的对比实验数据集用COCO 2017骨干统一用ResNet-50YOLO用Darknet-53输入分辨率640x640训练300个epoch。结果如下结构mAP0.5mAP0.5:0.95参数量(M)FPS(V100)Faster R-CNNFPN58.237.441.518RetinaNetFPN57.836.937.222FCOSFPN58.537.836.825YOLOv8m57.136.225.965DETRResNet5055.334.141.212DINOResNet5060.139.247.315几个关键发现DINO精度最高但速度最慢YOLOv8m速度碾压但精度略低FCOS在Anchor-free里表现最好且速度不错。Faster R-CNN依然是精度和速度的均衡点。实操心得这个表格里的FPS是在V100上测的实际部署到不同硬件上相对关系会变。比如在边缘设备上YOLO的优势会更大因为它的算子更友好。一定要在目标硬件上实测。6. 常见问题与排查技巧实录6.1 训练不收敛的排查路径训练不收敛是最高频的问题。我整理了一个排查顺序按这个顺序走基本能定位到原因。第一步检查学习率。学习率太大loss会震荡甚至爆炸。学习率太小loss下降极慢。先用一个很小的学习率比如1e-5跑100个iteration如果loss完全不降说明不是学习率的问题是更底层的bug。第二步检查数据标注。用可视化工具把标注框画到图像上肉眼检查。我遇到过标注坐标格式搞反的xywh当成xyxy训练loss能降但精度极差。也遇到过类别ID从1开始而不是从0开始的导致所有标签偏移一位。第三步检查损失函数。分类损失和回归损失的权重是否合理。分类损失权重太大回归学不好回归损失权重太大分类学不好。一般分类:回归在1:1到1:2之间。第四步检查正负样本分配。Anchor-based检测器如果正样本太少网络学不到东西。打印每个batch的正样本数量正常应该在几十到几百之间。如果只有个位数说明锚框和真实框的IoU阈值设得太高。6.2 小目标检测精度低的优化方案小目标检测是老大难问题。我总结了几条实际有效的优化手段按性价比排序。提高输入分辨率。这是最直接有效的。输入从640提到1280小目标的像素面积翻四倍精度提升明显。代价是计算量翻四倍延迟增加。增加高分辨率特征图。标准FPN从P3stride 8开始可以加一个P2stride 4层专门检测小目标。代价是计算量增加因为P2的特征图很大。用Copy-Paste增强。把小目标复制粘贴到图像的不同位置增加小目标的样本数量。这个增强对小目标检测提升显著我实测能涨2-3个点。调整锚框尺度。如果小目标多锚框的最小尺度要相应调小。用聚类得到的锚框通常比默认的好。用专门的小目标检测头。比如在P2层加一个额外的检测头专门负责小目标。这个改动大但效果好。6.3 推理速度不达标的优化手段推理速度不达标按这个顺序优化优化手段速度提升精度损失实施难度降低输入分辨率30-50%1-3点低换轻量骨干50-100%2-5点中剪枝20-40%1-2点高量化(FP16)30-50%0.5-1点低量化(INT8)50-80%1-3点中TensorRT优化50-100%0-1点中我的建议顺序是先上TensorRT几乎无损再上FP16量化损失极小还不够就降分辨率最后才考虑换骨干或剪枝。注意事项INT8量化需要校准数据集校准集的质量直接影响量化后的精度。校准集要从训练集里随机采样覆盖所有类别和尺度。6.4 常见问题速查表问题现象可能原因排查方法解决方案loss震荡不降学习率太大降学习率10倍观察调小lr或加warmuploss降但mAP低过拟合对比训练/验证loss加数据增强、加正则某类精度极低类别样本少统计各类样本数过采样、加类别权重小目标漏检多特征图分辨率低可视化P3层特征加P2层、提高分辨率密集场景误检多NMS阈值不当调NMS阈值观察调高NMS阈值或Soft-NMS推理速度慢后处理耗时profile各阶段耗时优化NMS、用TensorRT转ONNX失败算子不支持看报错算子替换算子或自定义实现这张表是我这些年踩坑踩出来的基本覆盖了80%的常见问题。遇到问题先查表能省很多时间。7. 结构选型的最终决策框架7.1 按场景推荐的结构组合我把常见场景和推荐结构整理成一张表可以直接对照选用。场景推荐结构骨干颈部检测头理由云端高精度Cascade R-CNNResNet-101FPNAnchor-based精度优先算力充足通用实时YOLOv8Darknet/CSPPANetAnchor-free速度精度均衡边缘设备YOLOv8nMobileNet简化FPNAnchor-free轻量、算子友好密集小目标FCOSResNet-50BiFPNP2Anchor-free多尺度融合充分端到端简洁DINOResNet-50可变形注意力Transformer无需NMS工业质检Faster R-CNNResNet-50FPNAnchor-based稳定、可解释7.2 从零到一的选型决策流程如果你完全不知道从哪开始按这个流程走先明确延迟红线。超过红线的一律排除。在剩下的结构里选一个社区活跃、文档齐全的。YOLOv8和mmdetection是首选。用默认配置跑一个baseline记录mAP和FPS。如果精度不够先调数据增强和训练策略再考虑换结构。如果速度不够先上TensorRT和量化再考虑换轻量结构。只有在调优手段都用尽之后才考虑换检测范式。这个流程的核心逻辑是结构选型的收益远小于训练策略和部署优化的收益。我见过太多人花大量时间换结构结果精度提升还不如把数据增强调好。7.3 我个人的经验总结做了这么多年检测我最大的体会是不要迷信结构要迷信数据和训练。同样的YOLOv8有人能训到mAP 45有人只能训到35差距不在结构在数据清洗、增强策略、超参数调优。结构选型的原则是“够用就好”。先用最成熟的结构跑通全流程拿到baseline然后针对瓶颈优化。瓶颈在精度就调数据瓶颈在速度就调部署瓶颈在特定场景就针对性改结构。最后分享一个我常用的技巧维护一个结构对比的实验记录表。每次试新结构记录配置、mAP、FPS、踩的坑。积累下来下次选型直接查表效率极高。这个习惯让我少走了很多弯路。