目标检测演进:从滑动窗口到候选区域与单阶段回归的深度学习路线

发布时间:2026/9/19 10:23:08
目标检测演进:从滑动窗口到候选区域与单阶段回归的深度学习路线 简介这是一份面向目标检测初学者与研究者的综述文档系统梳理了传统目标检测方法与基于深度学习的代表性算法。文档从滑动窗口区域选择、SIFT/HOG特征、SVM分类器等传统三阶段入手进而详解R-CNN、SPP-NET等Region Proposal框架的设计思路、训练流程与性能瓶颈有助于读者建立从手工特征到卷积神经网络的目标检测发展脉络。包体为单个docx文档共1个文件大小842KB内容为完整版综述正文重点清晰、可直接阅读与批注。已有325人浏览学习适合作为课程论文参考、算法入门笔记或组会分享素材。这份文档的亮点在于不仅罗列经典模型还针对R-CNN速度慢、SPP-NET提速方案等关键问题给出了原因与解决思路能帮助读者理解目标检测演进中的核心痛点。1. 滑动窗口为什么慢目标检测从穷举到提议调参排错做过目标检测的人都有体会当输入图像分辨率上去之后第一个扛不住的不是分类器而是区域选择那一层。传统检测把滑动窗口当成万金油在不同尺度、不同长宽比下穷举整幅图像窗口数量轻易破万但真正包含目标的往往只有几十个大量计算浪费在背景上。真正改变这个局面的是从 R-CNN 系列开始的候选区域加卷积网络路线。这篇综述把传统方法、两阶段检测、单阶段回归和精度补强手段逐条梳理对我这种既要复现论文又要在工程里挑模型的人而言最有价值的是它把每个框架的瓶颈讲得很直接——滑动窗口的冗余、ROI 映射的维度问题、RPN 的 anchor 设计这些恰恰是今天调 mAP、调小目标召回率时还在反复踩的坑。2. R-CNN 到 Fast R-CNN候选区域与多任务损失2.1 R-CNN 的三段式流程为什么繁琐R-CNN 把传统检测的滑动窗口加手工特征替换成了selective search 加 CNN 特征思路本身是干净的先用 selective search 从图像里挑出约 2000 个候选区域把每个候选区域 warp 成 227×227送进 AlexNet 或 VGG16 提特征再用 SVM 分类最后做 bounding-box regression 修正位置。但从工程复现的角度看这套流程极其别扭。训练要拆成三个阶段先在 ImageNet 预训练模型上微调 CNN然后用 CNN 特征训练 SVM最后单独训练边框回归器。每个阶段的数据准备、特征缓存、模型保存都是独立的5000 张图像就能产生几百 GB 的中间特征文件磁盘和 IO 压力非常大。推理环节同样不省心。一张图提出 2000 个 region proposal每个 proposal 都要单独过一遍 CNN。VGG16 处理一张图要 47 秒几乎全部耗在重复卷积上。这背后的原因在于 R-CNN 把每个候选区域当成了独立图像处理忽略了它们共享同一张输入图的事实。我在实际复现时也犯过类似错误为了省事直接对裁剪后的 proposal 逐张跑前向结果 batch size 上不去显存占用倒是很诚实。后来才意识到问题不在于 CNN 本身而在于特征提取的粒度——一张图上所有 proposal 的卷积特征本该只算一次。2.2 SPP-Net 如何省掉 2000 次卷积SPP-Net 的改进点很直接既然 2000 个候选区域都是同一张原图的子区域那只需对原图做一次卷积然后把 region proposal 在原图的位置映射到卷积特征图上取对应区域的特征送入全连接层。VGG16 经过 4 次 2×2 pooling特征图边长是原图的 1/16原图上的 proposal 坐标除以 16 就能落到特征图上。这样一来一次卷积计算可以被所有 proposal 复用整个检测过程省掉了将近 2000 次重复的卷积前向。但这里出现一个数学问题不同 proposal 映射到特征图上的窗口大小不一致而全连接层要求输入维度固定。SPP-Net 用空间金字塔池化解决——把每个 window 划分成 4×4、2×2、1×1 的块每块做 max-pooling得到一个固定长度的向量。import numpy as np # 假设 VGG16 conv5_3 输出 512 张特征图 # 某个 region proposal 映射到特征图上的窗口为 8x10 win_h, win_w 8, 10 feat_dim 512 # SPP 金字塔层级 pyramid [1, 2, 4] total_len 0 for level in pyramid: total_len (level * level) * feat_dim print(SPP 输出维度:, total_len) # 输出 10752这里的关键在于无论窗口是 8×10 还是 20×30经过金字塔池化之后都映射到 10752 维的固定向量。代码里的pyramid [1, 2, 4]对应 1×1、2×2、4×4 三种池化粒度分别捕捉全局、中等粒度和局部细节信息。max-pooling 的选择保证了平移不变性即使目标在窗口内有轻微偏移输出特征也不会剧烈变化。2.3 Fast R-CNN 把 SVM 和边框回归收编进网络Fast R-CNN 在 SPP-Net 基础上做了两个关键改动。第一把金字塔池化精简成 ROI pooling统一将 proposal 对应的特征区域下采样到 7×7。对 VGG16 而言 conv5_3 有 512 个特征图每个 proposal 最终得到 7×7×512 的固定维度向量。第二删掉 SVM 和独立的边框回归器直接用 softmax 分类同时把回归任务作为多任务损失的一个分支加进网络。训练流程从 R-CNN 的三阶段缩短为端到端的一阶段selective search 除外微调时不再锁定卷积层分类和定位的特征可以一起更新。框架训练阶段数端到端每张图耗时VOC2007 mAPR-CNN3否47s (GPU)66.0%SPP-Net3否约 10s—Fast R-CNN1除 region proposal部分3s其中特征分类仅 0.32s66.9% / 70.0%从表格可以看出Fast R-CNN 的速度瓶颈已经从特征提取加分类转移到了region proposal 提取。selective search 本身需要 2 到 3 秒占掉了全流程的大部分时间。换句话说到了这一步检测流程中最慢的环节已经不再是 CNN而是前置的候选区域生成算法。这也是 Faster R-CNN 把 region proposal 也替换成神经网络的根本动机。2.4 微调范围对检测任务的影响SPP-Net 微调时固定卷积层只更新全连接层这个做法在新任务上会吃亏。ImageNet 分类模型的卷积特征侧重高层语义抽象但目标检测除了语义还要关注目标位置、边缘、遮挡关系这些信息更多存在于中低层特征里。Fast R-CNN 在微调时放开了一部分卷积层让特征提取器能同时适应分类和定位两个任务。后续我在迁移检测模型时也一直沿用这个思路分类任务的预训练权重只是起点卷积层的微调步长往往要比全连接层小一个量级避免破坏底层特征结构。3. Faster R-CNN 的 RPN 与 anchor 机制提议也交给网络3.1 RPN 为什么是滑动窗口又区别于滑动窗口Faster R-CNN 的核心是 RPNRegion Proposal Network它把 region proposal 的生成从 selective search 换成了一个小型卷积网络。RPN 在最后一个卷积层的特征图上做 3×3 滑窗以 ZF 模型为例最后的卷积特征图是 40×60通道数 256。每个滑窗位置通过 3×3 卷积得到一个 256 维向量然后分叉成两个分支cls layer 输出目标是前景还是背景的二分类分数reg layer 输出边框回归的 4 个参数。说它是滑动窗口是因为 RPN 本质上还是在特征图上做密集遍历说它不同于滑动窗口是因为这次滑动的对象是特征图而不是原图遍历的步长从像素级变成了 16 像素位置数从几万个骤降到 2400 个。而且 RPN 的窗口天然带 anchor 机制不需要为每个尺度重复计算一次 3×3 卷积就能覆盖多种尺度和长宽比。ZF 网络的 4 次 pooling 使特征图分辨率降低 16 倍这正是计算量能够被压缩的关键。3.2 anchor 生成与参数配置RPN 在每个滑窗位置同时预测 3 种尺度128、256、512和 3 种长宽比1:1、1:2、2:1的候选框这就是 anchor 机制。40×60 的特征图每个位置 9 个 anchor总共约 20000 个候选框。cls layer 输出 18 个分数2 类 × 9reg layer 输出 36 个参数4 个坐标偏移 × 9。import numpy as np def generate_anchors(feat_h, feat_w, stride, scales, ratios): # stride: 原图相对特征图的缩放VGG16 为 16 anchors [] for i in range(feat_h): for j in range(feat_w): cx, cy (j 0.5) * stride, (i 0.5) * stride for s in scales: area s * s for r in ratios: w round(np.sqrt(area / r)) h round(w * r) anchors.append([cx - w/2, cy - h/2, cx w/2, cy h/2]) return np.array(anchors) # 600x1000 输入conv5 特征图 40x60 anchors generate_anchors(40, 60, 16, [128, 256, 512], [1, 1/2, 2]) print(anchors.shape) # (21600, 4)代码里(cx, cy)是 anchor 中心点在原图上的坐标stride16把特征图坐标映射回原图。scales是目标的大致像素面积ratios控制宽高比。生成的全部 anchor 并非都会参与训练通常只保留与 Ground Truth 的 IoU 大于 0.7 的作为正样本小于 0.3 作为负样本。anchor 尺度的设置要参考训练集中目标的实际尺寸分布如果目标普遍偏小scales列表里就应该加入 64 甚至 32。这里有一个容易被忽略的细节边框回归分支的输出是相对 anchor 的偏移量而不是直接回归绝对坐标因此在计算 loss 前需要把 Ground Truth 归一化到与 anchor 相同的坐标系下。3.3 四阶段训练法与参数共享NIPS2015 版本的 Faster R-CNN 采用 RPN 与 Fast R-CNN 分离训练、交替共享卷积权重的策略一共四个阶段阶段操作目的1ImageNet 预训练权重初始化微调 RPN让 RPN 学会产生高质量 proposal2用阶段 1 的 RPN 生成 proposal训练 Fast R-CNN让检测器适应 RPN 的 proposal 分布3用阶段 2 的 Fast R-CNN 权重初始化 RPN固定卷积层微调统一卷积特征让 RPN 与检测器共享特征4固定 Fast R-CNN 的卷积层用阶段 3 的 RPN 生成 proposal 微调最终收敛提示阶段 3 固定卷积层只调 RPN 的头部是为了防止 RPN 微调破坏检测器已经学到的特征分布。实际跑过这套流程就会明白四阶段交替训练非常耗时。后续版本直接将 RPN 和 Fast R-CNN 合并成一个联合网络proposal 从 RPN 直接送入 ROI pooling实现真正的端到端训练。RPN 训练时的正负样本比例也需要控制一般保持 1:1 左右否则负样本过多会让网络倾向于输出全背景。3.4 RPN 替换 selective search 的实际收益用上 RPN 之后proposal 数量从 2000 个下降到 300 个而且质量更高。在 VOC0712 上训练、VOC2007 测试mAP 达到 73.2%比 selective search 加 Fast R-CNN 的 70% 高出 3.2 个百分点。速度从每张 2~3 秒提升到每秒 5 帧。从工程角度看最大的收益不仅是速度更是整个检测流程的可微性——RPN 的梯度可以回传到卷积层后续再接任何任务头都更加灵活。4. YOLO 与 SSD单阶段回归的精度与速度取舍4.1 7×7 分割与 30 维输出张量Faster R-CNN 在速度上仍然达不到实时YOLO 选择完全放弃 region proposal 分支把检测重新定义为一个回归问题。输入图像被划分成 7×7 的网格每个网格负责预测 2 个边框每个边框包含 4 个坐标信息加 1 个置信度外加 20 个类别的概率VOC所以每个网格输出 30 维向量整个输出是一个 7×7×30 的张量。import numpy as np # 模拟 YOLO 输出张量 tensor np.random.rand(7, 7, 30) # 前 5 个: 边框1 (x, y, w, h, conf) box1 tensor[..., :5] # 中间 5 个: 边框2 (x, y, w, h, conf) box2 tensor[..., 5:10] # 最后 20 个: 20 类概率 cls_scores tensor[..., 10:] # 每个网格取置信度更高的那个边框 best_conf np.maximum(box1[..., 4], box2[..., 4]) # 最终类别得分 置信度 × 类别概率 class_prob np.expand_dims(best_conf, -1) * cls_scores print(class_prob.shape) # (7, 7, 20)理解这段代码的关键在于 YOLO 的置信度定义它同时编码了这个网格里是否有目标和预测框与真实框的 IoU两个信息因此最终类别得分要乘上置信度。推理过程非常简单——设置一个置信度阈值过滤低分框再做 NMS 去除重叠框。由于每个网格感受野覆盖全图上下文信息丰富YOLO 的 false positive 比例大幅降低。但 7×7 网格对目标位置的量化太粗糙小目标和密集目标很难被准确定位这是它在精度上输给 Faster R-CNN 的最主要原因。4.2 SSD 如何用多层特征弥补网格粗糙SSD 结合了 YOLO 的回归思想与 Faster R-CNN 的 anchor 机制。与 YOLO 用全图特征在每个网格上回归不同SSD 在多个不同尺度的特征图上做回归每个位置只使用该位置周围的特征更符合局部特征决定局部目标的直觉。SSD 的 default box 分布在多个特征层上低层特征图负责小目标高层特征图负责大目标。每一层的 3×3 滑窗都对应一组预设的 default box网络直接回归这些框的类别和坐标偏移。以 SSD300 为例默认在 6 个特征层上设置不同大小的 default box特征层特征图尺寸min_size / max_size每位置默认框数conv4_338×3830 / 604conv10_210×10120 / 1406conv11_25×5240 / 2646min_size和max_size的取值与输入图像尺寸成正比决定了该层能检测的目标像素范围。多层的设计意味着 SSD 天然具备多尺度检测能力这比 YOLO 在固定网格上做回归要精细得多。VOC2007 上 SSD 达到 72.1% mAP、58 FPS速度与精度都很可观。4.3 单阶段检测的 NMS 后处理技巧YOLO 和 SSD 输出的候选框数量远少于两阶段方法但重叠框依然不少后处理通常需要两步先用置信度阈值过滤一般取 0.01 到 0.5Faster R-CNN 常用 0.05SSD 常用 0.01具体看类别分布再做 NMS。NMS 的 IoU 阈值过小会误删相邻目标过大则保留冗余框。多类别检测时要注意NMS 应跨类别执行而不是按类别独立执行否则两个不同类别的目标重叠时会互相误伤。注意SSD 的 NMS 阈值通常比 YOLO 保守更小因为 SSD 的 default box 密度更高输出框之间的 IoU 天然偏大。5. OHEM 与多层特征融合检测精度的补强手段5.1 在线难分样本挖掘的实现思路Fast R-CNN 和 Faster R-CNN 在训练时只是按固定比例随机抽取正负样本训练早期大量易分负样本主导梯度难分样本学不到。OHEM 的思路是把难分样本挖掘嵌入 SGD前向计算所有 RoI 的 loss按 loss 从高到低排序选取 top-K 个参与反向传播。import torch def ohem_select(roi_losses, keep128, min_pos8): # roi_losses: (N,) 每个 RoI 的损失 _, idx torch.sort(roi_losses, descendingTrue) hard idx[:keep] # 检查正样本数量不足时从次难样本中补充 return hardOHEM 的关键是正负样本的动态平衡。如果选择阈值过高全部选中的都是难分负样本正样本几乎不参与梯度更新。CVPR2016 的实验结果是在 VOC2007 和 VOC2012 上 mAP 提升 4% 左右。我实际使用时会额外限制正样本的最小数量防止训练初期 loss 分布偏向背景。5.2 多层特征融合对小目标检测的意义最后一层卷积特征经过多次 pooling 后语义最强但空间分辨率最低小目标在特征图上可能只有几个像素定位精度天然受限。HyperNet 这类方法把浅层细节特征、中层语义特征和深层全局特征拼接在一起融合后的特征同时保留细节与语义。在现代检测模型里FPN 就是这条路线的直接延续。对于小目标占比高的场景在浅层特征图上增加更多 anchor 或者单独训练一个高分辨率检测头往往比单纯调 NMS 阈值有效得多。5.3 上下文信息的工程化用法结合 proposal 周围区域的上下文特征可以显著提升检测效果特别是遮挡严重的目标。Inside-Outside Net 在 ROI pooling 时同时接入 proposal 外部区域的特征相当于给检测器提供了环境线索。其中一个轻量化的落地方式是只把 proposal 周围外扩 1.2 倍的特征拼接到原始特征后不额外增加网络深度。这个做法的好处是推理开销几乎为零对遮挡目标的 recall 提升却很明显。检测模型落地时我会先看 baseline 在哪些类别上失败再决定用 OHEM、上下文融合还是多层特征——优化目标不同手段不能无脑叠加。本文还有配套的精品资源点击获取