模板匹配是指在当前图像A
内匹配与图像B
最相似的部分,一般将图像A
称为输入图像
,将图像B
称为模板图像
。 模板匹配的方法是将模板图像B在图像A上滑动,逐个遍历所有像素以完成匹配。
例如,下图中,大图像“lena”是输入图像,“眼睛”图像是模板图像。查找的方式是,将模板图像在输入图像内从左上角开始滑动,逐个像素遍历整幅输入图像,以查找与其最匹配的部分。
在OpenCV内,通过函数cv2.matchTemplate()实现模板匹配。语法格式为:
result=cv2.matchTemplate(image,templ,method[,mask])
函数cv2.matchTemplate()的返回值result 是一个结果集。类型是单通道32位浮点型。是由每个位置的比较结果所构成的。
如果输入图像(原始图像)尺寸是W * H,模板的尺寸是w * h,则返回值的大小为(W-w+1)*(H-h+1)。
在进行模板匹配时,模板在原始图像内遍历。在水平方向上:
因此,返回值result在水平方向上的大小是W-w+1(水平方向上的比较次数)。
在垂直方向上:
所以,返回值result在垂直方向上的大小是H-h+1(垂直方向上的比较次数)。
如果原始图像尺寸是 W * H,模板的尺寸是w * h,则返回值的大小为(W-w+1)* (H-h+1)。也就是说,模板图像要在输入图像内比较(W-w+1)*(H-h+1)次。
例如,在上图中,左上方的2×2小方块是模板图像,右下方的10×10图像是输入图像(原始图像)。在进行模板匹配时:
根据上述分析可知,比较结果result的大小满足(W-w+1)*(H-h+1),在上例中就是(10-2+1)×(10-2+1),即9×9。也就是说,模板图像要在输入图像内总计比较9×9=81次,这些比较结果将构成一个9×9大小的二维数组。
需要注意的是,函数cv2.matchTemplate()通过参数method来决定使用不同的查找方法。对于不同的查找方法,返回值result具有不同的含义。例如:
查找方法不同,结果的判定方式也不同。在查找最佳匹配时,首先要确定使用的是何种method,然后再确定到底是查找最大值,还是查找最小值。
查找最值(极值)与最值所在的位置,可以使用 cv2.minMaxLoc()函数实现。语法格式如下:
函数 cv2.minMaxLoc()能够查找整个数组内的最值及它们的位置,并且可以根据当前的掩模集来选取特定子集的极值。有关该函数的更多说明及实例,请参考第12章。
综上所述,函数cv2.matchTemplate()返回值中的最值位置就是模板匹配的位置。
例如,当method的值为cv2.TM_SQDIFF和cv2.TM_SQDIFF_NORMED时,0表示最佳匹配,值越大,则表示匹配效果越差。当使用这两种方法时,要寻找最小值所在的位置作为最佳匹配。如下语句能够找到cv2.matchTemplate()函数返回值中最小值的位置:
minVal,maxVal,minLoc,maxLoc=cv2.minMaxLoc(matchTemplate函数的返回值)
topLeft=minLoc # 查找最小值所在的位置
以topLeft点为模板匹配位置的左上角坐标,结合模板图像的宽度w和高度h可以确定匹配位置的右下角坐标,代码如下所示:
当 method 的值为 cv2.TM_CCORR、cv2.TM_CCORR_NORMED、cv2.TM_CCOEFF 和cv2.TM_CCOEFF_NORMED时,cv2.matchTemplate()函数的返回值越小,表示匹配度越差,而返回值越大则表示匹配度越好。此时,要寻找最大值所在的位置作为最佳匹配。
通过上述方式,我们确定了模板匹配的矩形对角坐标位置,接下来可以借助函数cv2.rectangle()将该位置用白色标记出来。
函数cv2.rectangle的语法格式为:
因此,使用的标记语句为:cv2.rectangle(img,topLeft,bottomRight,255,2)
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('../lena.bmp')
template = cv2.imread('../template.bmp')
th, tw = template.shape[:2]
rv = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(rv)
top_left = min_loc
bottom_right = (top_left[0] + tw, top_left[1] + th)
new_img = img.copy()
cv2.rectangle(new_img, top_left, bottom_right, 255, 2)
plt.subplot(131)
plt.imshow(template, cmap='gray')
plt.title('template')
plt.axis('off')
plt.subplot(132)
plt.imshow(rv, cmap='gray')
plt.title('matcing result')
plt.axis('off')
plt.subplot(133)
plt.imshow(new_img, cmap='gray')
plt.title('result')
plt.axis('off')
plt.show()
前面的例子中,我们在输入图像lena中搜索其眼部子图,该子图在整个输入图像内仅出现了一次。但是,有些情况下,要搜索的模板图像很可能在输入图像内出现了多次,这时就需要找出多个匹配结果。而函数 cv2.minMaxLoc()仅仅能够找出最值,无法给出所有匹配区域的位置信息。所以,要想匹配多个结果,使用函数 cv2.minMaxLoc()是无法实现的,需要利用阈值进行处理,来获取所有匹配的集合。
numpy模块中的函数where()能够获取模板匹配位置的集合。对于不同的输入,其返回的值是不同的。
例如:
# 当输入数组是一维时
import numpy as np
a=np.array([3,6,8,1,2,88])
b=np.where(a>5)
print(b)
# 输出结果
(array([1,2,5],dtype=int64),)
# 当输入数组是二维时
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))
综上所述,函数 np.where()可以找出在函数 cv2.matchTemplate()的返回值中,哪些位置上的值是大于阈值threshold的。
具体实现时,可以采用的语句为:
处理多个值,通常需要用到循环。因此,在获取匹配值的索引集合后,可以采用如下语句遍历所有匹配的位置,对这些位置做标记:
for i in 匹配位置集合:
标记匹配位置。
在循环处理匹配位置的时候,可以在循环中使用函数zip():
函数zip()用可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
例如:
import numpy as np
am = np.random.randint(0, 50, size=(3, 5))
print(am)
b = np.where(am > 20)
print(b)
for i in zip(*b):
print(i)
# 输出结果
[[37 39 43 48 49]
[16 48 37 23 25]
[44 40 0 44 38]]
(array([0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2], dtype=int64), array([0, 1, 2, 3, 4, 1, 2, 3, 4, 0, 1, 3, 4], dtype=int64))
(0, 0)
(0, 1)
(0, 2)
(0, 3)
(0, 4)
(1, 1)
(1, 2)
(1, 3)
(1, 4)
(2, 0)
(2, 1)
(2, 3)
(2, 4)
因此,如果希望循环遍历由np.where()返回的模板匹配索引集合,可以采用的语句为:
for i in zip(*模板匹配索引集合):
标记处理
函数 numpy.where()可以获取满足条件的模板匹配位置集合,然后可以使用函数cv2.rectangle()在上述匹配位置绘制矩形来标注匹配位置。
使用函数numpy.where()在函数cv2.matchTemplate()的输出值中查找指定值,得到的形式为“(行号,列号)”的位置索引。但是,**函数cv2.rectangle()中用于指定顶点的参数所使用的是形式为“(列号,行号)”的位置索引。**所以,在使用函数cv2.rectangle()绘制矩形前,要先将函数numpy.where()得到的位置索引做“行列互换”。可以使用如下语句实现loc内行列位置的互换:
函数cv2.rectangle()可以标记匹配图像的具体位置,分别指定要标记的原始图像、对角顶点、颜色、矩形边线宽度即可。
关于矩形的对角顶点:
其中的一个对角顶点A可以通过for循环语句从确定的满足条件的“匹配位置集合”内获取。
另外一个对角顶点,可以通过顶点A的位置与模板的宽(w)和高(h)进行运算得到。
因此,标记各个匹配位置的语句为:
for i in 匹配位置集合:
cv2.rectangle(输入图像,i, (i[0] + w, i[1] + h ), 255, 2)
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('../four_lena.bmp')
template = cv2.imread('../template.bmp')
h, w = template.shape[:2]
rst = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)
print(rst)
threshold = 0.99
loc = np.where(rst >= threshold)
new_img = img.copy()
for pt in zip(*loc):
cv2.rectangle(new_img, pt, (pt[0] + w, pt[1] + h), 255, 1)
plt.subplot(121)
plt.imshow(template, cmap='gray')
plt.title('template')
plt.axis('off')
plt.subplot(122)
plt.imshow(new_img, cmap='gray')
plt.title('rst')
plt.axis('off')
plt.show()