图像处理讲义——模板匹配
什么是模板匹配(Template Matching)
模板匹配是指在当前图像 A 内寻找与图像 B 最相似的部分,一般将图像 A 称为输入图像, 将图像 B 称为模板图像。模板匹配的操作方法是将模板图像 B 在图像 A 上滑动,遍历所有像素以完成匹配。
举个例子:我们现在有模板图像,以及输入图像(都是灰度图像且模板图像更小),设模板图像高为\(h_t\),宽为\(w_t\),输入图像高为\(h\),宽为\(w\),则模板匹配的过程为:
- 将模板图像放在输入图像内,设模板图像左上角顶点在输入图像的位置为\(P = (x,y)\),将模板图像放到\((0,0)\)的位置,进行第一次匹配,计算匹配度(匹配度计算后面再说)
- 然后将模板图像放到\((1,0)\)的位置,即向右移动一个像素,再次计算匹配度;然后再移动一个像素······一直移动到第一行末尾,每次都计算匹配度,可以发现一共计算了\(w-w_t+1\)次匹配度
- 第一行匹配结束,将模板图像放到\((0,1)\)的位置,开始第二行的匹配······和第一行一样,一共计算了\(w-w_t+1\)次匹配度
- 以此类推,直到最后一行匹配结束,可以发现整个模板匹配的过程中,我们一共计算了\((w-w_t+1)*(h-h_t+1)\)次匹配度
- 最后将匹配度以矩阵的形式返回(匹配度的位置与匹配时模板图像所在的位置一样),且该矩阵行数为\(h-h_t+1\),列数为\(w-w_t+1\)(在python中就是二维数组)
模板匹配的代码实现
单模板匹配
所谓单模板匹配,就是一个模板只能匹配输入图像中的一个位置,即一个一对一的映射。
我们先来看看opencv中与模板匹配相关的api函数吧
result = cv2.matchTemplate(image, templ, method[, mask ] )
- image 为原始图像,必须是 8 位或者 32 位的浮点型图像。
- templ 为模板图像。它的尺寸必须小于或等于原始图像,并且与原始图像具有同样的类型。
- method 为匹配方法。该参数通过 TemplateMatchModes 实现,有 6 种可能的值,如下图所示。

具体计算公式如下图所示

-
mask 为模板图像掩模。它必须和模板图像 templ 具有相同的类型和大小。通常情况下 该值使用默认值即可。当前,该参数仅支持 TM_SQDIFF 和 TM_CCORR_NORMED 两 个值。
-
result 为返回值,是一个行数为\(h-h_t+1\),列数为\(w-w_t+1\)的二维数组,每个元素代表由method对应公式计算出来的匹配度
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc( src [, mask] )
- src为单通道数组
- minVal 为返回的最小值,如果没有最小值,则可以是 NULL(空值)。
- maxVal 为返回的最大值,如果没有最小值,则可以是 NULL。
- minLoc 为最大值的位置,如果没有最大值,则可以是 NULL。
- maxLoc 为最大值的位置,如果没有最大值,则可以是 NULL。
- mask 为用来选取掩模的子集,可选项。
用途:该函数可以对 cv2.matchTemplate 函数的返回值 result 进行处理,找到 result 所代表的二维数组中最大值/最小值,及其所在的行列坐标位置(用opencv的坐标系表示,即 Loc[0] 为列数, Loc[1] 为行数)
模板匹配完成后,我们可以用opencv中的 cv2.rectangle 函数标记出我们所匹配到的位置
Img = cv.rectangle( img, pt1, pt2, color[, thickness])
- img 表示要标记的目标图像。
- pt1 是矩形的顶点。
- pt2是pt1的对角顶点
- color是颜色(灰度图像中就是明暗强度)
- thickness是矩形边线宽度(可选)
单模板匹配的代码实现
import cv2
import numpy as np
# 单模板匹配例程
if __name__ == '__main__':
img_bgr = cv2.imread('./example.png') # 读取rgb图像
img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # 转化为灰度图像
tp = cv2.imread('./template4.png', flags=0) # 读取模板
th, tw = tp.shape[::] # 获取模板宽高
method = cv2.TM_CCOEFF_NORMED # 定义模板匹配方法
retVal = cv2.matchTemplate(img, tp, method) # 进行模板匹配
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(retVal) # 找到最匹配的位置
# 用红色标记处最匹配的位置
topLeft = maxLoc
bottomRight = (topLeft[0] + tw, topLeft[1] + th)
cv2.rectangle(img_bgr, topLeft, bottomRight, (0, 0, 255), 3)
cv2.imshow('img_bgr', img_bgr)
print("博士,已经为您找到医疗兵了喵~")
cv2.waitKey(0)
cv2.destroyAllWindows()
多模板匹配
在日常的代码编写中,我们大都会遇到这种情况:你有模板1、2、3···n,每个模板在输入图像里都有多处匹配的位置。这时,你就不能使用 cv2.minMaxLoc 函数来获取最佳匹配位置,因为你所需要的是一个一对多的映射,而非一对一的映射,所以你需要另一种方法对 cv2.matchTemplate 函数的返回值 result 数组进行处理,来获取所有能匹配的位置,这也就是多模板匹配与单模板匹配的区别。
那么,我们如何处理 result 数组呢?
熟悉 numpy 的同学知道,numpy中有一个 np.where 函数,能够返回numpy数组中满足输入条件的所有元素的行列位置信息,该函数原型为:
loc = np.where( res >= threshold)
例如:
import numpy as np
am=np.array([[3,6,8,77,66],[1,2,88,3,98],[11,2,67,5,2]])
b=np.where(am>5)
print(b)
则改代码返回结果为:
(array([0, 0, 0, 0, 1, 1, 2, 2], dtype=int64), # 这是行数的索引
array([1, 2, 3, 4, 2, 4, 0, 2], dtype=int64)) # 这是列数的索引
上述结果说明,存在二维数组 am,其中,位置[0, 1]、[0, 2]、[0, 3]、[0, 4]、[1, 2]、[1, 4]、[2, 0]、[2, 2]上的元素值大于 5。
但是,我们一般所需要的位置信息是例如 [column, row] 这样行列索引的集合,而非上面那样第一行是行数索引,第二行是列数索引的矩阵,所以我们还需要对上面的返回结果进行处理,也就是线性代数中对矩阵进行转置。
熟悉python的同学知道,python里有一个 zip 函数,其用可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由 这些元组组成的列表。
举个例子,对于前面提到的数组 am,使用函数 zip()循环,就可以得到其中大于 5 的元素索引的集合
import numpy as np
am=np.array([[3,6,8,77,66],[1,2,88,3,98],[11,2,67,5,2]])
print(am)
b=np.where(am>5)
for i in zip(*b):
print(i)
输出结果为:
[[ 3 6 8 77 66]
[ 1 2 88 3 98]
[11 2 67 5 2]]
(0, 1)
(0, 2)
(0, 3)
(0, 4)
(1, 2)
(1, 4)
(2, 0)
(2, 2)
可以看到,我们能够获得形如 (column, row) 的位置信息了。但是在opencv坐标系中,我们的坐标是形如 (x, y) 的,其中x为列数,y为行数,所以我们需要将 (column, row) 变成 (row, column) ,即对数组元素进行倒置。这里我们使用python内置的 loc 函数。
举个例子:
import numpy as np
loc = ([1,2,3,4],[11,12,13,14])
print(loc)
print(loc[::-1])
输出为:
([1, 2, 3, 4], [11, 12, 13, 14]) # print(loc)的结果
([11, 12, 13, 14], [1, 2, 3, 4]) # print(loc[::-1])的结果
这样,我们就可以进行我们的多模板匹配了。
多模板匹配的代码实现
import cv2
import numpy as np
# 多模板匹配例程
if __name__ == '__main__':
img_bgr = cv2.imread('./example.png')
img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # 转成灰度图像
tp = {
'guard': cv2.imread('./template0.png', flags=0),
'shield': cv2.imread('./template1.png', flags=0),
'mage': cv2.imread('./template2.png', flags=0),
'archer': cv2.imread('./template3.png', flags=0),
'medic': cv2.imread('./template4.png', flags=0)
} # 读取多个模板
th, tw = tp['guard'].shape[::] # 获取模板的高、宽(这里为了省事模板宽高都一样,所以只获取一个)
method = cv2.TM_CCOEFF_NORMED # 定义模板匹配的method
retVal = {
'guard': cv2.matchTemplate(img, tp['guard'], method),
'shield': cv2.matchTemplate(img, tp['shield'], method),
'mage': cv2.matchTemplate(img, tp['mage'], method),
'archer': cv2.matchTemplate(img, tp['archer'], method),
'medic': cv2.matchTemplate(img, tp['medic'], method)
} # 进行多个模板匹配
loc = {
'guard': np.where(retVal['guard'] > 0.97),
'shield': np.where(retVal['shield'] > 0.98),
'mage': np.where(retVal['mage'] > 0.965),
'archer': np.where(retVal['archer'] > 0.97),
'medic': np.where(retVal['medic'] > 0.97)
} # 获取匹配位置
pt = {
'guard': [],
'shield': [],
'mage': [],
'archer': [],
'medic': []
}
# 获取匹配后的坐标
for key in loc:
for p in zip(*loc[key][::-1]):
pt[key].append(p)
color = {
'guard': (255, 255, 0), # 青色
'shield': (0, 255, 255), # 黄色
'mage': (255, 0, 255), # 紫色
'archer': (0, 0, 255), # 红色
'medic': (0, 255, 0) # 绿色
}
for key in color:
for p in pt[key]:
cv2.rectangle(img_bgr, p, (p[0] + tw, p[1] + th), color[key], 3)
cv2.imshow('img_bgr', img_bgr)
print("博士,所有干员已经为您分类好了喵~")
cv2.waitKey(0)
cv2.destroyAllWindows()
如果对python语言不是很熟悉的同学可能一开始看不太懂,可以多让chatGPT解释一下各段代码并自行理解消化😊

浙公网安备 33010602011771号