图像处理讲义——模板匹配

什么是模板匹配(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 种可能的值,如下图所示。

image

具体计算公式如下图所示

image

  • 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解释一下各段代码并自行理解消化😊

posted @ 2024-02-21 11:16  Akasa  阅读(3137)  评论(0)    收藏  举报