news 2026/10/21 5:16:26

选择性搜索算法全解:从滑动窗口到候选区域生成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
选择性搜索算法全解:从滑动窗口到候选区域生成

如果你是从目标检测这一路看下来的,大概率早晚会碰到选择性搜索(Selective Search)这个名字。我最近重读这篇论文,和第一次读时的感受完全不同:当年只觉得它是“给R-CNN提候选框的预处理步骤”,现在再看,它其实是手工特征时代对“如何用最少框覆盖最多目标”这个问题给出的最完整答案。这篇笔记会把这个算法彻底拆开——为什么滑动窗口和像素分割都不够用,四个相似度度量分别解决什么问题,分层合并是怎么执行的,实验结果要怎么看,以及我在实际工程里调用相关实现时踩过的坑。适合正在啃检测论文、想自己复现候选区域算法,或者在做图像检索候选召回的同学参考。

1. 问题原点:为什么滑动窗口和分割都不够用

在讨论选择性搜索的具体做法之前,得先回到2002年前后到2010年代初那个“目标检测还在用滑窗”的时代。今天看R-CNN、YOLO、DETR会觉得一切理所当然,但当时提出候选区域生成,本质上是在回答一个很朴素的问题:一张图里物体到底在哪?这个问题比“物体是什么”更费劲,因为位置搜索的空间实在太大了。

1.1 暴力滑窗为什么吃力不讨好

滑窗的思路非常简单:把分类器当成一个模板,在图像上按照不同尺度、不同长宽比移动,逐个位置打分,判断窗口里有没有目标。听起来很直接,但真去算一下开销,会发现这是个极其吓人的算法。

以一张常见的800×600图像为例,仅以4像素为步长、5个尺度、4种长宽比来枚举,窗口数量就是几十万量级。这还算保守,实际为了不漏检,很多系统会用更小的步长和更多的尺度组合,窗口数量很容易冲到百万级。每一个窗口都要过一次特征提取和分类器推理,哪怕早期的分类器是线性SVM或者浅层Boosting,整体耗时也难以接受。到了深度学习时代,情况更糟——CNN本身就贵,把它放到百万级窗口上去跑,基本等于不可用。

但滑窗更隐蔽的问题不是速度,而是样本不均衡。一张图里有目标的窗口可能只有几个到几十个,其余全是背景。分类器面对的这种正负样本比例,轻则让训练过程非常不稳定,重则让模型学会“输出背景”这种廉价的局部最优。即使做难例挖掘,也会增加一套复杂的训练基建。换句话说,滑窗把“位置搜索”的压力全部转移给了分类器,简单是简单,但整个系统变得又慢又脆。

1.2 分割结果为什么不能直接用

既然滑窗那么费劲,有人自然会想:能不能先用图像分割把物体轮廓找出来,然后只对分割区域做分类?

这个想法的诱人之处在于,分割区域比矩形窗口更贴合物体边界。但真实的分割结果远没有想象中可靠。同一只猫,有的分割算法把头、身体、尾巴分成好几块;有的又把猫和它趴着的沙发合并成一个区域。过度分割和欠分割同时存在,而且几乎无法用一个固定参数同时解决。原因在于,分割算法不知道你最终要识别什么。它只能根据颜色、纹理、边缘这些底层线索决定合并,而“猫是一整个物体”“沙发不是猫身体的一部分”这种语义信息,底层线索根本承载不了。

于是大家发现,问题的本质不在于“得到一个完美分割”,而在于“得到一堆足够覆盖真值框的候选区域”。目标边界本身在多尺度下是含糊的:大目标可以由多个小区域合并而来,小目标可能只有初始分割里的一个小块。想用一个粒度解决所有目标,注定失败。选择性搜索的核心洞察就是:不做单选题,把所有可能粒度下的区域都保留下来,让后续识别阶段自己挑。

2. 核心策略拆解:多样性如何变成候选区域

读这篇论文时会反复看到“多样性”这个词。很多传统方法恨不得把所有策略收敛到一组最优参数上,选择性搜索却故意把特征、颜色空间、相似度度量全部堆在一起。这不是炫技,而是针对“未知目标”的一种工程哲学:既然无法预知目标长什么样,就让尽可能多的互补视角参与候选生成。

2.1 颜色空间与补位思维

选择性搜索在多个颜色空间下计算特征,其中常见的包括RGB、HSV、Lab等。为什么要做这件事?不同颜色空间对同一场景的刻画方式不一样:RGB直接反映传感器响应,光照变化会让数值剧烈抖动;HSV把色相与亮度分开,对光照有一定稳定性;Lab在感知上更接近人眼对颜色差异的判断,对阴影区域的表现也更好。

拿一段天空来说,RGB里从深蓝到浅蓝的渐变可能被切得很碎,而HSV或Lab可能把整片天空视为同质区域。反之,某些在RGB里分得很开的物体,在Lab里可能非常接近。因此,同一个初始分割结果在不同颜色空间下计算相似度时,合并路径完全不同。让多个颜色空间各自跑一遍合并,最后把产生的区域全部汇总,相当于把多个“观察视角”的结果做并集。当时的经验反复证明:这种补位思维比单独选一个“最强颜色空间”更有价值,因为你永远不知道一个陌生类别的目标在哪个特征空间里最容易被合并出来。

2.2 四个相似度度量怎么算

选择性搜索方案里最常被引用的,就是四个相似度度量的组合。简单理解,它们分别管颜色、纹理、区域大小和空间填充程度。

颜色相似度的底层是区域颜色直方图。对每个区域,在某个颜色空间各通道上统计直方图并做归一化,然后计算两个直方图对应bin的最小值之和:

S_colour(r_i, r_j) = Σ min(c_i^k, c_j^k)

这个值越大,说明两个区域的颜色分布重叠越多,越像。为什么不直接用欧氏距离?因为直方图的最小值累加衡量的是分布重叠面积,对区域面积大小的变化不那么敏感,更符合“颜色相似”的直觉。

纹理相似度也用类似方式计算,只不过把颜色直方图换成基于局部梯度响应统计出的纹理直方图。简单理解,它关心的是两个区域的“图案”是否一致。草地和树叶颜色可能接近,但纹理差异很大;湖泊和天空颜色接近,纹理却很均匀。有了纹理项,就能避免只靠颜色把不该合并的区域强行并在一起。

第三个是大小相似度,公式可以理解为:

S_size = 1 - (size_i + size_j) / size_image

注意这里的关键是,它并不直接等于“鼓励两个区域一样大”,而是通过一项归一化的惩罚,阻止某个大区域持续吞并周围小区域。如果只看颜色和纹理,一个面积很大的背景区域很容易不断吃掉相邻的小物体,导致后续候选框全是大块,小目标全没了。加入大小项之后,小区域之间的合并得分相对更高,整体合并顺序更均衡。

第四个是填充相似度。两个区域合并后,它们的最小外接矩形里通常会有空隙。如果空隙占比很大,说明这两个区域虽然相邻,但空间上更像“被矩形硬凑到一起”,合并结果不紧凑。填充相似度就是惩罚这种包围盒面积远大于区域面积之和的情况。

最终的综合相似度就是四者的加权和。论文默认权重相同,工程上完全可以根据任务调整,比如小目标多的场景就把大小相似度的权重调高一点。读论文时最容易忽略的是这部分设计动机:四个度量不是四个并列的技巧,而是分别对付颜色噪动、纹理差异、尺度失衡和形状不规则这四类问题。

2.3 初始分割方法:一种基于图的图像分割算法

所有相似度计算都建立在一组初始区域上。选择性搜索没有直接从像素开始逐像素合并,而是先使用一种基于图的图像分割方法,把图像切成一堆小区域。

这个算法把图像看成一张图,像素或小超像素是节点,相邻像素之间的外观差异是边权。它按照差异从小到大逐个判断两个区域是否合并,只有当两区域之间的最小连接差异小于各自内部差异时才会并到一起。参数sigma控制图像平滑程度,k控制合并阈值,k越大初始区域越粗;min_size则限制了分割结果的最小区域面积,太小的区域会直接被并入邻居。

这步的价值在于,基于图的分割能提供“看起来像物体部件”的初始单元。直接对像素做合并会产生巨大计算量,而且很多像素级噪声会干扰高层判断。借助超像素级别的初始区域,后面的层次化合并既快又相对稳定。

3. 完整流程与参数玄机:分层分组是怎么执行的

理论策略说清楚后,整个算法的骨架就出来了:先得到初始区域,然后进入一个循环,每次选取最相似的两个相邻区域进行合并,再更新相似度集合。这个过程不断重复,直到整张图合并成一个区域。

3.1 层次化分组迭代伪代码

按我的理解,用伪代码表示如下:

输入:图像 I R0 = GraphBasedSegmentation(I) // 初始区域集合 S = empty // 相似度集合 for 每个相邻区域对 (ri, rj): S.add(Sim(ri, rj)) while S not empty: (ri, rj) = S 中相似度最大的一对 rt = ri ∪ rj 从 S 中移除所有包含 ri 或 rj 的条目 for 每个与 rt 相邻的区域 rk: S.add(Sim(rt, rk)) R = R ∪ {rt} 输出:R 中每个区域对应的最小外接矩形

这段流程里最容易漏掉的一点是:中间状态也要保留。只保留最终合并结果的话,大目标有了,小目标全丢了。选择性搜索的设计特点是让每条合并路径上的状态都作为候选,从最初的细碎超像素到中间逐渐合并的块,再到最终整图,每个历史区域都被记录为可能的物体边界。正是这种“历史状态全保留”,才保证了多尺度覆盖。

另一个值得注意的点是,相似度集合是动态更新的。两个区域合并成一个新区域后,新区域与所有邻居的相似度都要重新计算。这意味着前期的合并路径会影响后续所有决策,是一种典型的贪心层次聚类。它不保证全局最优,但胜在效率高,而且良好的初始分割和多样化的相似度组合,已经足够让它覆盖绝大多数目标。

3.2 参数k=100和k=1000不是候选框数量

读这篇论文时很多新手会被k=100、k=1000这两个参数直接劝退,误以为它们是“候选框数量”。实际上它们是初始分割阶段的分割粒度参数,不是最终输出候选框的个数。

k=100时初始分割更细,产生的小区域更多,后续合并路径更长,候选框数量通常也更多;k=1000时初始分割更粗,初始块的语义更完整,但小目标可能在第一步就被并进相邻大区域,导致后续候选框里漏掉它。工程上,k的选择要和你的目标尺度分布挂钩。如果你做的是小目标检测,初始分割需要尽量细;如果你做的是大目标为主的场景,过细的分割只是徒增计算量。

真正影响最终候选框数量的是整个迭代过程中产生的区域总数。一个常见尺寸的图像,在默认参数下产生的候选框通常有几千个。OpenCV实现的Fast模式会额外做一些缩减,Quality模式则会保留更多策略,候选框数量明显更多,召回也更好。

3.3 从数量看效率:从百万级窗口到几千候选框

选择性搜索最直观的成果,就是把“位置搜索空间”压缩了几个数量级。滑窗在普通图像上生成几十万到上百万个窗口,而选择性搜索通常只输出几百到几千个候选区域。这张对比表心里要有数:

方法大致候选数量特点
暴力滑窗10^5 ~ 10^6全穷举,召回高但计算量极大
选择性搜索10^2 ~ 10^3借助视觉先验,大幅压缩搜索空间

理解这个数量变化的关键在于“先验”。滑窗不假设任何关于物体的信息,所以只能靠密度弥补;选择性搜索认为,物体的边界大概率与分割边界重合,物体内部区域之间应该有较高相似度,不同物体之间相似度较低。把这些先验做成合并规则,就能在保留高召回的同时去掉大量重复的滑窗位置。当然,代价是它可能漏掉完全不符合这些先验的罕见目标,这也是后来基于深度学习的候选区域生成想要进一步解决的。

4. 实验结果怎么看:ABO、MABO与识别增益

论文的实验部分非常值得细读,尤其是评估指标的设计。如果只记住“MABO约0.8”这种数字,很快就忘了它到底在衡量什么。

4.1 ABO是什么、MABO怎么算

ABO全称是Average Best Overlap,可以逐类统计。假设有一类叫“猫”,测试集里所有真实猫框先列出来,每个真实框去候选集合里找一个IoU最大的候选框,记录这个最大IoU,再对所有猫的真实框求平均,得到猫这个类别的ABO。把所有类别的ABO再平均一次,就是MABO。

这个指标只回答一个问题:给定一个真值框,我们生成的候选集合里“离它最近的那个框”有多接近它。MABO越接近1,说明候选框集合越有可能把目标完整框出来。它不关心候选框里有多少是背景,也不关心分类器能不能把它认出来,因为这一阶段的目标只是“别漏掉目标的位置”。

理解ABO之后,再去看论文里的曲线就顺畅多了:随着候选框数量从100增加到1000,MABO持续上升;到几千个候选框以后,曲线开始变平。这说明增加候选框数量确实能提升召回,但边际收益递减,而且每一张图多出的候选框都会给后续分类阶段带来额外成本。

4.2 多样性组合的效果

论文里最有说服力的实验,是不同策略组合的消融对比。只使用单一颜色空间加单一相似度时,MABO明显低于组合版本;增加颜色空间数量、加入四种相似度之后,性能稳步上升。这说明每个策略都在贡献不同的“漏检解”,合在一起才能互补。

在读这类消融表格时,我最建议关注的不是最高分那列,而是每一列去掉某个策略后掉多少分。如果去掉某个相似度度量后性能几乎不掉,说明这个度量在当前数据集上是冗余的;如果掉得很厉害,说明它是关键路径。真实工程里,这种“哪些部件撑起了效果”的信息,比一个冷冰冰的SOTA数字有用得多。

4.3 在识别任务上:候选区域加分类器

选择性搜索本身不识别物体,它只负责产出“可能包含物体的区域”。在早期检测流程里,这些区域会交给分类器,比如先提取CNN特征再用SVM分类,或者直接用一个CNN对每个候选框做分类。由于候选框数量已经从百万级降到几千级,分类器终于承担得起,整个流程才变得可行。

用大约2000个候选框配合CNN特征,在当时已经能让检测精度出现大幅提升,这背后其实是“候选区域质量”和“分类器能力”的分工:候选生成负责保证召回,分类器负责排除误检和给出类别。这个经典的“两步走”范式,后来在Faster R-CNN中演变成“候选生成网络加分类网络”的一体化结构,但分工逻辑一脉相承。

5. 实操手记:在检测与检索项目里怎么用好它

论文是一回事,跑起来又是另一回事。选择性搜索在开源计算机视觉库里就有现成实现,但实际使用时的小坑不少,简单记录一下我的做法和踩坑记录。

5.1 OpenCV一行接入,但坑也明显

如果你只是想在实验里快速看效果,用OpenCV是个最省事的路径。核心代码很短:

import cv2 img = cv2.imread("demo.jpg") ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation() ss.setBaseImage(img) # Fast模式更快,候选框数量较少 ss.switchToSelectiveSearchFast() rects = ss.process() proposals = [] for x, y, w, h in rects[:1000]: if w * h < 1000: continue proposals.append((x, y, x + w, y + h))

想要更高召回时,可以把switchToSelectiveSearchFast()换成switchToSelectiveSearchQuality()。Quality模式会运行更多颜色空间和相似度组合,候选框数量明显更多,速度也更慢。

最需要注意的坑是:cv2.ximgproc不在常规的opencv-python包里,必须安装opencv-contrib-python。如果你发现from cv2 import ximgproc报错,很大概率就是装错了包。另外,不同版本的OpenCV对API命名略有差异,建议先查一下当前版本的文档再动手。

5.2 参数调优的几个实测心得

先跑默认参数,再用真实标注做量化评估,不要凭肉眼判断。把数据集的真实框和生成的候选框做IoU匹配,统计IoU>0.5时的召回率,这个数字能直观反映你的候选框质量。

小目标检测场景下,把min_size调小、把k调小,让初始分割更碎,能明显改善小目标漏检。但代价是候选框数量增加,后续分类网络会变慢。大目标为主的场景则相反,k可以适当调大,候选框更干净,分类阶段压力也小。

候选框数量不是越多越好。2000个候选框和5000个候选框在召回上的差距往往不大,但分类阶段的耗时几乎线性增加。更重要的是,大量高度重叠的候选框会让后处理变得麻烦,通常我会加一步非极大值抑制,按候选框的尺寸或区域得分筛掉重叠严重的框。

5.3 常见问题速查表

现象可能原因参考处理方式
很多目标被合并到一起初始分割粒度太粗,k或sigma偏大调小k,调小min_size,改用Quality模式
小目标大量漏检初始分割把小目标直接并进背景降低min_size,提高候选框数量上限
候选框重叠严重层次合并天然产生大量重叠区域对候选框做非极大值抑制
导入cv2.ximgproc报错安装的是opencv-python而非contrib包安装opencv-contrib-python
视频场景跑得太慢Quality模式在每帧都运行完整合并降低输入分辨率,使用Fast模式,或间隔帧做候选生成

6. 从这篇论文到现代目标检测:为什么还值得读

有些论文的价值在于它本身的效果,有些论文的价值在于它打通了一条路径。选择性搜索属于后者。

6.1 R-CNN时代的两步检测范式

早期检测框架被滑窗统治,分类器需要在巨大搜索空间里反复验证。选择性搜索出现后,思路变成了“先生成候选区域,再对候选区域分类”。R-CNN、Fast R-CNN都在用这个思路,只是候选生成默认使用选择性搜索,分类器从SVM换成了CNN。

Faster R-CNN用区域提议网络RPN替代了手工候选生成,但仔细看会发现,RPN承担的职责和选择性搜索是高度相似的:给出少量高质量候选框,只是候选框的生成条件变成了“从深度特征里学出来的,而不是手工规则拼出来的”。到了DETR这类基于Transformer的检测器,连候选框概念也被弱化,模型直接用查询向量回归目标框,但训练时依然要做真值匹配。本质上,目标检测始终在解决同一个问题:如何在巨大位置空间里高效率地找到物质边界。

6.2 手工特征时代的集大成者

回头看,选择性搜索是手工特征时代对“通用候选生成”最完整的一次总结。它把颜色、纹理、尺度、空间关系这些底层线索全部纳入一个可解释的合并框架里,效果虽然不是每项第一,但组合后的通用性很强。这也是为什么它在深度学习早期能成为事实标准,直到数据驱动的方法拥有足够强的特征表达能力。

论文的消融实验设计也值得反复学习:把算法拆成一个个组件,逐个分析每个策略的贡献。这种做法在今天看依然很奢侈,因为很多现代方法论文只报完整结果,不做细致消融。想培养对算法设计的直觉,这篇是很好的范本。

我以前自己动手实现过简化版的选择性搜索,最深的体会是——分层合并本身不难,难的是理解为什么贪心合并能work。后来想明白了,它依赖的是自然图像的一个基本性质:物体内部区域之间在某种特征空间下高度相似,而物体边界附近通常有突变。只要初始分割足够细、相似度度量足够多样、中间状态全保留,贪心策略就能覆盖目标的多尺度边界。这个思路放在今天的很多召回系统里依然适用。

每当我需要设计一个候选召回链路时,都会刻意问自己:我有哪些互补的观察视角?它们之间的差异够不够大?如果只有一套颜色特征,遇到外貌变化大的类别一定会漏;如果能把颜色、纹理、边缘、空间分布做成多个策略并联,召回就会稳得多。这大概就是选择性搜索这篇论文留给我的最值钱的经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 12:56:33

uniapp App端tabBar中间凸起按钮实现:cover-view与自定义组件方案对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 8:56:10

RGF 仓颉版位图画刷实战指南:从位图与图层创建到平铺填充渲染

图形学桌面应用 【免费下载链接】RGF_CJ RGF是仓颉Windows系统下的通用渲染框架&#xff08;兼应用开发框架&#xff09;。RGF仓颉版&#xff08;后续简称"RGF"&#xff09;基于RGF(C/C版)封装优化而来。RGF为开发者提供轻量化、安全、高性能以及高度一致性的2D渲染能…

作者头像 李华
网站建设 2026/10/18 10:56:52

Prim算法详解:从手算到代码实现最小生成树

1. 从一张网线布线图说起&#xff1a;Prim算法到底在解决什么问题很多刚接触图论的朋友第一次听到“最小生成树”这个词&#xff0c;脑子里冒出来的问号比代码行数还多。我当初也一样&#xff0c;直到有一次帮朋友规划一个小型办公区的网线走线——六个工位&#xff0c;两两之间…

作者头像 李华
网站建设 2026/10/20 19:40:15

Django与Flask整合开发公务员考试系统实践

1. 项目概述与核心价值这个公务员考试练习系统项目采用了前后端分离的架构设计&#xff0c;后端同时整合了Django和Flask两大Python框架&#xff0c;前端使用Vue.js实现动态交互界面&#xff0c;开发环境选择了PyCharm这款专业的Python IDE。这种技术组合在当前教育类Web应用中…

作者头像 李华