opencv-python 4.12. 模板匹配
理论
模板匹配是一种在较大图像中搜索和查找模板图像位置的方法。为此,OpenCV附带了一个函数cv.matchTemplate()。它只是在输入图像上滑动模板图像(如在2D卷积中),并比较模板图像下的输入图像的模板和补丁。在OpenCV中实现了几种比较方法。它返回一个灰度图像,其中每个像素表示该像素的邻域与模板匹配的程度。
cv2.matchTemplate(image,templ,method[,result[,mask]])->result
- image:源图像,待匹配图像,8bit整数型、32bit浮点型,可以是单通道或多通道;
- templ:模板图像,类型同源图像,尺寸必须小于源图像;
- method:匹配方法;
- mask:掩码;
- result:返回结果,32bit浮点型,源图像为W×H,模板图像为w×h,生成的图像对象为(W−w+1)×(H−h+1);
method可选值:
- cv.TM_SQDIFF: 判断 minVal 越小,效果越好计算模板与目标图像的方差,由于是像素值差值的平方的和,所以值越小匹配程度越高;
- cv.TM_CCOEFF_NORMED: 判断 maxVal 越接近1,效果越好 范化的cv::TM_CCOEFF,-1 ~ 1之间。
- cv.TM_CCORR_NORMED: 判断 maxVal 越接近1,效果越好 范化的cv::TM_CCORR,0-1之间,我用的这个;
- cv.TM_SQDIFF_NORMED: 判断 minVal 越接近0,效果越好 范化的cv.TM_SQDIFF,取值为0-1之间,完美匹配返回值为0;
- cv.TM_CCORR: 判断 maxVal 越大,效果越好 使用dot product计算匹配度,越高匹配度就好;
- cv.TM_CCOEFF: 判断 maxVal 越大,效果越好 采用模板与目标图像像素与各自图像的平均值计算dot product,正值越大匹配度越高,负值越大图像的区别越大,但如果图像没有明显的特征(即图像中的像素值与平均值接近)则返回值越接近0;
如果输入图像的大小(WxH)且模板图像的大小(wxh),则输出图像的大小为(W-w + 1,H-h + 1)。获得结果后,可以使用cv.minMaxLoc()函数查找最大/最小值的位置。将其作为矩形的左上角,并将(w,h)作为矩形的宽度和高度。那个矩形是你的模板区域。
注意:如果你使用cv.TM_SQDIFF作为比较方法,则最小值会给出最佳匹配。
OpenCV中的模板匹配
我们将尝试所有比较方法,以便我们可以看到它们的结果如何:
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\9.jpg', 0)
template = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\9_1.jpg', 0)
w, h = template.shape[::-1]
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
for i, meth in enumerate(methods):
img_name = f'img_{i}'
img_name = img.copy()
method = eval(meth)
# Apply template Matching
res = cv.matchTemplate(img_name, template, method)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv.rectangle(img_name, top_left, bottom_right, 0, 2)
plt.subplot(6, 2, i * 2 + 1), plt.imshow(res, cmap='gray')
plt.title(f'Matching Result {i}'), plt.xticks([]), plt.yticks([])
plt.subplot(6, 2, i * 2 + 2), plt.imshow(img_name, cmap='gray')
plt.title(f'Detected Point {meth}'), plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
你可以看到使用cv.TM_CCORR的结果不如我们预期的那样好。
与多个对象匹配的模板
像上边方块是有很多个,但是只标出来一个的位置,这是因为cv.minMaxLoc()不会给出所有的位置。在这种情况下,我们将使用阈值化。
import cv2 as cv
import numpy as np
img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\9.jpg')
template = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\9_1.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
template_gray = cv.cvtColor(template, cv.COLOR_BGR2GRAY)
w, h = template_gray.shape
res = cv.matchTemplate(img_gray, template_gray, cv.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
cv.rectangle(img, pt, (pt[0] + w, pt[1] + h), (25, 255, 0), 1)
cv.imshow('img', img)
cv.waitKey(0)