news 2026/10/15 16:19:11

selective_search 示例实战:从候选框生成到二次开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
selective_search 示例实战:从候选框生成到二次开发

简介:这份资源是选择性搜索(Selective Search)的 Python 入门示例,面向计算机视觉初学者及目标检测方向的学习者,帮助理解如何用超像素分割与区域生长策略生成候选目标区域,并衔接 R-CNN、Faster R-CNN 等检测模型。压缩包共 19 个文件,以 12 个 png 与 2 个 jpg 图像素材、3 个 py 脚本、1 个 md 说明和 1 个 license 为主,整体约 53KB,体积轻巧便于本地运行调试。代码按步骤拆分为搜索、过滤、特征提取等模块,配合示例图片可直观观察候选框的生成与筛选过程,适合作为算法原理验证与二次开发的起点。目前已有 285 人学习下载,读者可借此掌握选择性搜索的完整流程,理解超像素合并与区域排序思路,为后续目标检测项目打下基础。

1. 从一张图里找出所有候选框:这个 selective_search 示例到底能干什么

如果你写过目标检测相关的代码,大概率遇到过这样的场景:模型训练完了,推理时却不知道该把哪些区域送进网络。总不能把整张图滑窗滑一遍吧,算力扛不住。这时候就需要一种「区域提议」机制,先帮我们把可能藏有目标的候选框筛出来,再交给后续的分类或检测模型。selective_search 就是干这个的经典算法,而这个示例项目,把它的核心流程用 Python 拆成了能直接跑、能直接改的代码。

它适合两类人:一类是刚接触目标检测、想搞明白「候选区域是怎么来的」的新手,另一类是要在自定义数据集上快速验证区域提议效果、需要拿代码当脚手架改的老手。项目本身不依赖深度学习框架,纯 Python 加基础图像处理库就能跑,对环境的容忍度很高,拿来当理解 selective_search 的入口再合适不过。

2. 环境准备与最小运行路径:从零把示例跑起来

2.1 依赖清单与安装方式

这个示例的依赖非常克制,核心就几个:图像读写、数值计算、以及可选的图像显示。常见做法是用numpy做矩阵运算,opencv-python或Pillow做图像加载和绘制,matplotlib用来把候选框可视化出来。如果你不想装 OpenCV,用 Pillow 也能跑通,只是绘制框的时候接口略有差别。

安装命令我一般直接写在一个requirements.txt里,方便复现:

# 基础依赖,版本不锁太死,兼容性更好 pip install numpy pip install opencv-python pip install matplotlib

提示:如果你的环境里已经有 Pillow,可以不用装 opencv-python,把代码里cv2.imread换成Image.open即可,但要注意通道顺序从 BGR 变成 RGB。

2.2 目录结构与文件职责

示例项目的目录通常不会太复杂,我按常见组织方式给你梳理一遍,你拿到源码后可以对照着看:

selective_search/ ├── selective_search.py # 算法主入口,包含区域合并与候选框生成 ├── utils.py # 图像读写、相似度计算、框的绘制等辅助函数 ├── demo.py # 跑通示例的脚本,加载图片并输出候选框 ├── images/ # 放测试图片的目录 └── requirements.txt # 依赖清单

selective_search.py是核心,里面会定义区域初始化、相似度度量、合并策略这几个关键部分。utils.py负责把颜色空间转换、纹理梯度计算这些琐碎操作封装起来。demo.py则是给你一个「一键运行」的入口,不需要你手动传一堆参数。

2.3 最小运行命令与预期输出

跑通示例通常只需要一行命令:

python demo.py --image images/test.jpg --output output.jpg

执行完之后,你会得到一张画满候选框的图片,框的数量一般在几百到两千之间,取决于图片复杂度和阈值设置。同时终端会打印出生成的区域总数和耗时。如果你看到框密密麻麻盖在物体周围,说明流程是通的。

参数说明:--image指定输入图片路径,--output指定输出图片路径。有些示例还会暴露--scale参数控制图像缩放比例,缩放越小跑得越快,但小目标可能被漏掉。我一般先用默认值跑一遍,确认没问题再调。

2.4 运行失败时先看什么

如果报错ModuleNotFoundError,先检查依赖是否装全,尤其是cv2和matplotlib这两个容易漏。如果图片读进来是 None,多半是路径写错了或者图片格式不支持,换成绝对路径试试。如果跑完输出图是空白的,检查一下绘制框的时候坐标有没有越界,或者框的线宽设成了 0。这些坑我后面还会在避坑章节里细说。

3. 核心模块拆解:区域初始化、相似度度量与合并策略

3.1 区域初始化:把图像切成小碎片

selective_search 的第一步是把图像过度分割成很多小区域,这一步通常用 Felzenszwalb 算法或者类似的图分割方法。示例里一般会调用现成的分割函数,把图像变成几百个小块,每个小块内部颜色和纹理比较一致。

import numpy as np from skimage.segmentation import felzenszwalb def init_regions(image, scale=100, sigma=0.5, min_size=50): """ 用 Felzenszwalb 算法做过度分割 :param image: 输入图像,RGB 格式 :param scale: 控制分割粒度,越大块越少 :param sigma: 高斯平滑参数 :param min_size: 最小区域像素数 :return: 分割标签图,每个像素属于哪个区域 """ segments = felzenszwalb(image, scale=scale, sigma=sigma, min_size=min_size) return segments

逻辑说明:felzenszwalb返回的是一个和图像同尺寸的标签矩阵,每个像素值代表它属于哪个区域。scale参数很关键,调大之后区域数量减少,后续合并速度变快,但可能把不同物体合并到一起。min_size防止出现太小的碎块,太小的话相似度计算会不稳定。

参数建议:一般scale设在 100 到 300 之间,sigma保持 0.5 到 1.0,min_size根据图片分辨率调整,小图设 20 到 50,大图设 100 以上。

3.2 相似度度量:颜色、纹理、大小、填充度

有了初始区域之后,需要定义两个区域之间有多「像」。selective_search 原文用了四个维度:颜色相似度、纹理相似度、大小相似度、以及填充度相似度。示例里通常会把这四个加权求和,得到一个综合相似度。

def similarity(r1, r2, image, segments): """ 计算两个区域的综合相似度 :param r1: 区域1的标签 :param r2: 区域2的标签 :param image: 原图 :param segments: 分割标签图 :return: 0到1之间的相似度分数 """ # 颜色相似度:比较两个区域的颜色直方图 color_sim = color_similarity(r1, r2, image, segments) # 纹理相似度:比较梯度直方图 texture_sim = texture_similarity(r1, r2, image, segments) # 大小相似度:小区域优先合并 size_sim = size_similarity(r1, r2, segments) # 填充度相似度:合并后形状越接近矩形越好 fill_sim = fill_similarity(r1, r2, segments) # 加权求和,权重可以根据场景调 return 1.0 * color_sim + 1.0 * texture_sim + 0.5 * size_sim + 0.5 * fill_sim

逻辑说明:颜色和纹理相似度是主力,权重通常给 1.0;大小和填充度是辅助,权重给 0.5 左右。颜色相似度一般用 HSV 空间的直方图交集,纹理相似度用 LBP 或者 SIFT 类似的梯度统计。大小相似度让小的区域先合并,避免大区域吞掉小区域。填充度相似度鼓励合并后形状更紧凑。

参数说明:权重不是固定的,如果你发现候选框偏向大物体,可以把大小相似度权重调低;如果框的形状很怪,把填充度权重调高。

3.3 合并策略:贪心合并与候选框生成

有了相似度之后,就是不断找最相似的两个区域合并,直到整张图变成一个区域。每合并一次,就把当前所有区域的外接矩形记录下来作为候选框。

def merge_regions(image, segments, similarity_fn): """ 贪心合并区域,生成候选框 :param image: 原图 :param segments: 初始分割标签 :param similarity_fn: 相似度函数 :return: 候选框列表,每个框是 (x, y, w, h) """ regions = {i: np.where(segments == i) for i in np.unique(segments)} candidates = [] while len(regions) > 1: # 找到最相似的一对区域 best_pair = None best_score = -1 for r1 in regions: for r2 in regions: if r1 >= r2: continue score = similarity_fn(r1, r2, image, segments) if score > best_score: best_score = score best_pair = (r1, r2) # 合并这对区域 r1, r2 = best_pair regions[r1] = np.concatenate([regions[r1], regions[r2]]) del regions[r2] # 记录当前所有区域的外接矩形 for r in regions: ys, xs = regions[r] x, y = xs.min(), ys.min() w, h = xs.max() - x + 1, ys.max() - y + 1 candidates.append((x, y, w, h)) return candidates

逻辑说明:每一轮遍历所有区域对,找相似度最高的合并。合并之后把新区域的外接矩形加入候选框列表。这样跑完一轮,候选框数量会非常多,通常需要后续用 NMS 或者按面积过滤掉一部分。

参数说明:regions字典的 key 是区域标签,value 是像素坐标数组。合并时直接拼接坐标数组,简单粗暴但有效。候选框去重可以用集合或者简单的 IoU 过滤。

3.4 候选框后处理:去重与排序

原始候选框数量可能上万,直接送进后续模型不现实。常见做法是按面积排序,保留前 N 个,或者用 IoU 做非极大值抑制。

def nms_boxes(boxes, iou_thresh=0.5): """ 简单的 NMS 去重 :param boxes: 候选框列表 [(x, y, w, h), ...] :param iou_thresh: IoU 阈值,超过就抑制 :return: 去重后的框 """ boxes = sorted(boxes, key=lambda b: b[2] * b[3], reverse=True) keep = [] for box in boxes: if all(iou(box, k) < iou_thresh for k in keep): keep.append(box) return keep

逻辑说明:先按面积从大到小排序,然后依次保留与已选框 IoU 低于阈值的框。这样能去掉大量重叠的冗余框。iou_thresh一般设 0.5 到 0.7,设太低会误删不同物体的框。

4. 避坑与常见问题:跑 selective_search 时容易翻车的几个点

4.1 现象:跑完一张图要几十秒甚至几分钟

原因:相似度计算是 O(n²) 的,初始区域数量如果太多,合并轮次会爆炸。常见做法是调大scale参数减少初始区域,或者对图像先做缩放。

解决:把scale从默认的 100 调到 200 以上,或者把输入图片长边缩到 500 像素以内。如果还慢,可以在相似度计算里加缓存,避免重复算同一对区域。

4.2 现象:候选框大量重叠,几乎盖满整张图

原因:合并过程中每一轮都记录所有区域的外接矩形,导致同一个物体被反复记录。另外 NMS 阈值设得太高,没起到去重作用。

解决:降低 NMS 的iou_thresh到 0.5 以下,或者按面积过滤掉过大和过小的框。我一般会保留面积在图像面积 1% 到 50% 之间的框。

4.3 现象:小目标完全找不到候选框

原因:初始分割的min_size设得太大,小目标在第一步就被合并掉了。或者图像缩放太狠,小目标像素太少。

解决:把min_size调到 10 到 20,同时不要过度缩放图像。如果还不行,可以单独对小目标区域做一次高分辨率的分割。

4.4 现象:颜色相似度计算报错,提示直方图维度不匹配

原因:不同区域的像素数量不一样,直方图 bin 数没统一。或者图像通道顺序搞混了,BGR 当成 RGB 算。

解决:固定直方图的 bin 数量,比如每个通道 25 个 bin,然后做归一化。读图之后统一转成 RGB,再送进相似度计算。

4.5 现象:合并到最后只剩一个区域,候选框却很少

原因:合并策略太激进,相似度阈值设得太低,导致区域很快合并成整张图。或者候选框记录逻辑只在合并后记录,漏掉了中间状态。

解决:在每一轮合并前都记录一次当前区域的外接矩形,而不是合并后才记录。另外可以设置一个最大合并轮次,提前停止。

5. 二次开发与改造:把示例改成你自己的区域提议模块

5.1 替换分割算法

示例默认用 Felzenszwalb,但你可以换成 SLIC 超像素或者简单的网格分割。SLIC 对纹理丰富的图像更友好,网格分割速度最快但区域形状固定。

from skimage.segmentation import slic def init_regions_slic(image, n_segments=200, compactness=10): """ 用 SLIC 超像素做初始分割 :param n_segments: 期望的超像素数量 :param compactness: 紧凑度,越大越接近方形 """ segments = slic(image, n_segments=n_segments, compactness=compactness) return segments

逻辑说明:n_segments控制超像素数量,一般设 200 到 500。compactness越大,超像素形状越规则,但可能跨越物体边界。替换之后,后面的相似度计算和合并逻辑不用改,直接复用。

5.2 自定义相似度权重

如果你只关心颜色相近的区域,可以把纹理相似度权重降到 0,颜色权重提到 2.0。如果你做的是文本检测,纹理和填充度更重要,可以反过来调。

# 自定义权重配置 WEIGHTS = { 'color': 2.0, 'texture': 0.5, 'size': 0.5, 'fill': 1.0 } def similarity_custom(r1, r2, image, segments): return (WEIGHTS['color'] * color_similarity(r1, r2, image, segments) + WEIGHTS['texture'] * texture_similarity(r1, r2, image, segments) + WEIGHTS['size'] * size_similarity(r1, r2, segments) + WEIGHTS['fill'] * fill_similarity(r1, r2, segments))

参数说明:权重没有标准答案,我一般先在验证集上跑几组对比,看哪组候选框的召回率高。颜色权重高适合物体颜色区分明显的场景,纹理权重高适合纹理差异大的场景。

5.3 接入下游检测模型

候选框生成之后,你可以直接把它们裁剪出来送进 CNN 分类器,或者把框的坐标喂给 R-CNN 系列模型。常见做法是把候选框按面积排序,取前 2000 个,然后统一缩放到固定尺寸。

def crop_candidates(image, boxes, target_size=(224, 224)): """ 裁剪候选框并缩放到固定尺寸 :param image: 原图 :param boxes: 候选框列表 :param target_size: 目标尺寸 :return: 裁剪后的图像块列表 """ crops = [] for (x, y, w, h) in boxes: patch = image[y:y+h, x:x+w] patch = cv2.resize(patch, target_size) crops.append(patch) return crops

逻辑说明:裁剪时注意边界检查,防止坐标越界。缩放用双线性插值即可,不要用最近邻,否则纹理信息损失大。裁剪后的图像块可以直接送进分类网络。

5.4 验证改造效果的方法

改完之后怎么知道有没有变好?我一般看两个指标:候选框的召回率和平均框数量。召回率是在标注数据上算的,看有多少真实目标被至少一个候选框覆盖到,IoU 阈值设 0.5。平均框数量反映效率,太多说明冗余严重。

def recall(candidates, ground_truth, iou_thresh=0.5): """ 计算候选框对真实目标的召回率 """ hit = 0 for gt in ground_truth: if any(iou(gt, c) >= iou_thresh for c in candidates): hit += 1 return hit / len(ground_truth)

参数说明:iou_thresh一般设 0.5,严格一点可以设 0.7。召回率上不去就调低相似度阈值或者增加初始区域数量;框太多就加大 NMS 力度。

5.5 一个我踩过的坑

有一次我把scale参数调得特别大,想着跑快点,结果小目标全被合并没了,召回率直接掉了一半。后来我养成一个习惯:每次改分割参数,都先在一张有大小目标的测试图上跑一遍,肉眼确认小目标还在,再跑全量数据。从那以后我每次调参都强制走一遍可视化检查,再也不敢只看数字了。

希望这个示例能帮你把 selective_search 的流程跑通,后面改起来也有个靠谱的起点。

本文还有配套的精品资源,点击获取

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

无人自助拼豆系统开发实战 从需求拆解到落地部署全流程实操指南

无人自助拼豆系统开发实战 从需求拆解到落地部署全流程实操指南 随着线下手工DIY业态的快速发展&#xff0c;拼豆作为低门槛、高趣味性的手工项目受到年轻人欢迎&#xff0c;传统拼豆门店依赖人工值守、高峰时段核销效率低、拼单凑单流程繁琐的痛点愈发突出。基于无人共享设备的…

作者头像 李华
网站建设 2026/10/15 16:18:54

Pixel3编译Android 11:AOSP构建环境配置与设备适配全指南

1. 为什么Pixel3是AOSP Android 11编译的“黄金标尺” 在AOSP&#xff08;Android Open Source Project&#xff09;生态里&#xff0c;编译失败从来不是“能不能”的问题&#xff0c;而是“在哪一步崩”的问题。我见过太多人卡在 repo sync 中途断连、卡在 lunch 选错目标…

作者头像 李华
网站建设 2026/10/15 16:18:51

uniapp批量选择:全选、多选、反选实现与避坑指南

做后台系统或者订单列表的时候&#xff0c;复选框的全选、多选、反选几乎是天天见的交互。我这两年用uniapp做过好几个管理端项目&#xff0c;这几项功能看起来只是勾勾选选&#xff0c;真正落地时却容易在数据同步、跨端兼容、半选状态上栽跟头&#xff0c;尤其是H5和小程序端…

作者头像 李华
网站建设 2026/10/15 16:17:44

TDC-GP22激光测距开发实战:寄存器配置、标定滤波与避坑指南

简介&#xff1a;这份资源面向嵌入式开发工程师与激光测距方向的学习者&#xff0c;围绕Infineon TDC-GP22时间数字转换器展开&#xff0c;提供从硬件接口到软件驱动的完整工程参考&#xff0c;帮助解决亚纳秒级时间间隔测量与微米级距离解算的实现问题。压缩包共195个文件&…

作者头像 李华
网站建设 2026/10/15 16:17:10

AI 当你的免费 PMO 从排期到周报

AI PMO 项目管理2026.09项目没 PM&#xff0c;全靠人肉盯AI 当你的免费 PMO从排期到周报任务拆解 关键路径 自动化周报《人人都能用的AI工具》实操飞书多维表格Notion&#x1f4e6; 5 Parts Conclusion&#x1f449; 滑动PART 01先认清三层活从整理到自动化PART 02任务拆解…

作者头像 李华
网站建设 2026/10/15 16:17:07

Design Compiler综合本质:约束驱动的多目标优化

1. 为什么“Design Compiler”不是“点几下就能出网表”的工具Design Compiler——这个名字在数字前端设计圈里&#xff0c;像一把老式机械表的主发条&#xff1a;拧得越准&#xff0c;走时越稳&#xff1b;拧偏一格&#xff0c;整块表就停摆。但现实中&#xff0c;太多人把它当…

作者头像 李华