Python OpenCV入门到精通学习日记:模板匹配
前言
模板匹配是一种最原始、最基本的识别方法,可以在原始图像中寻找特定图像的位置。模板匹配经常应用于简单的图像查找场景中,例如,在集体合照中找到某个人的位置。
模板匹配
- Python OpenCV入门到精通学习日记:模板匹配
- 前言
- 1 模板匹配方法
- 2 单模板匹配
- 2.1 单目标匹配
- 2.2 多目标匹配
- 3 多模板匹配
- 4 小结
1 模板匹配方法
模板是被查找目标的图像,查找模板在原始图像中的哪个位置的过程就叫模板匹配。OpenCV提供的matchTemplate()
方法就是模板匹配方法
result = cv2.matchTemplate(image, templ, method, mask)
参数说明:
image:原始图像。
templ:模板图像,尺寸必须小于或等于原始图像。
method:匹配的方法,可用参数值如表所示。
mask :可选参数 。掩 模 ,只有cv2.TM_SQDIFF 和cv2.TM_CCORR_NORMED支持此参数,建议采用默认值。
result:计算得出的匹配结果。如果原始图像的宽、高分别为W、H,模板图像的宽、高分别为w、h,result就是一个W-w+1列、H-h+1行的32位浮点型数组。数组中每一个浮点数都是原始图像中对应像素位置的匹配结果,其含义需要根据method参数来解读。
参数值 | 值 | 含 义 |
---|---|---|
CV2.TM_SQDIFF | 0 | 差值平方和匹配,也叫平方差匹配。可以理解为差异程度,匹配程度越高,计算结果越小。完全匹配的结果为0 |
CV2.TM_SQDIFF_NORMED | 1 | 标准差值平方和匹配,也叫标准平方差匹配,规则同上 |
CV2.TM_CCORR | 2 | 相关匹配。可以理解为相似程度,匹配程度越高,计算结果越大 |
CV2.TM_CCORR_NORMED | 3 | 标准相关匹配,规则同上 |
CV2.TM_CCOEFF | 4 | 相关系数匹配,也属于相似程度,计算结果为-1~1的浮点数,1表示完全匹配,0表示毫无关系,-1表示2张图片亮度刚好相反 |
CV2.TM_CCOEFF_NORMED | 5 | 标准相关系数匹配,规则同上 |
在模板匹配的计算过程中,模板会在原始图像中移动。模板与重叠区域内的像素逐个对比,最后将对比的结果保存在模板左上角像素点索引位置对应的数组位置中。
以下是计算出来的结果格式,所有的方法计算出来的数组格式都是相同的,只有数值不同。
模板将原始图像中每一块区域都覆盖一遍,但结果数组的行、列数并不等于原始图像的像素的行、列数。这是因为我们的参照点是模版的左上角,假设模板的宽为w,高为h,原始图像的宽为W,高为H,模板移动到原始图像的边缘之后就不会继续移动了,所以该区域的边长为“原始图像边长-模板边长+1”,最后加1是因为移动区域内的上下、左右的2个边都被模板覆盖到了,如果不加1会丢失数据。
2 单模板匹配
匹配过程中只用到一个模板场景叫单模板匹配。原始图像中可能只有一个和模板相似的图像,也可能有多个。如果只获取匹配程度最高的那一个结果,这种操作叫作单目标匹配。如果需要同时获取所有匹配程度较高的结果,这种操作叫作多目标匹配。
2.1 单目标匹配
单目标匹配只获取一个结果即可,就是匹配程度最高的结果。
matchTemplate()
方法的计算结果是一个二维数组,OpenCV提供了一个minMaxLoc()
方法专门用来解析这个二维数组中的最大值、最小值以及这2个值对应的坐标,minMaxLoc()
方法的语法如下:
minValue, maxValue, minLoc, maxLoc = cv2.minMaxLoc(src,mask)
参数说明:
src:matchTemplate()方法计算得出的数组。
mask:可选参数,掩模,建议使用默认值。
返回值说明:
minValue:数组中的最小值。
maxValue:数组中的最大值。
minLoc:最小值的坐标,格式为(x, y)。
maxLoc:最大值的坐标,格式为(x, y)。
平方差匹配的计算结果越小,匹配程度越高。minMaxLoc()
方法返回的minValue值就是模板匹配的最优结果,minLoc就是最优结果区域左上角的点坐标,区域大小与模板大小一致。
在这里我们举个例子,首先我们需要图片和模板,首先我们截取一张我们的电脑壁纸:
然后我们找一个图标作为我们的实验品模板:
接下来开始编写代码:
# 导入cv2模块,它是OpenCV库的Python接口
import cv2
# 读取名为"img_2.png"的图像文件,将其存储在变量img中
img = cv2.imread("img_2.png")
# 读取名为"img_3.png"的图像文件,将其存储在变量templ中,用作模板
templ = cv2.imread("img_3.png")
# 获取模板图像的高度、宽度和通道数,分别存储在height、width和c中
height, width, c = templ.shape
# 使用matchTemplate函数在img图像中寻找templ模板的位置
# cv2.TM_SQDIFF_NORMED是匹配方法,表示使用平方差归一化方法
results = cv2.matchTemplate(img, templ, cv2.TM_SQDIFF_NORMED)
# 打印匹配结果矩阵
print(results)
# 使用minMaxLoc函数找到匹配结果中的最小值和最大值,以及它们的坐标位置
# minValue和maxValue分别存储最小和最大值
# minLoc和maxLoc分别存储最小值和最大值的坐标
minValue, maxValue, minLoc, maxLoc = cv2.minMaxLoc(results)
# 根据最小值坐标计算矩形的起始点,即模板匹配的起始位置
resultPoint1 = minLoc
# 根据模板的宽度和高度计算矩形的结束点,即模板匹配的结束位置
resultPoint2 = (resultPoint1[0] + width, resultPoint1[1] + height)
# 在原图中用红色矩形标记出模板匹配的位置,线宽为2
cv2.rectangle(img, resultPoint1, resultPoint2, (0, 0, 255), 2)
# 显示标记后的图像,窗口标题为"img"
cv2.imshow("img", img)
# 等待用户按键,任何键都可以
cv2.waitKey()
# 销毁所有由OpenCV创建的窗口
cv2.destroyAllWindows()
运行结果如下:
当我们有一个模板后,如何在两个很相似的图中准确匹配模板呢?接下来,使用模板匹配的相应方法模拟这个游戏。
我们在这里举个例子:
首先是模板:
然后是两个极其相似的情景:
注意看221路标的路障有个黄色的线。
现在开始识别:
import cv2
# 初始化一个空列表,用于存储图像
img = []
# 使用append方法将读取的图像添加到img列表中
img.append(cv2.imread("image_221.png"))
img.append(cv2.imread("image_222.png"))
templ = cv2.imread("templ.png")
# 用于存储匹配最佳图像的索引
index = -1
min = 1
for i in range(0,len(img)):
results = cv2.matchTemplate(img[i],templ,cv2.TM_SQDIFF_NORMED)
# 注意看这里他只匹配了第一行的数据,这样有时是不够准确的,但如果能够确认正确,这样也更加高效
if min > any(results[0]):
index = i
cv2.imshow("result",img[i])
cv2.waitKey()
cv2.destroyAllWindows()
在这段代码中,index 变量初始化为 -1 的原因主要有两个:
- 默认值:在编程中,使用 -1 作为索引的初始值是一种常见的做法,特别是在处理列表或数组时。-1 表示没有有效的索引,因为索引通常是从 0 开始的。这样做的目的是为了在循环开始之前,确保 index 值不指向列表中的任何有效元素。
- 错误检查:如果循环中没有找到任何匹配项,index 将保持其初始值 -1。这样,在循环结束后,你可以通过检查 index 的值来判断是否有有效的匹配项被找到。如果 index 仍然是 -1,这意味着没有找到匹配度更高的图像;如果 index 变成了一个非负整数,这意味着找到了匹配度最高的图像。
在这段代码中,index 用于跟踪在图像列表 img 中,模板图像 templ 匹配度最高的图像的索引。如果在遍历所有图像后没有找到匹配度更高的图像,index 将保持 -1,表示没有找到有效的匹配项。如果在循环中找到匹配度更高的图像,index 将被更新为当前图像的索引,这样在循环结束后,就可以通过 index 来访问匹配度最高的图像,并将其显示出来。
运行结果如下:
现在大家的图像文件中往往会有很多照片,有时会有很多的相同或者相似的图片,但我们如果想清除相同的文件我们在电脑上就需要一个一个打开用人眼来判断。这样很浪费时间和精力,而Opencv正好可以解决这个问题。
我们假设现在一个文件夹内有很多照片且文件格式不同:
import cv2 # 导入OpenCV库,用于图像处理
import os # 导入操作系统接口库,用于文件路径操作
import sys # 导入系统相关的参数和函数库
# 设置图片所在的文件夹路径
pic_path = "C:\\Users\\lyh20\\PycharmProjects\\this is a bird program\\Pictures"
# 设置图片缩放到的尺寸,宽100像素,高100像素
width, height = 100, 100
# 获取指定文件夹中所有的文件名列表
pic_file = os.listdir(pic_path)
# 初始化一个列表来存储检测到的相似图片的索引
same_pic = []
# 初始化一个列表来存储读取的图片数据
imgs = []
# 初始化一个集合来存储已经检测过的图片索引,避免重复检测
have_same = set()
# 获取图片文件的数量
number = len(pic_file)
# 如果文件夹中没有图片,则打印错误信息并退出程序
if number == 0:
print("error:没有图像")
sys.exit(0)
# 读取图片并将其缩放到指定尺寸,然后添加到imgs列表中
for file_name in pic_file:
pic_name = os.path.join(pic_path, file_name) # 拼接完整的文件路径
img = cv2.imread(pic_name) # 读取图片
img = cv2.resize(img, (width, height)) # 缩放图片
imgs.append(img) # 将图片添加到列表中
# 对imgs列表中的图片进行两两比较,查找相似的图片
for i in range(0, number - 1):
if i in have_same: # 如果索引已经在集合中,则跳过
continue
templ = imgs[i] # 当前图片作为模板
same = [i] # 初始化一个列表存储当前找到的相似图片索引
for j in range(i + 1, number): # 从下一个图片开始比较
if j in have_same: # 如果索引已经在集合中,则跳过
continue
pic = imgs[j] # 要比较的图片
# 使用cv2.matchTemplate函数进行模板匹配,cv2.TM_CCOEFF_NORMED是匹配方法
results = cv2.matchTemplate(pic, templ, cv2.TM_CCOEFF_NORMED)
# 如果匹配结果大于0.9,认为图片相似
if results > 0.9:
same.append(j) # 添加相似图片的索引
have_same.add(i) # 添加索引到集合中,避免重复比较
have_same.add(j)
# 如果找到多于一个相似图片,则添加到same_pic列表中
if len(same) > 1:
same_pic.append(same)
# 打印出相似图片的文件名
for same_list in same_pic:
text = "相同照片:"
for same in same_list:
text += str(pic_file[same]) + "," # 添加文件名到字符串
print(text) # 打印结果
代码中给出了详细的注释,如果还有不明白的评论区提问哦。
运行结果如下:
相同照片:10.png,4.jpg,
相同照片:2.jpg,5.jpg,9.png,
具体的结果大家可以自己去试试看。
2.2 多目标匹配
多目标匹配需要将原始图像中所有与模板相似的图像都找出来,使用相关匹配或相关系数匹配可以很好地实现这个功能。如果计算结果大于某值(例如0.999),则认为匹配区域的图案和模板是相同的。
示例:使用cv2.TM_CCOEFF_NORMED方法进行模板匹配,使用for循环遍历matchTemplate()方法返回的结果,找到所有大于0.99的计算结果,在这些结果的对应区域位置绘制红色矩形边框。编写代码时要注意:数组的列数在图像坐标系中为横坐标,数组的行数在图像坐标系中为纵坐标。
这是图像和模板:
import cv2
img = cv2.imread("img_5.png")
templ = cv2.imread("img_6.png")
height,width,c = templ.shape
results = cv2.matchTemplate(img,templ,cv2.TM_CCOEFF_NORMED)
for y in range(len(results)):
for x in range(len(results[y])):
if results[y][x] > 0.99:
cv2.rectangle(img,(x,y),(x+width,y+height),(0,0,255),2)
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
3 多模板匹配
匹配过程中同时查找多个模板的操作叫多模板匹配。多模板匹配实际上就是进行了n次“单模板多目标匹配”操作,n的数量为模板总数。
每一个模板都要做一次“单模板多目标匹配”,最后把所有模板的匹配结果汇总到一起。“单模板多目标匹配”的过程可以封装成一个方法,方法参数为模板和原始图像,方法内部将计算结果再加工一
下,直接返回所有红框左上角和右下角两点横纵坐标的列表。在方法之外,将所有模板计算得出的坐标汇总到一个列表中,按照这些汇总的坐标一次性将所有红框都绘制出来。
import cv2 # 导入OpenCV库
def myMatchTemplate(img, templ): # 定义一个函数,用于模板匹配
height, width, c = templ.shape # 获取模板图像的高、宽和通道数
loc = list() # 初始化一个列表,用于存储红框的坐标
results = cv2.matchTemplate(img, templ, cv2.TM_CCOEFF_NORMED) # 按照标准相关系数匹配
for i in range(len(results)): # 遍历结果数组的行
for j in range(len(results[0])): # 遍历结果数组的列
if results[i][j] > 0.99: # 如果相关系数大于0.99则认为匹配成功
loc.append((i, j, j + width, i + height)) # 在列表中添加匹配成功的红框对角线两点坐标
return loc # 返回红框坐标列表
img = cv2.imread("img_5.png") # 读取原始图像
templs = list() # 初始化模板列表
templs.append(cv2.imread("img_6.png")) # 添加模板1
templs.append(cv2.imread("img_7.png")) # 添加模板2
templs.append(cv2.imread("img_8.png")) # 添加模板3
loc = list() # 初始化一个列表,用于存储所有模板匹配成功位置的红框坐标
for temp in templs: # 遍历所有模板
loc += myMatchTemplate(img, temp) # 记录该模板匹配得出的坐标
for i in loc: # 遍历所有红框的坐标
cv2.rectangle(img, (i[0], i[1]), (i[2], i[3]), (0, 0, 255), 2) # 在图片中绘制红框
cv2.imshow("img", img) # 显示匹配的结果
cv2.waitKey(0) # 按下任何键盘按键后继续
cv2.destroyAllWindows() # 释放所有窗体
我实在找不到合适的材料,所以大家可以自己去试试哈。
4 小结
我个人觉得opencv的模板匹配不怎么好用,至少我跟着书上的材料学习,是可以准确判定的,但是自己找的一些复杂的材料,判定准确性很低,但是也要好好学,我个人觉得不好用,可能是我才疏识浅,欢迎评论区留言。
明天要开始学习滤波器。