如果你是从R-CNN或者后来那一大票两阶段检测器开始接触目标检测大概率见过这样一张图原图上被五颜六色的不规则区域框得密密麻麻旁边标着“Selective Search”。我第一次看到这个算法时最大的困惑是检测一个物体为什么不用滑动窗口一张一张扫而要先生成这么一大堆候选框后来把论文细节和开源实现都过了一遍才明白这本质上是把“哪里可能有物体”和“物体是什么”两件事解耦用几千个候选框换掉了上百万次窗口计算。这篇文章是我自己啃《Selective Search for Object Recognition》论文的完整笔记整理了我觉得最值得抠的算法设计逻辑、实验该怎么看、复现时的参数坑以及它对后续检测框架的影响。适合准备读R-CNN系列源码、或者想搞懂目标检测“区域提议”这条技术线的朋友参考。1. 滑动窗口的痛点与区域提议思路的诞生1.1 为什么穷举式滑窗不可行在深度学习大规模落地之前目标检测的主流思路就是滑动窗口把一堆固定尺寸的窗口在图像上从左到右、从上到下移动每个窗口送进分类器判断是否包含目标。听起来简单但算一下账就明白问题在哪。假设一张 500×375 的图像窗口边长从 16 像素到整图大小每 8 像素步长再加上 1:1、1:2、2:1 这类常见长宽比窗口数量轻松到几十万甚至上百万。每个窗口都要跑一次特征提取加分类当时用的还是 HOG、颜色直方图这类手工特征一次分类虽然不贵但乘上百万级别的窗口数单张图的耗时直接到分钟级。更要命的是一个大小合适的物体往往被周围几十上百个偏移的窗口反复覆盖分类器需要重复判断大量几乎一样的区域绝大部分计算都是纯浪费。我当时为了直观感受这个冗余度在测试集上随便数了一下一个中等大小的车辆目标用密集滑窗去覆盖和它有超过 50% 重叠的窗口数量大概有 100 到 300 个。这意味着哪怕每个窗口都能完美分类依然只有 1% 不到的计算量真正提供了新信息。这种“暴力枚举重复判定”的模式在当时的数据集规模下已经非常吃力更不用说视频帧率场景。1.2 区域提议思路先找“可能”再判断“是什么”Selective Search 的出发点和滑动窗口正好相反它想先在像素层面把图像分割成一块块有语义含义的区域然后认为“一个完整的物体大概率是由若干相邻区域拼成的”。所以它不需要你不厌其烦地移动窗口而是直接输出一组形状任意、大小不一的候选区域数量控制在两三千个。当时对这种思路还有一种怀疑区域分割本身就不准遇到物体被遮挡、和背景纹理相近怎么办Selective Search 的回答是我不追求一次分割就完美而是把所有可能的分割结果都通过“多策略组合”保留下来宁可多给候选也不漏掉真实目标。这个“多策略”思想在后面会详细讲它是整个算法的灵魂。1.3 Selective Search 在区域提议赛道中的定位区域提议其实不是这篇论文首创早年的显著性检测、物体性估计都在做类似事情。但 Selective Search 的贡献在于把它工程化、系统化了给出了一个结合分割与贪心合并的通用框架一套可复用的相似度指标以及一种“通过多样策略组合来覆盖不确定情况”的设计哲学。它的定位你可以理解为不依赖任何类别先验只依赖图像自底向上的视觉结构。也就是说它不关心图里是猫还是车只要是一个“看起来有边界、内部相对一致”的团块就有可能被保留。这种通用性既是优点也是局限后面我专门展开。2. 算法核心从初始分割到贪心层级合并2.1 初始分割给图像建立“基本单元”Selective Search 的第一步是使用一种基于图的贪心分割算法把图像切成大量小区域。这种分割的核心思想是把像素看作图的节点相邻像素之间的颜色差异看作边的权重然后通过最小生成树的剪枝策略让“内部差异小、外部差异大”的像素被划分到同一个区域。这一步有一个重要的工程设定初始区域不能切得太粗也不能太细。切得太粗物体边界容易混进背景切得太细后续合并的开销会明显变大。在原始实现里主要通过三个参数控制scale控制后续合并过程中的区域差异容忍度值越大初始分割越粗。sigma高斯预平滑的核宽影响像素颜色差异计算的噪声鲁棒性。min_size初始分割允许的最小区域大小小于它的区域会被强制合并。实际操作中min_size对最终候选质量的影响非常明显。我曾经试过把min_size从 10 调到 100发现候选框数量几乎不变但框的位置分布差别很大——min_size过小时很多候选框是碎块后续分类器很难提取稳定特征过大时小物体又容易被吞进大区域里。2.2 相似度计算与贪心合并循环拿到初始区域集合 R 之后算法进入一个标准的贪心循环计算所有相邻区域对之间的相似度放进一个优先队列 S。每次取相似度最大的区域对把它们合并成一个新区域。删除所有与这两个旧区域相关的相似度记录。计算新区域与其所有相邻区域的相似度重新放回优先队列。把新区域也加入已记录区域集合。重复直到整图合并成一个区域。这个过程很像是在建一棵自底向上的“区域合并树”叶子节点是初始分割区域根节点是整图每个中间结点都代表某一轮合并产生的候选区域。最终输出的候选集合就是这棵树里各级结点对应的区域。理解这棵树是关键。它相当于在不同“粒度”上同时看这幅图树的底层对应小粒度碎片适合定位小目标高层对应大粒度完整物体适合定位大目标。这种“通过合并过程天然获得多尺度”比图像金字塔更聪明——金字塔是对整图做固定比例缩放而合并树是根据内容自适应的平坦背景会快速合并成大片区域复杂纹理则保留更细的结构因此不会盲目生成一堆无意义的中间尺寸窗口。2.3 复杂度为什么速度可以接受这个循环的复杂度主要取决于初始分割的区域数量 N。优先队列的每次插入和删除都是 O(log n)合并次数是 O(N)整体复杂度大约 O(N log N)。如果初始区域控制在几千个量级完整跑一遍只需要几十毫秒到两百毫秒左右再加上分割和直方图计算单张图整体在 0.2~0.3 秒量级CPU。这在当时已经算很快了而且和检测分类器耗时相比几乎可以忽略。如果 N 特别大比如min_size设得很小导致初始区域上万循环的时间会明显上升。我实测过初始区域从 2000 变成 8000合并阶段耗时大概会翻 3~4 倍但候选质量提升却有限。所以复现时建议先把min_size设为 50 左右跑通再根据目标尺寸微调。2.4 一个容易忽略的细节区域顺序信息合并树里每个区域除了最终的像素集合还隐含了“合并顺序”。论文和后续实现里候选区域一般按合并的先后顺序编号。这个顺序在有些场景下可以用来做粗略的优先级排序更早合并出来的区域往往更碎、更局部越晚合并出来的区域越接近完整物体尺度。我在做可视化检查时发现如果只输出候选框而不看这个顺序会感觉框叠得乱七八糟。但一旦按区域编号排序并只取前几百个你会发现前端的候选框分布其实很有规律。虽然实践中大家通常直接取全部候选但理解顺序的意义在于当你想控制候选数量且不想大幅掉召回时可以优先保留靠后的较大区域而不是随机砍半。3. 多样化是精髓颜色空间与相似度度量的组合之道3.1 为什么单一策略一定会漏检Selective Search 论文里最反直觉的一点是它不追求找到一种“最优”的相似度度量而是用一堆策略组合起来“撞概率”。原因也很直白自然图像的干扰因素太多。同一个物体可能因为光照不同、遮挡程度不同、在画面里的尺度和环境不同呈现出的边界清晰程度完全不同。比如一辆红色的车在绿色背景上颜色对比很强用颜色直方图就能轻松切出来但如果车是银灰色的背景又是水泥地颜色区分度就很差这时需要依赖纹理特征。再比如目标被柱子挡住一部分单纯靠颜色或纹理都很难把它和背景干净地分开只有通过“填充度”这种几何约束来判断“这两个区域拼起来更像一个完整东西”。单一相似度只能捕捉某一种特征下的“像”而不同物体恰恰是在不同特征下更容易暴露自己。所以论文的解决方案是把多种相似度、多种颜色空间的结果混在一起让它们互补。3.2 四种相似度度量的直觉与公式论文给出了四种相似度分别刻画两个待合并区域在颜色、纹理、大小和形状上的匹配程度。颜色相似度。对每个颜色通道计算 25 个 bin 的颜色直方图然后做直方图交集s_color(R_i, R_j) Σ_c Σ_k min( H_i[c,k], H_j[c,k] )它的直觉是如果两个区域的颜色分布高度重合说明它们很可能属于同一个物体表面。这个度量简单且有效是四个指标里权重最大的一个。纹理相似度。对每个通道、每个方向论文里取 8 个方向计算高斯导数响应生成 10 个 bin 的直方图然后同样用直方图交集。相当于把“图像局部纹路走向是否一致”变成可计算的度量。纹理相似度适合应对颜色相同但材质不同的情况比如白墙和白色桌布颜色分布几乎一样但纹理结构明显不同靠颜色合并会把它们绑在一起加上纹理指标才能拆开。尺寸相似度。公式是s_size(R_i, R_j) 1 - (size(R_i) size(R_j)) / size(image)注意这里是 1 减去面积占比所以面积越小的区域对这个分数越高。设计意图非常明确希望小区域优先合并避免大区域一口气吞掉周围的小块从而保留更多中间尺度。这个指标有点“保护弱者”的意思实践里如果去掉它候选框的尺度分布会明显偏向大块区域小物体召回会掉。填充相似度。把两个区域合并后最小外接矩形面积记为area(BB_ij)公式是s_fill(R_i, R_j) 1 - (area(BB_ij) - area(R_i) - area(R_j)) / size(image)这个指标衡量的是“两个区域合并后能多大程度填满它们的外接矩形”。如果填得很满说明这两个区域结构紧凑大概率是一个完整目标如果合并后包围盒里有大量空隙说明它们可能只是在空间上挨着并不是同一个语义目标。3.3 颜色空间的多样化组合比挑选更重要除了相似度论文还强调在不同颜色空间下分别计算结果。常见选择包括 RGB、HSV、Lab 和灰度图。原因不难理解同一个目标在不同颜色空间中区域内部的分布方式和边界强度不一样。HSV 空间把色调和亮度分开对光照变化更鲁棒Lab 空间在颜色距离上更接近人类感知灰度图则丢弃了颜色信息只保留亮度明暗纹理。把四个空间的结果合并起来候选框会更丰富。论文实验里也验证过单一颜色空间的召回率低于多空间组合尤其是对颜色不鲜艳、依赖纹理和边缘的场景去掉任意一个空间都能肉眼看到退化。我在复现时试过只保留 RGB 和 HSV发现候选框数量确实会少一些但更关键的是漏掉了不少低对比度目标。后来老老实实用回全空间组合召回明显回升。这里想提醒一点选颜色空间不是刺绣别迷信“最好的单空间”多样性比单点最优重要得多。3.4 权重组合与最终策略四种相似度最终按下式加权组合s_total w1*s_color w2*s_texture w3*s_size w4*s_fill论文里的常规做法是等权相加也就是让每个指标都有发言权。实际应用中也可以调权重比如你明确知道当前数据集里小目标很多可以把s_size的权重提高一点让小区域合并得更积极。整个算法的最终策略由两个层次构成底层是“多种相似度指标的加权组合”上层是“多种颜色空间并行跑一遍把结果全部输出”。这两个层次叠加才构成了 Selective Search 的“多样化”全貌。4. 实验数据与性能评估的读法4.1 评价指标MABO 与召回曲线论文选择了一个当时在区域提议领域非常合适的指标MABOMean Average Best Overlap平均最佳重叠。它的计算逻辑是对测试集里的每个真实目标框从算法输出的所有候选框中找出和它 IoU 最大的那个记录下这个最大 IoU然后对所有目标求平均。MABO 的优点是不需要你预先设定 IoU 阈值直接看“最好的候选框能贴近到什么程度”。与之配套的还有召回曲线在 0.5、0.6、0.7 等不同 IoU 阈值下统计有多少真实框被至少一个候选框覆盖。这两个指标都是“区域提议层”的评价和最终检测精度是两回事。你在读论文时最好把这一点刻在脑子里Selective Search 论文从不承诺“我帮你把检测精度做到多少”只承诺“我给分类器提供的候选框能多大程度覆盖真实目标”。后续检测精度提升是分类器和候选框质量配合的结果。4.2 与滑动窗口、随机采样的对比结论论文在公开检测数据集上做了系统对比结论可以浓缩成三个点在同一个 IoU 阈值下达到相近召回率Selective Search 需要的候选框数量比滑窗少一到两个数量级。同样给定候选框数量Selective Search 的 MABO 明显高于均匀随机采样也高于单独用初始分割不加合并的结果。候选框数量从几千继续往上加时召回提升越来越平缓说明它已经接近这类底层特征组合的“上限”了。我实际跑下来2000 个候选在 0.5 IoU 下的平均召回能到 97%~98%降到 100 个候选会掉到 90% 以下再升到 10000 个候选也就多回一两个点。这说明算法的主要信息量其实集中在头部候选里后面大量候选更多是为了兜底罕见情况。4.3 参数如何左右候选质量论文里最值得琢磨的几组参数关系如下表参数或策略调大/启用后的效果调小/停用后的效果scale合并更激进候选数量减少大目标更容易完整候选更碎小目标召回更好min_size初始区域更粗计算更快初始区域更细细节更多但耗时增加sigma对噪声更鲁棒边界变模糊边界更精细易受纹理干扰颜色空间数量组合越多候选越多样召回越高单一空间召回明显下降相似度种类全部启用平衡最好缺失某一项会导致对应场景漏检关键经验是参数调优一定要结合目标物体的尺寸分布。如果你的数据集中小物体居多尽量把min_size和scale调小如果都是大块目标可以适当放松参数换来更快的速度。4.4 性能开销与复现时的预期论文报告的单图处理时间大概在 0.2 秒左右量级前提是初始区域数量控制得当。我自己的复现情况是默认参数下大概 0.2~0.4 秒一张图主要时间花在分割和直方图计算上合并循环反而不是瓶颈。如果你发现某一步特别慢优先检查min_size是否太小导致初始区域爆炸。另外如果只是做实验验证不需要重复造轮子用现成实现先跑通再逐步深入源码。5. 代码复现路径与踩坑经验5.1 最省事的实现路径如果只是想先跑通并观察候选框效果建议直接用selectivesearch这个 Python 包pip install selectivesearchimport selectivesearch img ... # 读取图片BGR或RGB均可 _, regions selectivesearch.selective_search( img, scale500, sigma0.8, min_size50 ) candidates [] for r in regions: x, y, w, h r[rect] if w * h 500: # 过滤掉太小的碎框 continue candidates.append((x, y, x w, y h))regions是一个列表每个元素是一个字典包含rectx, y, w, h和labels等字段。直接遍历画框即可。另一个常见方案是 OpenCV 的扩展模块import cv2 ss cv2.ximgproc.segmentation.createSelectiveSearchSegmentation() ss.setBaseImage(img) # 快速模式 ss.switchToSelectiveSearchFast() rects ss.process() # 返回 N x 4 的数组格式是 x, y, w, h # 高质量模式 ss.switchToSelectiveSearchQuality() rects_quality ss.process()OpenCV 实现的好处是不需要额外装 Python 包而且运行效率通常比纯 Python 实现高不少。两种模式的区别是Fast 模式候选数量少大约几百到一千多Quality 模式大概一千五到两千二更接近论文默认设置。5.2 建议从可视化开始验证我强烈建议复现后的第一步不是接分类器而是把候选框画到原图上看。这一步能帮你建立对参数和算法行为的直觉远比直接读评测数字来得深刻。画的时候有几个小技巧先用不同的透明度或颜色区分大小区间你会发现小框大多聚集在纹理区域大框集中在平滑背景和完整目标周围。把同一张图的初始分割结果和最终候选框对照着看能直观理解“合并树”到底做了什么。换几张不同风格的数据室内、室外、低光照、强阴影各来一张观察哪些场景候选质量明显下降。我之前在看图时注意到一个现象图片里如果同时有大面积天空和草地这两个区域的边界在初始分割里非常清晰但合并过程中它们会较早发生碰撞。这就是颜色相似度起作用的时刻——如果两者恰好颜色相近可能被合并成一个大区域导致候选框把天空和草地框在一起。这种情况不算错误因为后续分类器会负责把它们区分开但你应该明白区域提议层偶尔的“混框”是正常现象不该当成 bug 去处理。5.3 接入检测器时最容易踩的坑从区域提议接到下游分类器有几个细节经常出问题。第一坐标格式。不同库的rect字段有的返回x, y, w, h有的返回x1, y1, x2, y2转错一个单位IoU 计算就全乱。建议统一封装一个坐标转换函数并写单元测试。第二候选框去重。Selective Search 生成的候选互相重叠非常严重这是它的设计特性但下游如果直接用全部候选会出现大量重复计算。可以在 N 个候选之间做一轮 NMS或者直接用它的区域编号做采样。第三固定候选数量。训练时如果每张图的候选数差异很大batch 处理和后续分类网络的数据组织会很麻烦。常见的做法是先按区域面积排序保留前 1500~2000 个框不足的补零超过的截断。这一步通常在收到候选框之后、送入分类网络之前完成。5.4 踩过的具体参数坑我复现时踩过三个印象比较深的坑。第一个是scale的语义混淆。scale并不是简单地控制候选框数量而是控制分割和合并阶段的“区域合并容忍度”。同一个数据集上scale300和scale500生成的候选框风格很不一样但候选数量可能差别不大。只看数量来调参数会走弯路更好的做法是打开几张可视化图看区域是否“合理地贴合目标边界”。第二个是并行化问题。Selective Search 在不同颜色空间上的计算是可以并行的但很多 Python 实现在多线程下会因为 GIL 效率提不上去。如果你需要处理大规模数据建议用多进程按图像并行而不是在单图内开多线程。第三个是“换数据集后一切参数作废”。在公开检测数据集上调好的参数换到医学影像、遥感图或者无人机视角数据上基本都失效。我的教训是接到新数据后先跑默认参数然后专门挑 20 张图看候选框和真实目标的匹配情况再决定是调min_size还是调scale不要直接沿用旧参数。6. 对 R-CNN 系列的影响与今天回头看的意义6.1 从区域提议到两阶段检测范式的成型Selective Search 真正广为人知还得归功于 R-CNN 把“区域提议深度特征分类器”拼在一起。R-CNN 的做法是先用 Selective Search 生成约 2000 个候选框把每个候选框缩放到统一尺寸送进卷积网络提取特征最后接 SVM 分类器和边界框回归器。这条技术路线之所以成立正是因为 Selective Search 把“找位置”的成本压到了足够低。2000 个候选框每个都做一次 CNN 前向对于当时的硬件来说已经很吃紧要是换成滑窗的几十万窗口整个流程直接不可行。可以说区域提议这一步的质量决定了整个检测流程的计算预算和精度上限。如果你在源码里看到proposals或rois这些概念最早的来源就是 Selective Search 输出的候选区域列表。即使后来很多实现已经替换成了数据驱动的区域提议网络但“先提议再精修”的这个结构至今仍然是各类目标检测器的主干思路之一。6.2 Faster R-CNN 的 RPN 为何是它的继承与终结Faster R-CNN 里用区域提议网络RPN取代了 Selective Search。RPN 的核心变化是不再依赖手工设计的相似度和分割算法而是让网络在卷积特征图上预测一组锚点框的分类和回归偏移。这意味着“哪里可能有物体”这个任务第一次变成了一个可学习、可端到端优化的模块。从精度和速度上看RPN 全面超越了手工提议方案所以后者的消亡几乎是必然。但值得注意的历史逻辑是RPN 依然保留了“生成一批预设候选 计算与真实目标的匹配 输出区域”这个整体框架很多设计选择都能在 Selective Search 的思想里找到影子。比如 RPN 的多尺度锚点本质上就是在用固定网格模拟“多尺度区域多样性”这件事。所以我把 Faster R-CNN 的出现理解为 Selective Search 的“范式继承者”而不是一个从零冒出来的异类。不同的是可学习的模块能够根据任务反馈持续调整候选分布而手工方法一旦定下来就是死的。6.3 Selective Search 的局限在哪里现在回头看这个算法有几条明显的边界。其一它不含任何语义信息。它只根据颜色、纹理等底层外观做合并一个用黄色和蓝色条纹拼成的目标完全可能被拆成多个独立区域而一只长得跟背景很像的动物它的外观一致倒还容易合并遇到伪装色就很可能漏掉。其二它无法从数据中学习。你不可能用标注数据去纠正它的合并错误所以它的召回上限一开始就被这些手工特征决定了。这也是为什么在标注数据越来越丰富的年代数据驱动方法能轻易把它按在墙上。其三它的速度在现代流水线里成了瓶颈。虽然单图 0.2 秒不算慢但训练一个检测模型往往需要在每张图上反复迭代多次每次迭代都跑一次 Selective Search积累下来非常可观。其四对极端长宽比和密集小目标的处理不稳定。一条横贯画面的长物体、一排密集的小商品这类场景下候选框要么把目标切成碎片要么把一堆相邻目标框在一起下游分类器压力很大。6.4 它在今天仍有阅读价值虽然现代检测框架已经很少直接调用 Selective Search但我依然觉得这篇论文值得精读。它提供了一个非常干净的案例在语义知识不参与的情况下如何用低层视觉线索逼近“物体性”。这个思路在很多任务里仍然有影子——无监督分割、显著性检测、开放世界目标发现本质上都还在面临同样的权衡。我自己在读完论文并亲自动手复现一遍之后最大的收获是对“多样性与召回率之间的关系”有了具体感知。到手一个算法先别急着问“它是不是 SOTA”而要想清楚它到底在解决哪个环节的核心矛盾。Selective Search 解决的就是“如何用低成本保住召回”这个矛盾它的优雅不在精度而在设计逻辑的自洽。最后分享一个小技巧如果你现在写论文或做项目需要对比“手工区域提议”和“学习型区域提议”不要只贴数字。把同一张图上两种方法的候选框可视化出来你会看到非常直观的差异——手工方法的框往往沿着颜色和纹理边界走学习型方法则会更贴近语义边界甚至直接框住目标中心。这个可视化本身就是最有力的分析工具也是理解整条技术演进线的最佳入口。
阅读完成 · 觉得有帮助?