OpenCV Tutorials 11 - 模板匹配

1,178 阅读5分钟

模板匹配

本小节主要介绍如何使用模板匹配在图像中发现对象,用到以下函数: cv.matchTemplate(), cv.minMaxLoc()

  • 用法如下:
  1. cv.matchTemplate( image, templ, method[, result[, mask]] ) -> result
  2. cv.minMaxLoc( src[, mask] ) -> minVal, maxVal, minLoc, maxLoc

另外,我们在匹配之前。一定要将模板图片的像素大小和待匹配的原图中的ROI区域一定要保持一致,因为matchTemplate的匹配过程是:拿着模板滑过原图,按照模板左上角为参照,比较整个模板区域和待匹配区域的匹配程度的。

  • 模板匹配的六个参数
  1. TM_SQDIFF:计算平方差值:R(x,y)=x,y(T(x,y)I(x+x,y+y))2R(x,y)=x,y((T(x,y)I(x+x,y+y))M(x,y))2\begin{array}{c}R(x, y)=\sum_{x^{\prime}, y^{\prime}}\left(T\left(x^{\prime}, y^{\prime}\right)-I\left(x+x^{\prime}, y+y^{\prime}\right)\right)^{2} \\R(x, y)=\sum_{x^{\prime}, y^{\prime}}\left(\left(T\left(x^{\prime}, y^{\prime}\right)-I\left(x+x^{\prime}, y+y^{\prime}\right)\right) \cdot M\left(x^{\prime}, y^{\prime}\right)\right)^{2}\end{array},计算出来的值越小,则模板和原图像相关程度就越大

  2. TM_CCORR:计算相关性,计算出来的值越大,则模板和原图像相关程度就越大

  3. TM_CCOEFF:计算相关系数,计算出来的值越大,则模板和原图像相关程度就越大

  4. TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,则模板和原图像相关程度就越大

  5. TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,则模板和原图像相关程度就越大

  6. TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,则模板和原图像相关程度就越大

  • 实际运用时,最好使用归一化的方法

  • 匹配过程:“按图索骥”,将原图像划分为一个个和模板大小一致的区域,然后从左到右、从上到下按区比对,看模板位于哪个区域,滑动值一般设置为一个像素大小

  • 差异量化:对应像素点之间的灰度值差异

一、概念

模板匹配是一种在较大图像中搜索和查找模板图像位置的方法。 为此,OpenCV 附带了一个函数 cv.matchTemplate()。 它只是将模板图像滑到输入图像上(如在 2D 卷积中),并在模板图像下比较输入图像的模板和补丁。 OpenCV 中实现了几种比较方法。 它返回一个灰度图像,其中每个像素表示该像素的邻域与模板匹配的程度 (只是一个“程度”阵列,而非“图像”)

如果输入图像的大小为 (WxH),模板图像的大小为 (wxh),则输出图像的大小为 (W-w+1, H-h+1)。 得到结果灰度阵列后,您可以使用 cv.minMaxLoc() 函数查找最大值/最小值在哪里。 把它作为矩形的左上角,把(w,h)作为矩形的宽和高。 该矩形即是您的模板区域。

  • 注意:如果您使用 cv.TM_SQDIFF 作为比较方法,则最小值给出最佳匹配。
import cv2 as cv
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name, img):
    cv.imshow(name, img)
    cv.waitKey(0)
    cv.destroyAllWindows()
def compare(imgs):
  #  for i in range(len(imgs)):
 #       imgs[i][:,-3:-1,:] = [255,255,255]
    res = np.hstack(imgs)
    cv_show('Compare', res)

二、使用OpenCV进行模板匹配

我们将在照片中搜索xy的背部。 所以我创建了一个模板如下:

back = cv.imread('xy_back.png')
cv_show('Template',back)

download.png

# 模板最好使用灰度图
template = cv.cvtColor(back,cv.COLOR_BGR2GRAY)
# 获取模板的尺寸便于之后在原图中绘制出Bounding box
# 行数是高度,列数是宽度,别弄混
h,w = template.shape
img = cv.imread('xy.png')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 计算模板像素点和原图的“差异性”
res = cv.matchTemplate(gray, template, cv.TM_SQDIFF_NORMED)
# 得到结果阵列的特征点值和位置
minv, maxv, minLoc, maxLoc  = cv.minMaxLoc(res)
# 获取最佳匹配点,也就是差异最小点
W,H = minLoc
result = cv.rectangle(img.copy(), (W,H),( W+w-1, H+h-1), (0, 0, 255), 2)
cv_show('Result', result)

download.png

# 显示所有类型的匹配结果
img = cv.imread('xy.png',0)
img2 = img.copy()
template = cv.imread('xy_back.png',0)
w, h = template.shape[::-1]
# All the 6 methods for comparison in a list
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
            'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
for meth in methods:
    img = img2.copy()
    # 获取方法名称字符串
    method = eval(meth)
    # Apply template Matching
    res = cv.matchTemplate(img,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,top_left, bottom_right, 255, 2)
    
    plt.subplot(121),plt.imshow(res,cmap = 'gray')
    plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
    plt.subplot(122),plt.imshow(img,cmap = 'gray')
    plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
    plt.suptitle(meth)
    plt.show()

download.png

download.png

download.png

download.png

download.png

download.png

从左边图像可以看出,除了TM_SQDIFF or TM_SQDIFF_NORMED以外,其余的所有匹配方式匹配最好的点就接近纯白,而上面两二者接近纯黑,且匹配结果阵列的大小确为(W-w+1, H-h+1)。另外,基本上归一化模式都做得很好,非归一化中的 cv.TM_CCORR 模式匹配得最差!

三、多对象匹配

在上一节中,我们在图像中搜索了夏羽的背部,它在图像中只出现过一次。 假设您正在搜索一个多次出现的对象, cv.minMaxLoc() 不会为您提供所有位置。 在这种情况下,我们将使用阈值。 所以在这个例子中,我们将使用著名游戏马里奥的截图,我们将在其中找到砖块。

  • 待匹配图像:

download.png

  • 模板图像:

download.png

brick = cv.imread('brick.png')
mary = cv.imread('Mary.png')

template_b = cv.cvtColor(brick, cv.COLOR_BGR2GRAY)
template_m = cv.cvtColor(mary, cv.COLOR_BGR2GRAY)
img = cv.imread('SM.png')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
h_b, w_b = template_b.shape
h_m, w_m = template_m.shape

# 进行匹配,值越大匹配的越好
res_b = cv.matchTemplate(gray, template_b, cv.TM_CCORR_NORMED)
thresh = 0.9
# 使用np.where方法重新构造一个符合要求的结果阵列,
# (loc[0], loc[1])分别是符合要求的所有点集的y,x坐标
loc = np.where( res_b >= thresh)
# 这里逆序遍历的原因是(loc[0], loc[1])分别是符合要求的所有点集的y,x坐标,一语中的的
for pt in zip(*(loc[::])):
    cv.rectangle(img, (pt[1], pt[0]), (pt[1]+w_b, pt[0]+h_b), (0,255, 0), 2)
cv_show('Res', img)


# cv.rectangle(img, (loc[0][-2],loc[1][-2]), (loc[0][-2]+w_b,loc[1][-2]+h_b), (0,255, 0), 2)
#cv_show('Test', img)

# 方法正确
# minv,maxv, minloc,maxloc = cv.minMaxLoc(res_b)
# W,H = maxloc
# res = cv.rectangle(img.copy(), (W,H), (W+w_b, H+h_b), (0, 0, 255), 2)
# cv_show('Result', res)

SM = cv.imread('SM.png')
compare([SM, img])

download.png