简介:这份综述文档面向计算机视觉学习者、算法工程师及准备相关面试的开发者,系统梳理目标检测领域从传统思路到深度学习范式的演进脉络。文档先讲解传统方法中滑动窗口区域选择、SIFT/HOG特征提取及SVM分类器组合的局限;随后重点剖析基于区域候选的深度学习算法,包括R-CNN流程、SPP-Net对卷积特征复用的优化,并对比各方法的精度与速度差异。资源为单个docx文件,大小842KB,目录结构清晰,适合按章节阅读和标记笔记。已有325人浏览学习,既可辅助课程复习,也能为论文调研和实际工程中的算法选型提供参考。
1. 目标检测综述到底在梳理什么:从“框出目标”到“理解场景”
做工业质检、自动驾驶或者安防监控的人翻开目标检测综述,多半是被同一件事逼来的:模板匹配和颜色阈值撑不住了,想知道该换哪个模型、要不要自己改网络、训练集扩到多少才够用。目标检测综述不是把 YOLO 到 DETR 的论文按时间轴排一遍,它实际在回答三个问题:检测器把什么当作“目标”、用什么方式表达并回归出“目标”、拿到预测框之后拿什么标准判断好坏。算法发展现状讲的是前两个问题的答案迁移,评价指标讲的是最后一个问题的共识。这篇内容按“技术演进—最小复现—边界场景—诊断技巧”的顺序展开,新手能顺着路线图跑出第一个模型,熟手在 mAP 涨不动的时候,也知道先看 PR 曲线而不是先改学习率。
2. 目标检测的技术演进而非堆模型:两阶段、单阶段与无锚框之争
2.1 两阶段与单阶段的取舍逻辑
2.1.1 R-CNN 家族为什么先在“候选区域”上做文章
检测综述里绕不开的第一个节点是 R-CNN:先用选择性搜索提出约两千个候选区域,再逐个送入卷积网络提取特征并分类。这个思路把“检测”从传统的滑动窗口暴力枚举里解放出来,代价是每个候选区域都过一遍网络,一张图推理动辄十几秒,离工程落地很远。Fast R-CNN 用 RoI Pooling 把共享卷积特征截出来,只在分类头上逐区域计算,速度大幅提升;Faster R-CNN 又用 RPN(Region Proposal Network)把候选区域生成也变成网络的一部分,至此“两阶段”的稳定形态确定下来。
两阶段把任务拆成“先找在哪、再认是什么”两个子问题,定位精度高,尤其对大物体的框回归更细。代价是结构里多一个区域提议环节,训练时要分别处理 RPN 和检测头的损失,显存占用也更高。在综述的演进图表里,这一阶段的意义不是被单阶段取代,而是提出了“多任务联合训练”和“锚框采样”这两个至今仍被使用的基础操作。
2.1.2 YOLO 与单阶段方法用回归换速度
YOLO 走的是相反路线:把检测完全当作回归问题来做。输入图像被划分成 S×S 网格,每个网格单元一次预测若干个边界框、置信度和类别概率,没有显式的候选区域生成环节。早期版本对小目标和重叠物体漏检明显,因为网格粒度决定了下限;后来引入多尺度预测,在多个特征层上各自回归,小目标的召回才有了实质改善。
SSD 更早意识到特征层的价值:在不同分辨率的特征图上分别做分类和框回归,浅层负责小目标、深层负责大目标,这为 FPN 的“多尺度预测”搭好了骨架。今天的工业项目里,实时场景默认选单阶段:产线质检 60 FPS 以上没有商量余地,自动驾驶感知也需要前向推理时间稳定可预估。单阶段省掉的不是精度,而是“两遍计算”的冗余路径。
2.2 检测头、损失函数与正负样本分配
2.2.1 从 Smooth L1 到 CIoU:回归损失在解决什么
框回归损失的目标是让预测框和真值框尽量贴近,但直接相减 L1 有梯度抖动的毛病,所以 Faster R-CNN 用了带阈值切换的 Smooth L1。后来 IoU Loss 把优化目标换成“交并比最大”,比 L1 更贴近 mAP 的测量方式;再往后 DIoU 加入中心点距离约束,CIoU 再加长宽比一致性,解决的是 IoU=0 时梯度消失和中心偏移的问题。
单阶段检测器常用的另一类是 Focal Loss 及其变体,它解决的是类别极端不平衡:背景框数量比目标框多几个数量级,普通交叉熵会淹没真正的前景信号。Focal Loss 给易分类样本降权,让训练重心落到难例上,这也是 RetinaNet 能把单阶段精度拉到两阶段水平的关键。
2.2.2 正负样本分配:比损失函数更决定收敛质量
损失函数定好之后,“谁算正样本、谁算负样本”的分配策略直接决定训练走向。Faster R-CNN 的做法是把与真值 IoU 大于 0.7 的锚框都算正样本并随机采样 256 个;YOLOv5 之后用 shape 匹配加 CIoU 筛选;DETR 则把分配问题退化成二分图匹配,让每个真值只对应一个预测框。不同分配策略对收敛速度的影响,往往比主干网络换大换小更明显。
正负样本分配的一个常见误解是“负样本就是背景”,其实还包括“定位差但类别对的框”。这类框在单阶段里容易被打成正样本,导致模型学出的边界框粗糙。综述里讨论的 ATSS、OTA 等分配算法,本质都是在回答同一个问题:怎么让分配规则对物体大小、遮挡程度和稀疏程度更鲁棒。
2.3 无锚框与 Transformer 检测器把“框”还给特征
锚框一直是挡在工程师面前的一堵墙:尺寸、比例、数量都要手动调。FCOS 和 CornerNet 尝试去掉锚框,用中心点加距离回归的方式预测目标,但多目标重叠时中心点语义不清晰,误检仍偏高。DETR 直接换思路:把检测建模成集合预测问题,用 Transformer 的注意力机制建立全局关联,再通过匈牙利匹配监督预测框与真值的对应关系,彻底移除了锚框和后处理 NMS。
DETR 的代价是训练收敛慢,小目标特征容易在注意力计算中被稀释;Deformable DETR 用可变形注意力只聚焦稀疏采样点,把收敛速度和精度同时拉回来。演进到 DINO 这类模型后,检测器的结构描述已经很难用“几阶段”概括,更像“特征提取器 + 查询机制 + 匹配损失”的组合体。综述里做横向对比时,会把模型分成两阶段卷积、单阶段卷积、无锚框和 Transformer 四组,选型的核心依据变成:数据量、推理平台和延迟预算。
3. 动手跑通最小复现:数据集格式、训练命令与评价指标计算
3.1 用 COCO 格式组织自己的数据
目标检测的数据格式绕不开 COCO 和 Pascal VOC 两种。COCO 的 JSON 结构对初学者不友好,但主流工具链都支持它,所以最小复现从 COCO 起步更省事。转换时最关键的是 categories 信息要从 1 开始编号,标注框用[x, y, width, height],坐标一律是浮点数,类别 ID 要和训练时的数据配置保持一致。
如果手里是 VOC XML,最常见做法是用开源脚本转一遍,但转换逻辑容易踩两个坑:一是 VOC 的 bndbox 坐标没有归一化,COCO 要求的是像素坐标;二是 VOC 类别名和 COCO 类别 ID 的映射表如果多一份或少一份,训练结果会莫名少几类。一个最小但完整的 COCO 子集 JSON 结构如下:
{ "images": [ {"id": 0, "file_name": "000001.jpg", "width": 1280, "height": 720} ], "annotations": [ {"id": 0, "image_id": 0, "category_id": 1, "bbox": [420.5, 100.2, 312.1, 420.8], "area": 131328.0, "iscrowd": 0} ], "categories": [ {"id": 1, "name": "helmet"}, {"id": 2, "name": "person"} ] }这个结构里iscrowd是很多人会忽略的字段:它标记该标注框内是否包含一组密集物体,值为 1 时评估阶段会跳过该框内部的预测,误标为 0 会让密集场景的 mAP 虚高。area在训练时用不到,但 COCO 官方评估会按面积区间拆分 AP,缺失时某些评估脚本会直接报错。
3.2 用 Ultralytics YOLO 跑通最小训练命令
选 Ultralytics 做为最小复现工具不是因为它最强,而是因为配置文件和命令行的约定最少。先准备一个描述数据集的 YAML:
path: ./helmet_dataset train: images/train val: images/val nc: 2 names: ['helmet', 'person']然后执行训练命令:
yolo detect train data=helmet.yaml model=yolo11n.pt epoch=50 imgsz=640 batch=16 device=0 project=./runs name=helmet_exp1model=yolo11n.pt表示加载 nano 尺寸的预训练权重,而不是从头训练,迁移学习能让收敛时间缩短一半以上;imgsz=640是输入边长,训练时会等比例缩放并补齐到 640 的倍数;batch=16在 12GB 显存上可以跑,更大的批量需要配合梯度累积或者换更小的输入分辨率。
训练过程中的关键监控指标不是 mAP,而是box_loss和cls_loss的下降曲线。如果 box_loss 前 10 个 epoch 没有明显下降,优先检查学习率而不是网络结构。生产项目里常用的 yolo 训练参数通常有这三项调整:mosaic=1.0做四图拼接增强但最后 10 个 epoch 要关闭,避免分布偏移;cos_lr=True让学习率按余弦退火衰减,比固定步长下降更平顺;close_mosaic=10指定最后多少轮关掉 mosaic,让模型在接近真实数据分布上微调。数据量少的时候mosaic是提点的主力,数据量达到数万张时反而要控制增强强度。
3.3 mAP 的计算口径与常见误区
评价指标是综述里最容易被一句话带过、但实际影响判断结论的部分。mAP 不是一个数,而是一组数的平均:先按置信度从高到低排所有预测框,与真值框做 IoU 匹配,IoU 大于阈值算 True Positive,再逐点画出 Precision-Recall 曲线,计算曲线下面积。COCO 风格把 IoU 阈值从 0.5 到 0.95 每隔 0.05 取一次,算出十个 AP 再平均,写出来是 mAP@0.5:0.95;Pascal VOC 风格只算 IoU=0.5 的 AP,数值普遍高出一截,对比不同论文或项目时必须先确认口径一致。
一个计算单类别 AP 的最小实现可以用来排查结果:
import numpy as np def compute_ap(precision, recall): # 11 点插值法,取每个 recall 区间内 precision 最大值 ap = 0.0 for t in np.linspace(0, 1, 11): p = precision[recall >= t] if len(p) == 0: continue ap += np.max(p) / 11.0 return ap这里的插值逻辑是 VOC 早期标准,COCO 用的是稠密采样加数值积分,结果会有少量偏差。评估脚本输出里的AP_small、AP_medium、AP_large对应目标像素面积小于 32²、32² 到 96²、大于 96² 三类,如果项目数据集以小目标为主,单看总体 mAP 会掩盖小目标召回率很低的问题。注意不要拿训练集上的评价数值去指导模型选型,那只是过拟合程度的度量。
4. 小目标、开放词汇与激光雷达场景:综述边界外的硬骨头
4.1 小目标检测的三个突破口:输入分辨率、上下文与切片推理
小目标定义在 COCO 里很明确:像素面积小于 32×32。这类物体在特征图上只占极少量像素,经过几次下采样后基本消失,所以最常见的第一个改动是提高输入分辨率。imgsz 从 640 提到 1280,小目标 AP 往往能涨 5 到 8 个点,但显存占用和推理延迟也同步翻倍,产线上要算的是帧率预算而不是单纯看精度。
第二个突破口是上下文信息:小目标容易和背景混在一起,需要更大的感受野来捕获“它在什么地方”。YOLO 系列在 head 前拼接不同尺度特征,本质是让浅层小目标的特征也能看到深层语义。第三个工程技巧是切片推理,用 SAHI 这类工具把大图切成若干有重叠的块,分别检测再合并结果:无人机航拍和卫星图里的目标检测几乎全靠切片才跑得动。切片的 overlap 一般设在 20% 到 50%,太小会切断跨块目标,太大则产生大量重复预测,合并时要用 NMS 把阈值降到 0.3 以下才能压住重复框。
4.2 开放词汇目标检测:从“认识固定类别”到“按文本找目标”
传统检测器做一次训练只能识别固定类别,换个场景就要重新标注重新训练。开放词汇检测想解决的是:训练时见过“车”,但测试时让它找出“救护车”的框。常见方案有两种:一是把语言模型和检测器做联合训练,GLIP 把类别文本拼成 prompt 输入网络,让文本编码和视觉特征在统一空间里对齐;另一种是把现成检测器当区域提案器,对每个候选框做裁剪,再用 CLIP 之类的大型视觉语言模型做零样本分类。
后一种方案工程上更好实现,不需要重新训练检测器:
import open_clip import torch from PIL import Image model, _, preprocess = open_clip.create_model_and_transforms( "ViT-B-32", pretrained="laion2b_s34b_b79k" ) tokenizer = open_clip.get_tokenizer("ViT-B-32") cropped = Image.open("crop.jpg").convert("RGB") image = preprocess(cropped).unsqueeze(0) text = tokenizer(["a photo of a helmet", "a photo of a person", "background"]) with torch.no_grad(): image_features = model.encode_image(image) text_features = model.encode_text(text) logits = (image_features @ text_features.T) * 100 # 温度系数 100 probs = logits.softmax(dim=-1) print(probs)这里的温度系数 100 是 CLIP 训练时用的常数值,它控制相似度分数的分布锐度;温度越高概率越接近均匀分布,低则接近 one-hot。实际项目中几个系数对效果影响很大:裁剪框边缘要留 10% 到 20% 的余量,不然目标被切掉一半分类就会错;文本模板用“a photo of a {类别}”比直接写类别名效果更稳。开放词汇检测的边界在于“开放”不等于“全能”,CLIP 对密集小目标和同类别细粒度区分的表现仍然偏弱,工业落地大多还是拿它做初筛。
4.3 激光雷达点云与三维目标检测的评价差异
三维目标检测的输入来源决定了它的算法路线差异很大。单目相机方案沿用二维检测思路,只加一个深度估计头;激光雷达方案要先做三维数据预处理。点云是稀疏且无序的,无法直接喂卷积网络,常见做法是体素化或 Pillar 化。VoxelNet 把点云划分成三维体素网格,在稀疏体素上做 3D 卷积;PointPillars 压成伪图像再走 2D 检测器,在算力和精度之间取得平衡。感知里做传感器融合时,二维检测框和三维框的匹配逻辑也与纯视觉项目不同,常用 IoU 之外的距离阈值限制来判定同一目标。
评价指标上,三维检测的 IoU 计算和二维完全不是一回事。KITTI 的评测把物体分 easy、moderate、hard 三个难度级别,AP 计算用 40 点插值而不是 COCO 的 101 点采样;3D IoU 对车辆的阈值通常取 0.7,对行人和骑行者取 0.5,因为行人外形不规则,严格要求 IoU 不现实。更关键的是,三维检测常用 BEv 视角的 AP 和三维视角的 AP 两个指标并列,前者评估鸟瞰位置精度,后者评估完整三维框精度。只看 BEV AP 会掩盖高度估计的误差,而高度误差在真实场景里直接影响路径规划的安全距离。
5. PR 曲线拆解与“mAP 涨不动”时的诊断顺序
mAP 长时间不涨,比调参更重要的是先定位瓶颈方向。第一步永远是画出每个类别的 PR 曲线,而不是盯着总体数字看。
import matplotlib.pyplot as plt for cls in class_names: precision, recall = eval_data[cls]["precision"], eval_data[cls]["recall"] plt.plot(recall, precision, label=cls) plt.xlabel("Recall") plt.ylabel("Precision") plt.legend() plt.savefig("pr_curves.png", dpi=150)PR 曲线的形状能快速区分两类问题:精度掉得早说明误检严重,模型把大量背景当成了目标,深挖时看置信度排名靠前的假例是不是集中在某一类;召回到不了高位说明漏检严重,真值框在低置信度区间就匹配不上,这时加大输入分辨率或增加小目标增强会更有效。多类别项目还要看各类别曲线的分离程度:如果某一类的 AP 明显低于其他类,优先查这一类训练样本量和标注质量,很多情况是标注框缺边或者类别样本太少导致的,模型结构反而没责任。
5.1 用错误类型分析代替直觉调参
TIDE 这类错误分析库把预测错误拆成分类错误、定位错误、背景错误和重复检测四类,跑完之后能告诉你“如果所有定位错误都修好,mAP 会涨多少”。把这次实验结果做一次错误类型统计之后再决定改哪里,通常能避开最常犯的一个浪费:类别 AP 差距大时换更大的主干网络。主干网络提升的是整体特征表达能力,对样本量不足的类别作用有限;更好的做法是对该类做过采样或复制粘贴增强,把训练分布纠正过来。
5.2 三个立即可用的针对性技巧
低置信度下漏检多,可以调低预测阶段的置信度阈值到 0.05 重新评估,观察 recall 是否明显上升,如果上升很多,说明训练正常但模型对某个类别不够自信,需要的是难例挖掘而不是换损失函数;误检集中在某几类在混淆矩阵里交叉分布,可以考虑在数据加载时加入类别重采样,让相似类别在每个 batch 里同时出现,让判别边界被反复强化;定位误差在所有错误中占比最高,关注焦点从分类头转到回归头,查看回归损失是否提前收敛,必要时对回归分支加大损失权重或者切换更精细的 IoU 变体损失。
跑一次这个脚本,把 PR 曲线和错误类型统计存进实验目录,后续每次训练对比只多一条命令,却能避开低效调参反复试错的路。
本文还有配套的精品资源,点击获取