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

1|0什么是模板匹配(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中就是二维数组)

模板图片

输入图片

匹配过程

2|0模板匹配的代码实现

2|1单模板匹配

所谓单模板匹配,就是一个模板只能匹配输入图像中的一个位置,即一个一对一的映射。

我们先来看看opencv中与模板匹配相关的api函数吧

1|0result = 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对应公式计算出来的匹配度

1|0minVal, 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 函数标记出我们所匹配到的位置

1|0Img = cv.rectangle( img, pt1, pt2, color[, thickness])

  • img 表示要标记的目标图像。
  • pt1 是矩形的顶点。
  • pt2是pt1的对角顶点
  • color是颜色(灰度图像中就是明暗强度)
  • thickness是矩形边线宽度(可选)

2|2单模板匹配的代码实现

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()

2|3多模板匹配

在日常的代码编写中,我们大都会遇到这种情况:你有模板1、2、3···n,每个模板在输入图像里都有多处匹配的位置。这时,你就不能使用 cv2.minMaxLoc 函数来获取最佳匹配位置,因为你所需要的是一个一对多的映射,而非一对一的映射,所以你需要另一种方法对 cv2.matchTemplate 函数的返回值 result 数组进行处理,来获取所有能匹配的位置,这也就是多模板匹配与单模板匹配的区别。

那么,我们如何处理 result 数组呢?

熟悉 numpy 的同学知道,numpy中有一个 np.where 函数,能够返回numpy数组中满足输入条件的所有元素的行列位置信息,该函数原型为:

1|0loc = 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])的结果

这样,我们就可以进行我们的多模板匹配了。

2|4多模板匹配的代码实现

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


__EOF__

本文作者Asaka
本文链接https://www.cnblogs.com/Asaka-QianXiang/p/18024680.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Akasa  阅读(2105)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2023-02-21 【线性代数复习笔记】矩阵特征值,特征向量,相似对角化与实对称矩阵
点击右上角即可分享
微信分享提示