我的毕业课题需要在零件图片上做注意机制(视觉显著性)实验,用的工具箱是 Caltech 的 SaliencyToolbox(Itti–Koch 模型,MATLAB 实现,2006–2013)。但我装的 MATLAB R2025b 和这个老工具箱不兼容,根本跑不起来。于是我用 C++(MSVC x64)把工具箱的默认处理路径逐行复刻了一遍,约 2000 行,脱离了 MATLAB 环境,还顺手把课题下一步要用的 top-down 模板匹配也做了。本文记录这件事的来龙去脉、模型原理、实现中的坑和最终效果。
1 为什么做这件事
课题的实验流程是:对一批零件照片(86 张)计算显著图,得到人眼可能的注视点序列;后续还要把”标准模板”的匹配结果作为先验,做 top-down 引导的注意实验。这些都依赖 SaliencyToolbox。
问题出在环境上:这个工具箱最后一次更新是 2013 年,而我的 MATLAB 是 R2025b,直接运行报错,改兼容性问题等于在一个十三年的代码库里考古。权衡之后我决定换条路:MATLAB 只用来读原始实现当”金标准”参考,真正的批量计算用 C++ 重写。好处很实际:
- 不依赖 MATLAB 运行时,任何一台装了 VS 的 Windows 机器都能跑;
- 命令行批处理,86 张图一条命令出全部结果;
- top-down 接口自己设计,后面接模板匹配不用受 MATLAB 代码结构约束。
2 原理:Itti–Koch 注意机制模型
模型的核心是模拟人类视觉系统的 bottom-up 注意机制,数据流如下:
1 | 输入图像 → 高斯金字塔 → 特征提取 → 中央-周边差 → 特征图归一化 → 显著图 |
分几步说:
- 高斯金字塔:把原图逐层降采样,得到多个尺度的表示。
- 特征提取:在每个尺度上分别计算三类特征——强度、颜色对立(红-绿、蓝-黄)、方向(Gabor 滤波,0/45/90/135 度)。
- 中央-周边差(center-surround):模拟视网膜感受野,用粗尺度(周边)与细尺度(中央)做差,突出”和周围不一样”的区域。
- 归一化与合成:每张特征图做迭代归一化,再按权重合成一张显著图(saliency map),图上越亮的位置越”显眼”。
- WTA(winner-take-all):一个 LIF(leaky integrate-and-fire)神经元网络在显著图上演化,最先达到阈值发放的位置就是下一个注视点。
- 返回抑制(IOR):选中一个注视点后,对该区域施加抑制,让下一轮注意跳到别的地方——这和人类扫视行为一致。
top-down 扩展是在第 4 步做文章:把外部先验(比如模板匹配的热力图)当作一路额外特征,按权重混入显著图合成,注意力就会被先验”拉”向目标区域。
3 做了哪些工作
3.1 逐行复刻默认处理路径
用 VS 2022 的 MSVC(x64,/O2 /std:c++17)复刻了工具箱默认路径,约 2000 行,第三方依赖只有 stb 两个单头文件(图像读写)。文件分工:
| 文件 | 职责 |
|---|---|
saliency.hpp |
公共类型与声明(Map/Image/SalParams/LIF/WTA) |
salmap.cpp |
金字塔、颜色/强度/方向特征、center-surround、迭代归一化、显著图合成、top-down 混入 |
wta.cpp |
LIF 神经元、WTA 演化、disk/shape 两种 IOR、坐标转换 |
segment.cpp |
fastSegmentMap、estimateShape、二值形态学 |
main.cpp |
命令行、图像 IO、overlay 绘制、selftest、批量处理 |
3.2 调试中的坑
一个真 bug:sepConv2PreserveEnergy 里的反向累加我一开始写错了。MATLAB 的 cumsum(v(end:-1:1)) 语义是”末尾 j+1 个元素的和”,不是后缀和。写错的表现是常数图金字塔的底边和右边直接爆炸,修正后恢复平坦。
一个假 bug:在完美均匀图上,显著图出现了条纹。查了一圈发现是降采样核 [1,5,10,10,5,1,1]/33 求和不严格等于 1,产生 1e-16 量级的浮点尾差,normalizeImage 把它放大到 [0,10],再被迭代归一化放大成可见条纹。对照 MATLAB 原版,一模一样的行为——这是工具箱固有的病态边界,真实图片不会触发,所以 selftest 里这一项只检查”输出有限”。
一堆编码坑:build.bat 里的中文注释会让 cmd 解析爆炸(改纯 ASCII);git-bash 中文路径下 cmd 找不到批处理(用绝对路径);MSVC 用 GBK 读 UTF-8 源文件报 C4819(加 /utf-8);windows.h 的 min/max 宏冲突(NOMINMAX)。Windows 上写 C++ 的日常。
3.3 top-down 模板匹配
工具箱本身没有模板匹配,这是我加的。做法是对每个”标准模板”在零件图上做零均值归一化互相关(NCC)滑窗匹配,多个模板的热力图逐像素取 max 融合成一张,然后走已有的 top-down 通道混入显著图。
NCC 的公式:
1 | r(x,y) = Σ(I−Ī)(T−T̄) / sqrt( Σ(I−Ī)² · Σ(T−T̄)² ) |
窗口的均值和方差用积分图 O(1) 算,互相关项直接滑窗。86 张零件图 × 8 个模板几分钟跑完,不需要 FFT。负相关(反匹配)不是注意目标,直接裁为 0;比图大的模板自动跳过。
3.4 验证
内置 --selftest 六项全过:白方块/红方块的显著图峰值定位、首注视点位置、两张均匀图的输出有限性、NCC 峰值精确定位(合成图贴图案,峰值落在窗口中心,相关度 1.0000)。真图上注视点落在连杆小头孔、杆身、大头孔等高对比区,返回抑制衰减后的重访行为与 MATLAB 一致。
4 软件使用
构建(需要 VS 2022):
1 | build.bat |
build.bat 内部调 vcvarsall.bat x64 设置环境,然后:
1 | cl /O2 /std:c++17 /EHsc /W3 /utf-8 /Fe:saliency.exe main.cpp salmap.cpp wta.cpp segment.cpp |
命令行用法:
1 | saliency.exe <图片或目录> [选项] |
每张输入图输出三个文件:*_salmap.png(显著图)、*_overlay.png(原图叠加注视点轨迹和形状轮廓)、*_fixations.csv(注视点坐标和时间);用 --template 时多一个 *_tmap.png(模板匹配热力图)。
实际跑过的命令:
1 | :: 批量 bottom-up,86 张零件图 |
5 处理结果
5.1 bottom-up:连杆
显著图上最亮的位置在连杆小头孔附近:

对应的前几个注视点依次落在小头孔、杆身、大头孔——都是图中对比度最高的结构:

5.2 top-down:模板匹配引导
用 8 个标准模板对同一张连杆图做 NCC 匹配,热力图的亮带准确落在杆身:

把它以权重 2 混入显著图后,注视点序列被明显调制,第一轮就锁定小头孔区域(纯 bottom-up 时第一注视点在大头孔边缘):

5.3 法兰
注视点分布在螺栓孔和内圈边缘:

5.4 活塞
销孔和顶部边缘是最显著区域:

5.5 轴承
滚子与保持架的高对比纹理吸引了大部分注视点:

6 小结
一次环境兼容性问题,最后变成了一次完整的重写:C++ 复刻的注意机制工具箱不依赖 MATLAB,一条命令批量出结果,还顺带把课题下一步的 top-down 模板匹配打通了。复刻老代码最大的收获是搞清楚了哪些”bug”是真 bug,哪些是原作者留下的固有行为——后者要原样保留,否则就谈不上”复刻”了。