使用OpenCV,Python和模板匹配来播放“Waldo在哪里?”

时间:2022-04-26
本文章向大家介绍使用OpenCV,Python和模板匹配来播放“Waldo在哪里?”,主要内容包括目标:、我们的拼图和查询图像、进一步的探讨、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

这是一篇来自PyImageSearch的Adrian Rosebrock的博客,他的博客内容包括计算机视觉,图像处理和建筑图像搜索引擎等。

图1 在这个谜题中找到Waldo需要多长时间?

看看上面的谜题《Waldo在哪里》(直译,或称《聪明的沃利》,是一套由英国插画家Martin Handford创作的儿童书籍,目标就是在一张人山人海的图片中找出一个特定的人物——沃尔多)。多久才能找到沃尔多(Waldo)?10秒?30秒?一分钟?

沃尔多是考察眼力的终极游戏。他实际上就“隐藏”在眼前 - 但由于各种干扰因素令人分心,我们不能马上把他挑出来。

归根到底,Waldo只是一个视觉模式。他戴眼镜,一顶帽子,身着他的经典白色和红色水平条纹衬衫。我们可能需要一点时间在页面上下左右扫视,但是我们的大脑能够挑选出这种模式,即使周围有各种东西令人分心。

问题是,电脑可以做得更好吗?我们可以写出来一个可以自动找到Waldo 的程序吗?

其实,我们可以。

使用计算机视觉技术,我们可以在一秒钟内找到沃尔多,比我们任何人自己找都快!

在这篇博客文章中,我将向您展示如何使用OpenCV和模板匹配功能来查找总是隐藏在视野之外的讨厌的Waldo。

以下是待办速览:

  • 我们要做什么:使用OpenCV构建Python脚本,可以在“Where's Waldo?”谜题中找到Waldo。
  • 你将学到什么:如何利用Python,OpenCV,并在其中使用模板匹配cv2.matchTemplatecv2.minMaxLoc。使用这些功能,我们将能够在我们的拼图图像中找到Waldo。
  • 你需要什么: Python,NumPy和OpenCV;了解一些基本的图像处理概念将有所帮助,但不是必须要求。这个操作指南是为了让您了解如何使用OpenCV进行模板匹配。没有安装这些库?没问题。我创建了预先配置好的虚拟机,并预先安装了所有必要的计算机视觉,图像处理和机器学习软件包。点击这里了解更多
  • 假设:我假设你已经在python2.6或python2.7环境中安装了NumPyOpenCV。同样,你可以在这里下载一个预配置了所有必需的包的虚拟机。

目标:

那么我们创建Python脚本的最终目标是什么?

目标是:给出沃尔多的查询图像和拼图图像后,找到沃尔登在拼图里的形象,并突出显示他的位置。

正如你将在本文后面看到的那样,我们只能用两行Python代码来完成这个任务 。其余的代码只是处理逻辑,如参数解析,以及显示解决的难题到我们的屏幕上。

我们的拼图和查询图像

我们需要两个图像来构建我们的Python脚本来执行模板匹配。

第一个图像是我们要解决的沃尔多之谜。您可以在本文的顶部看到图1中的谜题。

第二个图像是我们的Waldo查询图像:

图2 我们的Waldo查询图像

通过使用Waldo查询图像,我们将在原来的谜题中找到他。

不幸的是,这里是我们方法的实用性缺失的地方。

为了在我们的拼图中找到沃尔多,我们首先需要有沃尔多本身的形象。你可能会问,如果我已经有了沃尔多的形象,我为什么要去解开这个谜题呢?

好问题。

使用计算机视觉和图像处理技术在图像中找到Waldo 当然是可能的

但是,它需要一些稍微更先进的技术,例如:

  1. 过滤掉不是红色的颜色。
  2. 计算条纹图案的相关性,以匹配Waldo衬衫的红色和白色的过渡。
  3. 对与条纹图案具有高相关性的图像区域进行二值化。

本文旨在介绍基本的计算机视觉技术,如模板匹配。稍后我们可以深入讲解更先进的技术。Waldo只是一个我很想要和你分享的很酷且简单的模板匹配方法!

译者注:原文代码显示格式附带行数,但由于markdown主题不支持,译文中无法在代码区块显示行数,且部分注释翻译导致行数变化,故文章中代码所在行数仅供参考。

进一步的探讨

准备看一些代码?好的,让我们看一看:

# 导入(import)必要的包
import numpy as np
import argparse
import imutils
import cv2
 
# 构造参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--puzzle", required = True,
    help = "Path to the puzzle image")
ap.add_argument("-w", "--waldo", required = True,
    help = "Path to the waldo image")
args = vars(ap.parse_args())
 
# 加载谜题和Waldo的图像
puzzle = cv2.imread(args["puzzle"])
waldo = cv2.imread(args["waldo"])
(waldoHeight, waldoWidth) = waldo.shape[:2]

1-13行简单地导入我们要使用的包并配置我们的参数解析器。我们将使用NumPy进行数组操作,argparse来解析我们的命令行参数,以及cv2来把我们的OpenCV绑定。这个包imutils实际上是一组便利功能,用于处理基本的图像操作,如旋转,调整大小和平移。您可以在这里阅读更多关于这些类型的基本图像操作。

之后,我们需要设置我们的两个命令行参数。首先,--puzzle是我们的沃尔多谜题图像,而--waldo的路径,是沃尔多查询图像的路径。

再一次提醒,我们的目标是使用模板匹配在谜题图像中查找查询图像。

现在我们已经有了我们的图像的路径,我们使用cv2.imread函数将它们从第16行和第17行的磁盘加载进来-——这种方法只是从磁盘上读取图像,然后将其存储为多维的NumPy数组。

由于图像在OpenCV中被表示为NumPy数组,我们可以很容易地访问图像的尺寸。在第18行,我们分别获取了Waldo查询图像 的高度和宽度

我们现在准备好执行我们的模板匹配:

# 在谜题中找出Waldo
result = cv2.matchTemplate(puzzle, waldo, cv2.TM_CCOEFF)
(_, _, minLoc, maxLoc) = cv2.minMaxLoc(result)

我们用cv2.matchTemplate function第21行完成了模板匹配。这个方法需要三个参数。首先是我们的puzzle图像,这个图像包含了我们正在寻找的内容。第二个是我们的查询图像waldo。该图像包含在谜题图像中,我们正在寻找它的确切位置。最后,第三个参数是我们的模板匹配方法。有多种方法可以进行模板匹配,但在这种情况下,我们使用的是由标志指定的相关系数cv2.TM_CCOEFF

那么这个cv2.matchTemplate函数到底在做什么呢?

本质上,这个函数将我们的waldo查询图像作为“滑块”,并一次一个像素地将它从左到右,从上到下滑过我们的拼图谜题。然后,对于每一个位置,我们计算相关系数以确定匹配“好”还是“差”。具有足够高的相关性的区域可以视作我们的waldo模板的“匹配”。

由此,我们需要做的调用位于第22行cv2.minMaxLoc以找到我们的“好的”匹配在哪。

这就是模板匹配的全部内容!

实际上,以上只需要我们两行代码。

我们其余的源代码涉及提取包含Waldo的区域,然后在原始谜题图像中突出显示他:

# 找到包含Waldo 的滑块并将它从谜题图像中突出显示
topLeft = maxLoc
botRight = (topLeft[0] + waldoWidth, topLeft[1] + waldoHeight)
roi = puzzle[topLeft[1]:botRight[1], topLeft[0]:botRight[0]]
 
# 创建一个变暗的透明“图层”覆盖住除了Waldo之外的一切
mask = np.zeros(puzzle.shape, dtype = "uint8")
puzzle = cv2.addWeighted(puzzle, 0.25, mask, 0.75, 0)

第26行根据我们的滑动窗口抓取包含最佳匹配的图像的左上角(x,y)坐标。然后,我们根据第27行waldo图像宽度和高度计算右下(x,y)坐标。最后我们提取第28行的roi(Region of Interest兴趣区)。

下一步是构建一个透明的图层,使图像中除了 Waldo所有东西都变黑。在第32行,我们首先用零填充和我们谜题图像一样大小的mask来进行初始化。通过用零填充图像,我们可以创建一个全黑的图像。

为了创建透明效果,我们使用第33行cv2.addWeighted功能。第一个参数是我们的谜题图像puzzle,第二个参数表明我们希望它对我们输出图像的25%有贡献。然后,我们提供mask作为我们的第三个参数,使其对我们输出图像的75%有贡献。通过利用puzzlemaskcv2.addWeighted功能,我们就能够创造透明效果。

但是,我们仍然需要突出沃尔多!这很简单:

# 将原本的Waldo的图形放入图片中,他就会比其余部分“更亮”
puzzle[topLeft[1]:botRight[1], topLeft[0]:botRight[0]] = roi

# 显示图像
cv2.imshow("Puzzle", imutils.resize(puzzle, height = 650))
cv2.imshow("Waldo", waldo)
cv2.waitKey(0)

在这里,我们只是使用第37行的NumPy数组剪切技术把Waldo的图形放回到原始图像中,这没有什么。

最后,第40-42行在屏幕上显示我们的Waldo查询结果和(突出显示后的)谜题图像并等待按键。

要运行我们的脚本,请启动您的shell并执行以下命令:

$ python find_waldo.py --puzzle puzzle.png --waldo waldo.png

当你的脚本执行后,你应该在你的屏幕上看到类似这样的内容:

图3 我们已经成功找到了Waldo

我们在图片的左下角找到了Waldo!

使用Python和OpenCV进行模板匹配其实很简单。首先,您只需要两个图像 - 要匹配的对象的图像和包含该对象的图像。然后,你只需要调用cv2.matchTemplatecv2.minMaxLaoc。剩下的只是封装代码,将这些函数的输出粘贴在一起。