16 模板匹配
“这个东西在图里哪儿?”——这是工业视觉被问得最多的问题:机械手要抓取的零件在托盘的哪个位置?贴装头要对位的焊盘偏了多少?测量程序的基准点这一帧落在哪里?对这个问题最直接的回答,就是拿着目标的一张”照片”,在图像里逐个位置比对,看哪里最像。这就是模板匹配(template matching):以一小块参考图像为模板(template),在搜索图中滑动比较,输出相似度最高的位置(以及角度)。它不需要建模、不需要训练样本、对任意纹理都适用,是工业定位当之无愧的主力。
本章用一组真实样图贯穿始终:贴片铝电解电容(标称 470 µF、35 V、RVT 系列)散落在黑色背景上,由 Smart3 灰度匹配示例方案提供(\(1280\times 960\) 单通道灰度)。每颗电容是一枚反光的金属圆顶,顶面丝印 “470 / 35V / RVT” 三行字,左侧有一道暗色极性条带——条带与文字共同打破了圆形的旋转对称,使角度有唯一解。模板取自样图 001 左上角的一颗电容(图 16.1 (a)):以其圆顶外接框裁出 \(230\times 230\)(圆顶直径约 214 px)。搜索图为样图 003(图 16.1 (b)),同一型号的五颗电容以各异的位置与姿态散布其上:有正立的、有小角度偏转的,也有整整翻转近 \(180^\circ\) 的——这正是产线上料后零件的真实样貌。后续 小节 16.5 还会在样图 004、005 上验证同一模型。
16.1 相似度度量
把模板 \(t(u,v)\) 的左上角对到搜索图 \(f\) 的 \((x,y)\) 处,“像不像”需要一个数来回答——这就是相似度度量(similarity measure)。最朴素的两种是逐像素差的累积:绝对差和(sum of absolute differences, SAD)与平方差和(sum of squared differences, SSD):
\[ D_{\mathrm{SAD}}(x,y)=\sum_{u,v}\bigl|f(x{+}u,\,y{+}v)-t(u,v)\bigr|, \qquad D_{\mathrm{SSD}}(x,y)=\sum_{u,v}\bigl(f(x{+}u,\,y{+}v)-t(u,v)\bigr)^2 . \]
两者都是越小越像,计算便宜、含义直白,但有一个产线上致命的缺陷:对亮度变化敏感。设某处窗口内容恰好是模板乘上照度增益 \(g\)(灯光变强变弱、工件表面反射率批次差异都会造成这种线性变化),则
\[ D_{\mathrm{SSD}}=\sum_{u,v}\bigl(g\,t(u,v)-t(u,v)\bigr)^2=(g-1)^2\sum_{u,v}t(u,v)^2 . \]
形状分毫不差,残差却随增益偏差平方增长——照明一变,“完美匹配”在 SSD 眼里就成了”差得很远”。
解决之道是先把窗口和模板各自”标准化”再比较:减去均值(消除亮度偏移),除以标准差(消除增益),剩下的才是纯粹的”形状”。这就是归一化互相关(normalized cross-correlation, NCC):
\[ \rho(x,y)=\frac{\displaystyle\sum_{u,v}\bigl(f(x{+}u,\,y{+}v)-\bar f_{x,y}\bigr)\bigl(t(u,v)-\bar t\,\bigr)} {\sqrt{\displaystyle\sum_{u,v}\bigl(f(x{+}u,\,y{+}v)-\bar f_{x,y}\bigr)^2}\;\sqrt{\displaystyle\sum_{u,v}\bigl(t(u,v)-\bar t\,\bigr)^2}}, \]
其中 \(\bar f_{x,y}\) 是当前窗口的均值,\(\bar t\) 是模板均值。它对线性照明变化的不变性可以一行看穿:把窗口内容换成 \(a f + b\)(\(a>0\)),减均值一步消去偏移 \(b\),增益 \(a\) 作为公因子在分子与分母中约去——\(\rho\) 纹丝不动。
几何视角:把去均值后的窗口与模板各看作一个高维向量,NCC 就是两向量夹角的余弦,因此恒有 \(|\rho|\le 1\);\(\rho=1\) 当且仅当窗口是模板的正线性变换,\(\rho=-1\) 对应黑白反转(极性相反)——这正是 SDK polarity 参数所区分的情形。
实验完全印证了推导。用样图 001 裁出的固定 \(0^\circ\) 模板对样图 003 逐像素扫描,最高响应落在左上角那颗近乎正立的实例上,NCC 为 0.827。这个数没到接近 1,正是因为该实例本身相对模板还残留约 \(12^\circ\) 的偏转(见 小节 16.5 的角度搜索结果)——固定角度的模板连”最好”的实例都对不齐,这一伏笔留到 小节 16.3。要单独检验 NCC 的照明不变性,我们在真实图像上做一次受控且公开披露的扰动:把这颗实例邻域内的灰度整体乘以 0.5,模拟该工位照度骤降一半。结果一目了然——NCC 从 0.827 变到 0.827(变化 0.0001),纹丝不动;同一位置的 SSD 均方根残差(rms)却从 41.2 涨到 62.3,约 1.5 倍。若按亮实例标定一个留 40% 裕量的检测阈值,判定结果如下:
| 度量与阈值 | 亮实例 | 照度 50% 实例 |
|---|---|---|
| SSD(rms \(\le 58\)) | 检出(41.2) | 漏检(62.3) |
| NCC(\(\rho \ge 0.80\)) | 检出(0.827) | 检出(0.827) |
一次再普通不过的灯光衰减,就能让 SSD 把好零件判成”不存在”。NCC 还有一个工程上的好处:分数天然落在 \([-1,1]\)(SDK 折算为百分制),“合格分数”这样的阈值在不同工位、不同相机之间可以直接迁移;而 SSD 阈值依赖灰度尺度与噪声水平,换一台相机就得重新标定。这就是工业匹配几乎一律使用 NCC(或下一章的形状匹配)的原因。
16.2 分数图与峰
把每个位置的相似度排成一幅图,就得到分数图(score map)。图 16.2 是同一场景的 NCC 分数图与 SSD 分数图(后者以”亮 = 残差小”显示)。为把全分辨率全图扫描的代价压到可演示的范围,这两幅图都在 \(1/2\) 分辨率(\(640\times 480\))上用 \(115\times 115\) 模板计算(理由见 小节 16.4)。
NCC 分数图上五颗电容各自激起一团圆形响应,但尖峰高低分明:固定 \(0^\circ\) 模板下,五个实例的峰值从 0.827(正立)依次降到 0.750、0.656、0.622、0.516——越偏离模板姿态,相关性越低,近 \(180^\circ\) 翻转的实例只剩一团 0.5 出头的弱响应。这恰恰是下一节的引子:NCC 对旋转无能为力。SSD 图讲同一个故事的”残差版”:正立实例是最亮的一团,其余实例的亮度迅速塌下去。
分数图还揭示了一个更深的事实:峰的锐度就是定位能力。峰越尖,最优位置周围的分数衰减越快,噪声把峰顶推偏的余地就越小;反之,平缓的脊状响应意味着一串位置”差不多像”,定位必然摇摆。模板里的高频结构(圆顶边缘、极性条带、丝印文字)越丰富,峰越锐。最后,整像素的峰顶只是起点:在峰的邻域内对分数分别沿 \(x\)、\(y\) 做三点抛物线顶点插值(章节 2 推导过的 \(\delta=(g_{-1}-g_{+1})/(2(g_{-1}-2g_0+g_{+1}))\)),即可把位置精化到亚像素——商用匹配器报出的 \((404.07, 204.06)\) 这类小数坐标正是由此而来。
16.3 旋转与姿态搜索
NCC 解决了照明,却对旋转无能为力:固定 \(0^\circ\) 模板在样图 003 上连对得最齐的实例也只有 0.827(它本身还偏转着约 \(12^\circ\)),翻转近 \(180^\circ\) 的那颗更是只剩 0.5 出头——按 0.80 的阈值,同一颗电容仅仅转了向就要被判为不存在。NCC 是逐像素对齐的比较,模板一转,边缘错位,相关性迅速崩塌。因此实用的匹配器必须把角度(必要时还有尺度)作为显式的搜索维度:对每个候选角度准备一份旋转后的模板,逐一比对,取分数最高的位姿。
SciVision 的做法是把这笔开销搬到训练期:CreateGreyImageModel 按 startAngle(起始角)、angleExtent(角度范围)、angleStep(步长)把模板预旋转成一系列采样姿态存入模型。由于电容上料后可能呈任意朝向(样图里就有近 \(180^\circ\) 翻转的实例),本例取全圆搜索:\(-180^\circ\) 起、跨度 \(360^\circ\)、步长 \(1^\circ\),共 360 个姿态;搜索期逐姿态评分,再在最优姿态附近插值精化角度。代价与角度数成正比——范围越宽、步长越细,训练越慢、模型越大、匹配越耗时。立竿见影的回报是:那颗固定模板下只有 0.827 的正立实例,开启角度搜索后分数追到 97.58(百分制)——角度一对齐,相关性立刻回满。
步长怎么选?太粗,真实角度落在两档采样之间时分数下降、定位漂移;太细则纯属浪费。一个实用的估算:转动 \(\Delta\theta\) 使半径 \(r\) 处的点位移约 \(r\,\Delta\theta\)(弧度),让模板最外缘的位移不超过 1 px,即 \(\Delta\theta \approx 57.3^\circ/r\)——本例圆顶半径约 107 px,\(1^\circ\) 步长对应外缘位移约 1.9 px,已略粗于”1 px 准则”,靠搜索期的亚像素角度插值补足。
16.4 金字塔加速
全分辨率逐像素扫描的代价是惊人的:本例 \((1280-230+1)\times(960-230+1)\) 个窗口、每窗口约 \(5.3\times 10^4\) 次乘加,合计约 \(4\times 10^{13}\) 次运算——单一角度就已不可承受,这正是金字塔与商用匹配器存在的理由(也是上一节分数图退到 \(1/2\) 分辨率计算的原因)。出路是图像金字塔(image pyramid)与粗到精(coarse-to-fine)策略:把场景与模板反复以 \(2\times 2\) 均值降采样(先盒式预滤波再抽取,正是 章节 10 强调的防混叠降采样),在最小的顶层做全图扫描——便宜得近乎免费;取若干个粗候选,逐层映射回高分辨率,只在候选周围的小窗口内精化。
代价账:每下一层,场景像素数与模板面积各缩为 \(1/4\),单层扫描代价缩为 \(1/16\);降两层后顶层全扫描只剩原工作量的 \(1/256\),候选精化窗口(本例 \(\pm 8\) px)的代价更是九牛一毛。层数每加一层,收益都是数量级的。
实验中我们以 \(1/2\) 分辨率(\(640\times 480\),模板 \(115\times 115\))为基准做对照:基准上一次全图 NCC 扫描约 \(2.5\times 10^9\) 次运算,实测约 10.6 s(未优化的 /Od 构建,绝对值偏大且随机器负载波动,但比例有代表性)。再在此基准上手写一个两级金字塔:顶层把场景与模板各再降两层(顶层 \(160\times 120\)、模板 \(28\times 28\))做全扫描,保留 3 个候选,各自回到基准分辨率在 \(\pm 8\) px 窗口内精化。结果:约 0.11 s 对全扫描的 10.6 s,约 100 倍加速,而找到的峰位置与基准全分辨率扫描逐像素一致——精度分文未失。
层数能无限加吗?不能。约束是顶层的模板必须仍然可辨认:\(230\times 230\) 的电容圆顶缩到几十像素时圆形轮廓与极性条带尚在;再缩下去,条带与丝印将彻底糊掉,顶层给出的候选便不可信。SDK 的 GetGreyAutoPyramidLevel 替你做这笔权衡——对本模板它自动给出 5 层(含原图层),顶层模板约 \(14\times 14\),圆顶与条带仍可辨。
16.5 SciVision 实现
SciVision 的灰度匹配由 SCIMV::SciSvGreyMatch 承担,训练与搜索两步:
SCIMV::SciSvGreyMatch gm;
int level = -1;
gm.GetGreyAutoPyramidLevel(tmplImg, mask, &level); // 本例自动建议 5 层
SciMatchModel model;
gm.CreateGreyImageModel(tmplImg, mask, /*pyramidLevel 自动*/ -1,
/*startAngle*/ -180, /*angleExtent*/ 360, /*angleStep*/ 1, &model);
SciPointArray centers; SciVarArray angles, scores;
gm.FindGreyImageModel(sceneImg, searchROI, model,
/*minScore*/ 60, /*matchCount*/ 5, /*overLapRatio*/ 50,
/*startAngle*/ -180, /*angleExtent*/ 360,
/*interpMethod 双线性*/ 1, /*polarity 区分极性*/ 0,
/*endLevel 精化到原图层*/ 0, /*clutter*/ 0.0f,
¢ers, &angles, &scores);训练侧:pyramidLevel 传 \(-1\) 让 SDK 自动选层(即 GetGreyAutoPyramidLevel 的结果);startAngle/angleExtent/angleStep 就是 小节 16.3 讨论的角度采样三参数,本例覆盖整圈。搜索侧:minScore 是百分制的接受阈值(60 分以下的候选直接丢弃);matchCount 限定最多返回的实例数(本例 5,对应五颗电容);overLapRatio 控制两个结果允许的重叠比例,用于多实例去重;角度区间与训练一致;interpMethod=1 用双线性插值旋转模板;polarity=0 要求明暗极性一致(黑白反转的”零件”不算匹配);endLevel=0 表示一路精化到原图层——若定位精度要求不高,停在更高层可再省时间;clutter 用于惩罚模板区域外的杂乱背景,0 为关闭。
两个实测踩过的坑值得如实记录。其一,mask 的 ROI 语义。匹配家族对 mask 参数的要求并不统一:特征匹配、颜色匹配与轮廓匹配(SciSvFeatureMatch/SciSvColorMatch/SciSvScaleShapeMatch)的 Create*Model 必须传默认构造的 UNDEF ROI,传一个覆盖全图的矩形反而会报”特征点不足”等错误;灰度匹配实测接受覆盖整张模板的 GenRect1 矩形,但务必记住 GenRect1 的右下角是排他端点——全模板应传 \((T,T)\) 而非 \((T{-}1,T{-}1)\)。换用同族的另一个匹配器时,请先确认它期望哪种 mask。其二,角度约定。SDK 返回的角度是数学正方向(逆时针为正):样图 003 正立实例被报为 \(+12.00^\circ\),把它直接喂给 y 轴向下的图像端旋转绘制会转错方向,需取负号换算。本章 matches.png(图 16.3)中方向正确的姿态框正是取反之后画出的。
FindGreyImageModel 在样图 003 上的搜索结果:五颗电容全部找到,十字标记亚像素中心,白框标示各自的匹配姿态——包括近 \(180^\circ\) 翻转与大角度偏转的实例;黑色背景上无一误检。
SDK 在样图 003 上的实测结果如下(角度为 SDK 逆时针正方向):
| 实例 | 位置 \((x, y)\) | SDK 角度 | 分数 |
|---|---|---|---|
| 0(正立) | \((404.07, 204.06)\) | \(+12.00^\circ\) | 97.58 |
| 1 | \((621.79, 714.39)\) | \(-35.26^\circ\) | 92.87 |
| 2(翻转) | \((878.39, 241.39)\) | \(+163.00^\circ\) | 91.15 |
| 3 | \((914.14, 794.30)\) | \(+49.00^\circ\) | 90.70 |
| 4 | \((321.08, 649.11)\) | \(-107.66^\circ\) | 89.59 |
五颗电容分数全部在 89 分以上,角度跨越整圈(含一颗 \(+163^\circ\) 的翻转实例),位置精化到亚像素。两个交叉印证值得一提:其一,手写固定 \(0^\circ\) 模板在 \(1/2\) 分辨率上扫到的最高峰(升采样回 \((404,204)\))与 SDK 实例 0 的 \((404.07,204.06)\) 逐像素吻合——两套独立实现指向同一位置;其二,同一颗正立实例,固定角度的 NCC 只有 0.827,而开启角度搜索后 SDK 给出 97.58——角度搜索把分数追了回来,正是 小节 16.3 的论点。同一模型在样图 004、005 上同样把各自的五颗电容全部找到(最低分分别为 88.9 与 91.2),印证了模型对位置与姿态变化的鲁棒性。完整可运行工程在 code/template_matching/。
工业案例:上料偏转超出角度搜索范围
某装配线的灰度匹配模型训练时按当时来料情况把 angleExtent 设为 \(\pm 20^\circ\),稳定运行数月。后来供料方式更换,工件实际偏转达 \(\pm 35^\circ\)——超出搜索范围的实例没有任何对得上的模板姿态,匹配分数骤降到阈值之下,整批漏检。第一反应是把范围直接放大到整圈”求保险”,结果训练时间与单次匹配耗时明显上升,更糟的是姿态数翻倍后相似形状撞上某个角度的机会也变多,误检率反而抬头。最终方案是实测上游机构的偏转分布,按其包络加裕量设定范围,同时在料道上加挡边限位把物理偏转压回范围内。教训:角度搜索范围的职责是覆盖物理现实——本章样图里电容确会翻转,故取整圈;而对姿态受限的工位,既不能小于来料的真实偏转,也绝不是”越大越保险”。
16.6 小结
- 模板匹配 = 相似度度量 + 位姿搜索:度量决定对何种干扰鲁棒,搜索维度决定能容忍何种姿态变化,两者缺一不可。
- SSD/SAD 对亮度变化敏感(增益 \(g\) 带来 \((g-1)^2\) 的残差项);NCC 减均值、除标准差,对线性照明严格不变——实测把一颗电容邻域照度降到 50% 时 NCC 仅从 0.827 变到 0.827,而 SSD 残差涨 1.5 倍导致漏检。
- 分数图的峰锐度就是定位能力;整像素峰顶经抛物线顶点插值精化到亚像素,本例 SDK 五颗电容定位至亚像素、分数 89 分以上。
- NCC 不具旋转不变性(固定 \(0^\circ\) 模板下正立实例仅 0.827、翻转实例跌到 0.5 出头),角度必须显式搜索;本例取整圈覆盖电容的任意朝向,开启搜索后分数从 0.827 追到 97.58。
- 金字塔粗到精是匹配提速的标准手段:顶层粗扫 + 候选精化,实测约 100 倍加速且峰位置与全扫描完全一致;层数的上限是顶层模板仍可辨认(本例自动 5 层)。
NCC 的鲁棒性边界止于线性照明:镜面反光、局部阴影等非线性变化,以及零件被部分遮挡、出现尺度变化时,灰度匹配的分数都会垮掉——这些场景要靠基于边缘的形状匹配解决,见 章节 17。本章的归一化互相关如何借助积分图高效计算,见 Lewis 的经典短文 (Lewis 1995);把模板匹配置于图像配准的更大图景中,可参阅 Brown 的综述 (Brown 1992)。关于灰度匹配与其加速策略更系统的论述,可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)。




