5.30. 模板匹配

到目前为止介绍的检测器回答的都是关于单帧内容的问题:色块在哪里、线条通向何处、印刷的代码表示什么。还有一类问题是将一幅图像与另一幅进行比较。所捕获帧中的这块区域,看起来是否像我在标定时存储的参考图块? 匹配方法回答的正是这个问题。

色调与统计分析一节为相关问题引入了 get_similarity() —— 这两幅同尺寸图像整体上有多相似? —— 其底层度量为 SSIM。剩下的匹配问题则是定位问题:不是“这两幅图像有多相似”,而是“那块较小的图块出现在这幅较大图像的什么位置?”解决定位问题的合适工具是模板匹配

5.30.1. 基本调用

find_template() 在所捕获的帧中查找一幅小模板图像首次出现的位置。其实现使用归一化互相关(NCC):模板在帧上滑动,每个位置的匹配分数由模板像素与其下方帧像素之间的相关性计算得出(并针对局部均值和方差进行归一化,使增益变化不会干扰匹配),第一个分数超过 threshold 的位置会作为边界框返回:

template = image.Image("/sdcard/template.bmp", copy_to_fb=False)
template.to_grayscale()

match = img.find_template(template, threshold=0.7,
                           search=image.SEARCH_DS)

if match is not None:
    img.draw_rectangle(match, color=(255, 0, 0))

该方法只对灰度图像有效。请以灰度方式捕获(对任何没有彩色传感器的摄像头来说都是自然的选择),或在调用前通过 to_grayscale() 就地转换。从磁盘加载的模板也同样如此:彩色模板会用相同的方法转换,转换结果正是匹配器所期望的。

threshold 是一个从 0.01.0 的浮点数。值为 1.0 要求逐像素完美匹配(对真实捕获的图像而言永远不会发生),0.0 接受任何结果,而介于 0.60.8 之间的值适用于常见情形:模板是在相似光照下捕获的,且场景没有发生剧烈变化。提高阈值可抑制误报;降低阈值可接受更多噪声较大的匹配,但代价是出现更多虚假命中。

5.30.2. 搜索策略

search 在两种策略之间选择。image.SEARCH_EX穷举搜索:模板在帧中每隔 step 个像素的位置上滑动,返回第一个超过阈值的命中。image.SEARCH_DS菱形搜索:匹配器先粗略采样,然后在最佳分数附近细化,速度大幅提升,但如果粗略遍历恰好落在某个超过全局最大值的局部最大值附近,则可能错过真正的匹配。对于模板定义明确、不易混淆的实时流水线,SEARCH_DS 是合适的默认选项;对于一次性标定(漏检的代价高于扫描较慢的代价)来说,SEARCH_EX 更为稳妥。

step 控制穷举遍历过程中跳过的像素数(菱形搜索自行管理其步长)。step 值越大,扫描越快,但代价是亚像素精度降低。roi 将搜索限制在帧的某个区域内,既缩小了匹配器考虑的范围,又减少了工作量。

返回值是一个标识最佳匹配的 (x, y, w, h) 边界框元组,如果没有任何位置超过阈值,则返回 None。该边界框可直接传入 draw_rectangle()crop() 用于下一阶段处理。

5.30.3. 尺度与旋转陷阱

模板匹配的经典陷阱是对尺度和旋转的敏感性。匹配器逐像素地将模板与帧进行比较;在某一距离捕获的模板无法匹配在不同距离捕获的同一物体,正面捕获的模板也无法匹配从侧面观察的同一物体。即便物体在人眼看来清晰可见,阈值也会悄然降到匹配水平以下,方法随即返回 None

对于简单情形,存在一些变通办法。应用程序可以在不同尺度下捕获多个模板,并依次对每个模板运行 find_template(),接受第一个超过阈值的结果;其代价随模板数量增长。应用程序也可以在匹配运行前,用 rotation_corr() 或极坐标变换(几何变换一节)对帧进行预处理,以消除造成干扰的旋转;不过此时被匹配的模板仍须与校正后的几何形态相符。

对于质检(QA)检测流水线,一个实用的范式是将模板匹配器与色调和统计分析一节介绍的相似度评分器配对使用:find_template() 在所捕获的帧中定位部件,将返回的边界框裁剪出来,再与参考图块一起传入 get_similarity()。模板匹配这一步决定部件在哪里;相似度评分这一步决定部件是否合格。这两步每帧都运行,对 mean 设定的阈值是合格/不合格的判定关卡,而绘制回帧中的匹配边界框则是操作员所观察的 IDE 预览。