模板匹配是 OpenCV 中一种基础且直观的区域定位方法。它不需要提取特征点或训练分类器,只需提供一张模板图像,就可以在源图像里找到与之最相似的区域。下面从原理、核心函数、代码示例到常见问题,逐步展开。
将模板图像 TT 在源图像 II 上逐像素滑动,每滑动到一个位置,就计算一次相似度(匹配值)。最终得到一个匹配结果图 RR,每个像素值代表以该点为左上角、大小与模板相同的子图与模板的相似程度。通过寻找 RR 中的极值点(最大值或最小值),就能定位到最佳匹配区域。
cv2.matchTemplate()result = cv2.matchTemplate(image, templ, method, mask=None)
image:源图像(8位灰度或彩色)。
templ:模板图像,大小不能超过源图像。
method:匹配方法,决定了相似度如何计算。常用以下6种:
| 方法标识符 (OpenCV) | 公式逻辑 | 最佳值是 |
|---|---|---|
cv2.TM_SQDIFF |
平方差 | 最小值 |
cv2.TM_SQDIFF_NORMED |
归一化平方差 | 最小值(越接近0越好) |
cv2.TM_CCORR |
相关性 | 最大值 |
cv2.TM_CCORR_NORMED |
归一化相关性 | 最大值(越接近1越好) |
cv2.TM_CCOEFF |
相关系数 | 最大值 |
cv2.TM_CCOEFF_NORMED |
归一化相关系数 | 最大值(越接近1越好) |
归一化方法(后缀 _NORMED)对光照变化更鲁棒,推荐优先使用。
对于 _SQDIFF 系列,完全匹配时理论值为 0,因此要找最小值;其余方法找最大值。
cv2.minMaxLoc()minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)
如果使用的是 TM_SQDIFF 或 TM_SQDIFF_NORMED,最佳位置是 minLoc;
否则最佳位置是 maxLoc。
这样得到的 maxLoc / minLoc 就是模板左上角在源图像中的坐标。
import cv2
img = cv2.imread('./images/face.png', 0) #0表示灰度图
template = cv2.imread('./images/facet.png', 0)
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
print(img.shape)
print(template.shape)
print(res.shape)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(min_val,max_val)
print(min_loc, max_loc)
#画出匹配到的位置
cv2.rectangle(img, min_loc, (min_loc[0] + template.shape[1], min_loc[1] + template.shape[0]), (255, 0, 0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
展示效果图如下:
如果图中有多个相同物体,仅一次 minMaxLoc 拿不到全部位置。可以设定一个阈值,把所有置信度超过该阈值的区域都标记出来:
threshold = 0.8
locations = np.where(result >= threshold) # 得到所有满足阈值的点坐标
# locations 是 (y数组, x数组) 的元组
for pt in zip(*locations[::-1]): # pt = (x, y)
cv2.rectangle(img, pt, (pt[0]+w, pt[1]+h), 255, 2)
注意:这样会在同一物体周围产生多个重叠矩形,因为阈值范围内的邻近像素都会满足条件。通常需要非极大值抑制或按一定步长筛选。
模板匹配本身不具备旋转和尺度不变性。如果目标的尺寸和模板不同,匹配效果会很差。一个经典的补救措施是多尺度匹配:不断缩放模板(或图像),逐一匹配,取最佳结果:
scales = np.linspace(0.8, 1.2, 20) # 尺寸变化范围
best_match = None
for scale in scales:
resized = cv2.resize(template, None, fx=scale, fy=scale)
if resized.shape[0] > img.shape[0] or resized.shape[1] > img.shape[1]:
continue
result = cv2.matchTemplate(img, resized, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
if best_match is None or max_val > best_match[0]:
best_match = (max_val, max_loc, scale, resized.shape)
# 画出最佳尺度的结果
_, loc, best_scale, (h, w) = best_match
cv2.rectangle(img, loc, (loc[0]+w, loc[1]+h), 255, 2)
若要同时处理旋转,则可在不同旋转角度下重复上述过程,计算量会显著增加。当需要同时处理尺度、旋转变化时,更推荐使用特征匹配(如 SIFT + FLANN)。
| 问题 | 解答 |
|---|---|
| 模板可以比源图大吗? | 不可以,会导致报错。 |
| 光照变化怎么办? | 使用归一化方法(_NORMED)可一定抵抗亮度变化;若光照极不均匀,可先做直方图均衡化。 |
| 匹配很慢怎么办? | 模板与图像越大越慢。可先缩小图像进行粗匹配,再在局部放大精细定位。 |
| 彩色图像匹配 | matchTemplate 也支持三通道图,计算是按通道分开算再相加。 |
| 结果没有峰值 | 可能模板与场景内容差异太大,或方法选择不当,可换用 CCOEFF_NORMED 并可视化 result 热力图排查。 |
| 如何应对轻微旋转? | 纯模板匹配不行,需配合金字塔搜索或直接使用特征匹配。 |
模板匹配因其无需训练、实现简单,常用于:
工业视觉中固定位姿的缺陷检测、零件定位;
屏幕截图的 UI 元素定位(自动化测试);
简单计数或存在性检查(已知目标形态和方向一致);
配合几何约束,作为初始位置提供给更精确的算法。
cv2.matchTemplate() 是 OpenCV 中的平移滑窗相似度计算器;
选择 CCOEFF_NORMED 等归一化方法可提高鲁棒性;
通过 minMaxLoc 提取最佳位置(注意最小/最大值的对应关系);
多目标和多尺度匹配可通过阈值筛选和缩放循环轻松实现;
模板匹配不处理旋转和剧烈尺度变化,遇到这些需求建议升级到特征匹配。

全部评论