1. 引言
今天我们来研究一种传统图像处理领域中对象检测和跟踪不可或缺的方法——模板匹配,其主要目的是为了在图像上找到我们需要的图案,这听起来十分令人兴奋。
所以,事不宜迟,让我们直接开始吧!
2. 概念
模板匹配的算法的核心十分简单:它将模板与源图像中的每个部分进行比较,逐像素滑动。结果是一个相似度的图,该相似度图中每个像素值反映了模板与源图像中该位置的相似程度。
从本质上讲,它将模板在图像上进行卷积,类似于卷积神经网络中使用卷积核的方式。通过这个过程,创建了一个新的图像或矩阵,其中每个像素值表示模板与源图像中相应区域之间的相似性。通过分析该结果图像,我们可以识别峰值,这些峰值表示源图像中存在模板图像的精确位置。值得注意的是,模板匹配的实现可能会有所不同,主要是基于相似性的度量因方法各异而不同,这里不做扩展展开。
3. 举个栗子
巴拉巴拉讲了一堆概念性的文字,好多小伙伴会感觉到枯燥无味,基于此,我们来看我们的例子,首先我们引入我们需要的基础库,如下:
# Import libraries
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread, imshow
from skimage.color import rgb2gray
from skimage.feature import match_template
from skimage.feature import peak_local_max
紧接着,我们来观察我们的用例图像,代码如下:
original_image = imread('emojis.png')
plt.figure(figsize=(20,20))
plt.imshow(original_image)
plt.title('Original Image', fontsize=20, weight='bold')
plt.axis('off')
plt.show()
显示图像如下:
假设我们的任务安排为通过基本的图像处理流程,从上图中找到我们需要的心动模板。弄清楚了具体的需求,我们直接开始编码吧!
4. 图像灰度化
虽然模板匹配适用于彩色图像,但让我们简化并将图像转换为灰度图来减少计算量。
灰度化代码如下:
# Convert the image to grayscale
gray_image = rgb2gray(original_image[:,:,:3])
plt.figure(figsize=(20,20))
plt.imshow(gray_image, cmap='gray')
plt.title('Grayscale Image', fontsize=20, weight='bold')
plt.axis('off')
plt.show()
结果如下:
5. 加载模板
现在,让我们从灰度图中截取一个心动的表情作为我们的目标模板,代码如下:
template = gray_image[1330:1850,625:1140]
plt.figure(figsize=(10,10))
plt.imshow(template, cmap='gray')
plt.title('Template Image', fontsize=20, weight='bold')
plt.axis('off')
plt.show();
结果如下:
6 模板匹配
通过使用 skimage
库中的match_template
函数 , 我们可以得到衡量模板图和原图的相似度的热力图,如下:
result = match_template(gray_image, template)
plt.figure(figsize=(10,10))
imshow(result, cmap='viridis')
plt.show();
结果如下:
上图中颜色越鲜艳的区域显示了和我们的模板相似度越高的区域,你注意到图像中明亮的颜色区域形成的形状了吗?如果我们假设模板在源图像中只找到一次,那么我们可以通过寻找具有最高值(~1.00)的像素来找到它的位置。代码如下:
x, y = np.unravel_index(np.argmax(result), result.shape)
imshow(gray_image)
template_width, template_height = template.shape
rect = plt.Rectangle((y, x), template_height, template_width, color='y',
fc='none')
plt.gca().add_patch(rect);
得到结果如下:
7. 设置容忍度
为了定位模板的多个匹配,我们可以通过设定相关性值的峰值的容忍度来实现,代码如下:
imshow(gray_image)
template_width, template_height = template.shape
for x, y in peak_local_max(result, threshold_abs=0.99):
rect = plt.Rectangle((y, x), template_height, template_width, color='red',
fc='none')
plt.gca().add_patch(rect);
结果如下:
进而可以通过以下代码,将结果画到原图,如下所示:
plt.figure(figsize=(20, 20))
plt.imshow(original_image)
plt.title('We found our heart eyes emojis!', fontsize=20, weight='bold', color='red')
template_width, template_height = template.shape
for x, y in peak_local_max(result, threshold_abs=0.99):
rect = plt.Rectangle((y, x), template_height, template_width, color='red',
fc='none')
plt.gca().add_patch(rect);
最终结果如下:
8. 问题思考
- 如果我们改变阈值会发生什么?降低阈值将给我们更多的匹配(但也会有更多的误报),而提高阈值将使匹配更少,但可能更准确。
- 放大模板怎么样?模板越大,我们得到的匹配项就越少。这是因为匹配的大小必须与模板的大小几乎相同。
- 水平翻转模板?这可能会导致没有匹配,因为模板匹配对方向很敏感。
- 更改图像对比度?只要模板和原图像发生相同的更改,匹配项就应该保持有效。然而,剧烈的变化可能会改变结果。
9. 总结
本文重点介绍了在传统图像处理中,如何利用模板匹配的方法来进行从表情包图像中寻找心动表情模板的样例,并给出了相应的代码实现。由于是传统方案,该方法的阈值选择和泛化能力都有一定的局限性,但是学习其背后的原理可以帮助我们更好的理解相关理论概念。