<返回更多

用Python和OpenCV构建属于自己的图像搜索引擎

2019-09-02    
加入收藏

无论你是在给你的个人图片加标签和分类,还是为你的公司网站搜索库存照片,或者仅仅是为你的下一篇史诗般的博客文章寻找合适的图片,试图使用文本和关键词来描述一些本质上是可视化的东西,都是一件非常痛苦的事情。

为了解决这个痛苦,OpenCV为我们提供了很好的工具,之前学习的很多关于图像特征点提取的算法,现在就派上用场了。下面我们将创建一个属于自己的图像搜索引擎

什么是图像搜索引擎呢?

顾名思义,就是用一张图像作为搜索关键词,这和文本类搜索类似,区别就是一个用文本,一个用图像文件。或许你以前使用过google和其它搜索引擎,基本上现在也支持图像搜索,就是给定一张图像文件,搜索引擎就会给出和给定图像类似的图像列表供你参考。听起来很难,如何量化一个图像的内容,使其可搜索? 我们稍后会讨论这个问题的答案。但首先,让我们学习更多关于图像搜索引擎的知识。一般来说,有三种类型的图像搜索引擎: 元数据搜索、实例搜索和两者的混合方法。

元数据搜索

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 1: 一个由元数据图像搜索引擎搜索的例子。请注意如何将关键字和标记手动赋值到图像。

使用元数据进行搜索与上面提到的基于关键字的标准搜索引擎略有不同。元数据系统的搜索很少检查图像本身的内容。相反,它们依赖于文本线索,如(1)人工注释和标签,以及(2)自动上下文提示,如网页上出现在图像附近的文本。

当用户通过元数据系统对搜索执行搜索时,他们提供一个查询,就像在传统的文本搜索引擎中一样,然后返回具有类似标记或注释的图像。

同样,当使用元数据系统搜索时,很少检查实际图像本身。元数据图像搜索引擎的一个很好的例子是Flickr。上传图片到Flickr后,会出现一个文本字段,用于输入描述上传图片内容的标签。然后Flickr获取这些关键字,对它们进行索引,并利用它们来查找和推荐其他相关的图片。

实例搜索

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 2: TinEye是一个"逐例搜索"图像搜索引擎的例子。图像本身的内容用于执行搜索,而不是文本。

另一方面,实例系统的搜索完全依赖于图像的内容——不提供关键字。对图像进行分析、量化和存储,以便系统在搜索期间返回类似的图像。量化图像内容的图像搜索引擎称为基于内容的图像检索(CBIR)系统。CBIR这个词在学术文献中很常用,但在现实中,它只是"图像搜索引擎"的一种更奇特的说法,更令人辛酸的是,搜索引擎严格依赖于图像的内容,而不是与图像相关的任何文本注释。

一个很好的例子是一个实例搜索系统如TinEye。TinEye实际上是一个反向图像搜索引擎,您提供一个查询图像,然后TinEye返回几乎相同的匹配图像,以及原始图像出现的网页。看一下本节中的示例图像。这里我上传了一张谷歌logo的图片。TinEye检查了图片的内容,在搜索了超过60亿张图片的索引后,返回给我谷歌标志出现的13000多个网页。

所以考虑一下这个:你打算用TinEye手工标记这60亿张图片中的每一张吗?当然不是。这将需要大量的员工,而且成本极高。相反,您可以使用某种算法来提取"特性"(即:用于量化和抽象地表示图像的数字列表)。然后,当用户提交查询图像时,您从查询图像中提取特性,并将它们与您的特性数据库进行比较,然后尝试找到类似的图像。

再次强调,通过示例系统搜索严格依赖于图像的内容,这一点很重要。这些类型的系统往往非常难以构建和扩展,但是允许一个完全自动化的算法来控制搜索——不需要人工干预。

混合模式

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 3: 混合图像搜索引擎可以同时考虑文本和图像。

当然,两者之间有一个中间地带——以Twitter为例。在Twitter上,你可以上传照片来配推文。一种混合的方法是将从图片中提取的特征与推文相关联。使用这种方法,您可以构建一个图像搜索引擎,该引擎可以同时接受上下文提示和按示例搜索策略。

让我们继续定义一些重要的术语,这些术语将在描述和构建图像搜索引擎时经常使用。

一些重要的术语

在深入讨论之前,我们先花点时间来定义一些重要的术语。当建立一个图像搜索引擎时,我们首先要索引我们的数据集。索引数据集是通过使用图像描述符从每个图像中提取特征来量化数据集的过程。图像描述符定义了我们用来描述图像的算法。

比如:

· 每个红、绿、蓝通道的均值和标准差

· 用图像的统计矩来表征形状。

· 用来描述形状和纹理的梯度大小和方向。

这里的重要结论是,图像描述符决定了如何对图像进行量化。

另一方面,特性是图像描述符的输出。当您将图像放入图像描述符时,您将从另一端获得特性。特征(或特征向量)是最基本的术语,只是用于抽象地表示和量化图像的数字列表。

形象的用下图表示:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 4: 图像描述符的管道。将输入图像呈现给描述符,应用图像描述符输出特征向量(i。返回一个数字列表),用于量化图像的内容。

这里向我们展示了一个输入图像,我们应用图像描述符,然后我们的输出是一个用于量化图像的特性列表。然后利用距离度量或相似度函数对特征向量进行相似性比较。距离度量和相似度函数将两个特征向量作为输入,然后输出一个数字,表示这两个特征向量有多"相似"。

下图显示了比较两幅图像的过程:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 5:为了比较两幅图像,我们将各自的特征向量输入距离度量/相似度函数。输出是一个值,用于表示和量化这两幅图像彼此之间的"相似性"。

给定两个特征向量,用距离函数来确定这两个特征向量的相似程度。距离函数的输出是一个单浮点值,用来表示两幅图像之间的相似性。

任意一个图像检索系统包含四步

无论你正在构建什么基于内容的图像检索系统,它们都可以归结为4个不同的步骤:

1. 定义图像描述符: 在这个阶段,您需要决定要描述图像的哪个方面。你对图像的颜色感兴趣吗?图像中物体的形状?或者你想描述纹理?

2. 索引你的数据集: 现在,你有你的图像描述符定义,你的工作是这个图像描述符应用于每个图像数据集,从这些图像中提取的特征,编写功能存储(例CSV文件,RDBMS,redis,等等), 这样便于后续比较他们的相似性。

3. 定义相似性度量: 现在你有了一堆特征向量。但是你要怎么比较它们呢? 流行的选择包括欧氏距离、余弦距离和卡方距离,但实际的选择高度依赖于(1)数据集和(2)提取的特征类型。

4. 搜索: 最后一步是执行实际的搜索。用户提交查询图像系统(从一个上传表单或通过移动应用程序,例如),你的工作将是(1)从这个查询图像, 然后提取特征(2)应用相似性函数比较查询功能已索引的特性。然后,根据相似性函数返回最相关的结果。

同样,这是任何CBIR系统中最基本的4个步骤。随着它们变得更加复杂,并利用不同的特性表示,步骤的数量会增加,您将向上面提到的每个步骤添加大量的子步骤。但是现在,让我们保持简单,只使用这4个步骤。

让我们看一些图形,使这些高级步骤更具体一些。下图详细说明了步骤1和步骤2:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 6: 表示从数据集中每个图像中提取特征的过程的流程图。

我们首先获取图像数据集,从每个图像中提取特征,然后将这些特征存储在数据库中。然后我们可以继续执行搜索(步骤3和步骤4):

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 7: 在CBIR系统上执行搜索。用户提交查询,描述查询图像,将查询特性与数据库中的现有特性进行比较,根据相关性对结果进行排序,然后将结果呈现给用户。

首先,用户必须向图像搜索引擎提交查询图像。然后我们获取查询图像并从中提取特性。然后将这些"查询特性"与我们已经在数据集中索引的图像的特性进行比较。最后,根据相关性对结果进行排序并呈现给用户。

度假照片数据库

我们将使用INRIA Holidays数据集作为我们的图像数据集。

这个数据集包含了来自世界各地的各种度假旅行,包括埃及金字塔的照片、海底与海洋生物的潜水、山上的森林、晚餐时的酒瓶和餐盘、划船远足和大洋对岸的日落。

以下是数据集中的一些例子:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 8: 来自INRIA Holidays数据集的示例图像。我们将使用这个数据集来构建我们的图像搜索引擎。

一般来说,这个数据集在建模我们期望游客在风景旅游中拍摄什么方面做得非常好。

目标

我们的目标是建立一个个人图像搜索引擎。给定我们的度假照片数据集,我们希望通过创建一个"更像这样"的功能使这个数据集"可搜索"——这将是一个"示例搜索"图像搜索引擎。例如,如果我提交一张帆船划过河流的照片,我们的图像搜索引擎应该能够找到并检索我们在游览码头和码头时的度假照片。

请看下面的例子,我提交了一张水上船只的照片,并在我们的度假照片收藏中找到了相关的图片:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 9: 我们的图像搜索引擎的一个例子。我们提交了一个包含海上船只的查询图像。返回给我们的结果是相关的,因为它们也包括船只和大海。

为了构建这个系统,我们将使用一个简单但有效的图像描述符: 颜色直方图。通过使用颜色直方图作为图像描述符,我们将依赖于图像的颜色分布。因此,我们必须对我们的图像搜索引擎做出一个重要的假设:

假设: 具有相似颜色分布的图像将被认为彼此相关。即使图像的内容有很大的不同,只要它们的颜色分布也相似,它们仍然会被认为是"相似的"。这是一个非常重要的假设,但是当使用颜色直方图作为图像描述符时,这通常是一个公平合理的假设。

步骤1: 定义图像描述符

我们不使用标准的颜色直方图,而是使用一些技巧,使它更加健壮和强大。

我们的图像描述符将是HSV颜色空间(色调、饱和度、值)中的三维颜色直方图。通常,图像表示为红、绿和蓝(RGB)的三元组。我们经常把RGB颜色空间想象成"立方体",如下图所示:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 10: RGB立方体的例子

然而,虽然RGB值很容易理解,但是RGB颜色空间不能模拟人类感知颜色的方式。相反,我们将使用HSV颜色空间,它将像素强度映射到一个圆柱体:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 11: HSV圆柱体的例子

还有其他一些颜色空间在模仿人类感知颜色方面做得更好,比如CIE L*a*b*和CIE XYZ空间,但是让我们在第一个图像搜索引擎实现中保持颜色模型相对简单。

现在我们已经选择了一个颜色空间,现在我们需要为直方图定义方柱的数量。直方图是用来给一个(粗略)的感觉像素强度的密度在一个图像。本质上,我们的直方图将估计底层函数的概率密度,在本例中,是图像i中出现像素颜色C的概率P。如果你选择了太少的方柱,那么你的直方图将会有更少的成分,并且无法消除具有本质上不同颜色分布的图像之间的歧义。同样的,如果你使用了太多的方柱,你的直方图将会有很多的成分和图像非常相似的内容可能会被认为和不相似的时候,实际上他们是。下面是一个只有几个方柱的直方图的例子:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 12: 一个9箱直方图的例子。

请注意,对于要表示的给定像素而言,算是非常少的了。

下面是一个有很多方柱的直方图的例子:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 13: 一个128箱直方图的例子。请注意,一个给定的像素可以放在很多方柱中。

在上面的例子中可以看到, 用了很多的方柱, 但随着更多的方柱, 你失去了概括图像之间相似的感知内容的能力, 因为所有的高峰和低谷的直方图将不得不为了匹配两个图像是否是类似的。就我个人而言,我喜欢一种可迭代的、实验性的方法来调整方柱的数量。这种迭代方法通常基于我的数据集的大小。我的数据集越小,我使用的方柱就越少。如果我的数据集很大,我使用更多的方柱,使我的直方图更大,更有辨别力。

一般情况下,您需要对颜色直方图描述符的方柱数进行实验,因为它依赖于(1)数据集的大小和(2)数据集中的颜色分布彼此之间的相似性。

为我们的假期照片图像搜索引擎, 我们将利用3D颜色直方图,其中8个方柱用于HSV颜色空间中对的色彩通道,12个方柱为"饱和度"通道和3个方柱为"值"通道,总特征向量维度为 8 x 12 x 3 = 288。

这意味着,对于我们数据集中的每个图像,无论是36 x 36像素的图像还是2000 x 1800像素的图像,都将只使用288个浮点数列表抽象地表示和量化所有图像。我认为解释3D直方图最好的方法是使用连接词AND。3D HSV颜色描述符将询问给定的图像中,有多少像素的色相值落在第1个方柱中,和有多少像素的饱和度值落在第1方柱中,以及有多少像素的强度值落在第1方柱中。然后列出满足这些要求的像素数。对每个方柱的组合重复这个过程; 然而,我们能够以一种计算效率极高的方式来做这件事。

新建文件,命名为colordescriptor.py,让我们开始编写需要的代码:

Python

# import the necessary packages
import numpy as np
import cv2
import imutils
 
class ColorDescriptor:
	def __init__(self, bins):
		# store the number of bins for the 3D histogram
		self.bins = bins
 
	def describe(self, image):
		# convert the image to the HSV color space and initialize
		# the features used to quantify the image
		image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
		features = []
 
		# grab the dimensions and compute the center of the image
		(h, w) = image.shape[:2]
		(cX, cY) = (int(w * 0.5), int(h * 0.5))

我们将从导入我们需要的Python包开始。我们将使用NumPy进行数值处理,使用cv2进行OpenCV绑定,并使用imutils检查OpenCV版本。然后在第6行定义ColorDescriptor类。这个类将封装从图像中提取3D HSV颜色直方图所需的所有逻辑。ColorDescriptor的_init__方法只接受一个参数bin,它是颜色直方图的bin数量。然后我们可以在第11行定义description方法。这个方法需要一个图像,也就是我们要描述的图像。在描述方法的内部,我们将从RGB颜色空间(或者更确切地说,BGR颜色空间;OpenCV将RGB图像表示为NumPy数组,但顺序相反)到HSV颜色空间,然后初始化我们的特性列表以量化和表示ourimage。第18行和第19行简单地获取图像的维数并计算中心(x, y)坐标。所以现在艰苦的工作开始了。我们不计算整个图像的三维HSV颜色直方图,而是计算图像不同区域的三维HSV颜色直方图。使用基于区域的直方图而不是全局直方图可以让我们模拟颜色分布中的局部。例如,请看下面这张图片:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 14: 查询图像实例.

在这张照片中,我们可以清楚地看到图片顶部的蓝天和底部的沙滩。使用全局直方图,我们将无法确定图像中蓝色出现的位置和棕色沙粒出现的位置。相反,我们只知道存在一定比例的蓝色和一定比例的棕色。为了解决这个问题,我们可以计算图像区域的颜色直方图:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 15: 把我们的图像分成5个不同的部分的例子。

对于图像描述符,我们将图像分为五个不同的区域:(1)左上角,(2)右上角,(3)右下角,(4)左下角,最后(5)图像的中心。利用这些地区我们会能够模仿一种原油的定位,能够代表我们的海滩上方的形象在左上的深浅的蓝色的天空和右上的角落,棕色砂在左下角和右下角的角落,然后结合蓝天和布朗在中心地区的沙子。也就是说,下面是创建基于区域的颜色描述符的代码:

Python

		# divide the image into four rectangles/segments (top-left,
		# top-right, bottom-right, bottom-left)
		segments = [(0, cX, 0, cY), (cX, w, 0, cY), (cX, w, cY, h),
			(0, cX, cY, h)]
 
		# construct an elliptical mask representing the center of the
		# image
		(axesX, axesY) = (int(w * 0.75) // 2, int(h * 0.75) // 2)
		ellipMask = np.zeros(image.shape[:2], dtype = "uint8")
		cv2.ellipse(ellipMask, (cX, cY), (axesX, axesY), 0, 0, 360, 255, -1)
 
		# loop over the segments
		for (startX, endX, startY, endY) in segments:
			# construct a mask for each corner of the image, subtracting
			# the elliptical center from it
			cornerMask = np.zeros(image.shape[:2], dtype = "uint8")
			cv2.rectangle(cornerMask, (startX, startY), (endX, endY), 255, -1)
			cornerMask = cv2.subtract(cornerMask, ellipMask)
 
			# extract a color histogram from the image, then update the
			# feature vector
			hist = self.histogram(image, cornerMask)
			features.extend(hist)
 
		# extract a color histogram from the elliptical region and
		# update the feature vector
		hist = self.histogram(image, ellipMask)
		features.extend(hist)
 
		# return the feature vector
		return features

第23行和第24行分别定义了左上角、右上角、右下角和左下角区域的索引。然后,我们需要构造一个椭圆来表示图像的中心区域。我们将通过在第28行定义一个椭圆半径,它是图像宽度和高度的75%来实现这一点。然后初始化一个空白图像(用0填充以表示黑色背景),其尺寸与我们想在第29行描述的图像相同。最后,让我们使用cv2.ellipse 函数在第30行上画出实际的椭圆。这个函数需要八个不同的参数:

1. ellipMask: 我们想要绘制椭圆的图像。我们将使用掩码的概念,稍后我将对此进行讨论。

2. (cX, cY): 表示图像中心(x, y)坐标的2元组。

3. (axesX, axesY): 表示椭圆轴线长度的二元组。在本例中,椭圆将拉伸到我们所描述的图像的宽度和高度的75%。

4. 0: 椭圆的旋转。在这种情况下,不需要旋转,所以我们提供一个0度的值。

5. 0: 椭圆的起始角。

6. 360: 椭圆的结束角。查看前面的参数,这表明我们将绘制一个从0到360度的椭圆(一个完整的圆)。

7. 255: 椭圆的颜色。255的值表示白色,这意味着我们的椭圆将在黑色背景上绘制为白色。

8. -1: 椭圆的边框大小。提供一个正整数r将绘制一个大小为r像素的边框。为r提供一个负值将填充椭圆。

然后为第36行上的每个角掩模分配内存,在第37行上绘制一个表示图像角的白色矩形,然后从第38行上的矩形中减去中心椭圆。如果我们要使这个在角段上循环的过程动画化它看起来会像这样:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 16: 为要提取特征的图像的每个区域构造掩码。

正如这个动画所示,我们分别检查每个角段,在每次迭代时从矩形中删除椭圆的中心。你可能会想,难道我们不应该从图像中提取颜色直方图吗?为什么要做这些伪装?好问题。原因是我们需要掩码来指示OpenCV直方图函数从哪里提取颜色直方图。记住,我们的目标是分别描述这些部分。表示这些段的最有效方法是使用掩码。直方图计算中只包括(x, y)-在掩模中具有对应(x, y)位置的(x, y)坐标,其像素值为白色(255)。如果遮罩中(x, y)坐标的像素值为black(0),则会忽略它。要重申在直方图中只包含具有相应掩码值为白色的像素的概念,请看下面的动画:

用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 17: 将掩码区域应用于图像。注意,如果左边图像中的像素在右边图像中具有相应的白色掩码值,则只显示左边图像中的像素。

如您所见,直方图计算中只包含图像掩码区域的像素。现在明白了吧?现在,对于我们的每一个段,我们调用第42行上的直方图方法,用我们想要提取特征的图像作为第一个参数来提取颜色直方图,用表示我们想要描述的区域的掩码作为第二个参数来提取颜色直方图。然后,直方图方法返回表示当前区域的颜色直方图,并将其添加到特性列表中。第47和48行提取中心(椭圆)区域的颜色直方图,并更新特征列表a。最后,第51行将我们的特征向量返回给调用函数。现在,让我们快速看看实际的直方图方法:

Python

	def histogram(self, image, mask):
		# extract a 3D color histogram from the masked region of the
		# image, using the supplied number of bins per channel
		hist = cv2.calcHist([image], [0, 1, 2], mask, self.bins,
			[0, 180, 0, 256, 0, 256])
 
		# normalize the histogram if we are using OpenCV 2.4
		if imutils.is_cv2():
			hist = cv2.normalize(hist).flatten()
 
		# otherwise handle for OpenCV 3+
		else:
			hist = cv2.normalize(hist, hist).flatten()
 
		# return the histogram
		return hist

直方图方法需要两个参数:第一个参数是要描述的图像,第二个参数是表示要描述的图像区域的掩码。通过调用cv2,在第56行和第57行处理图像掩码区域的直方图。使用我们的构造函数提供的桶数calcHist。我们的颜色直方图在第61行或第65行进行标准化(取决于OpenCV版本),以获得尺度不变性。这意味着如果我们计算两个相同图像的颜色直方图,除了其中一个比另一个大50%,我们的颜色直方图将(几乎)相同。将颜色直方图标准化是非常重要的,这样每个直方图都由特定bin的相对百分比计数表示,而不是每个bin的整数计数。再次强调,执行这种标准化将确保具有相似内容但维度显著不同的图像在应用相似性函数后仍然是相似的。最后,将标准化的3D HSV颜色直方图返回到第68行上的调用函数。

步骤2:从图像数据中提取特征

现在我们已经定义了图像描述符,我们可以继续到步骤2,并从数据集中的每个图像中提取特征(即颜色直方图)。提取特征并将其存储在持久存储上的过程通常称为"索引"。

让我们继续深入研究一些代码来索引我们的度假照片数据集。打开一个新文件,命名为index.py,让我们得到索引:

Python

# import the necessary packages
from pyimagesearch.colordescriptor import ColorDescriptor
import argparse
import glob
import cv2
 
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required = True,
	help = "Path to the directory that contains the images to be indexed")
ap.add_argument("-i", "--index", required = True,
	help = "Path to where the computed index will be stored")
args = vars(ap.parse_args())
 
# initialize the color descriptor
cd = ColorDescriptor((8, 12, 3))

我们将从导入我们需要的包开始。您将记得第1步中的ColorDescriptor类,出于组织目的,我决定将它放在pyimagesearch模块中。我们还需要argparse来解析命令行参数,glob来获取到图像的文件路径,以及cv2来绑定OpenCV。解析命令行参数是在第8-13行进行的。我们将需要两个开关,—dataset,它是到我们的假期照片目录的路径,—index,它是包含图像文件名和与每个图像相关的特性的输出CSV文件。最后,我们在第16行使用8个色调箱、12个饱和度箱和3个值箱初始化ColorDescriptor。现在一切都初始化了,我们可以从数据集中提取特性:

Python

# open the output index file for writing
output = open(args["index"], "w")
 
# use glob to grab the image paths and loop over them
for imagePath in glob.glob(args["dataset"] + "/*.png"):
	# extract the image ID (i.e. the unique filename) from the image
	# path and load the image itself
	imageID = imagePath[imagePath.rfind("/") + 1:]
	image = cv2.imread(imagePath)
 
	# describe the image
	features = cd.describe(image)
 
	# write the features to file
	features = [str(f) for f in features]
	output.write("%s,%sn" % (imageID, ",".join(features)))
 
# close the index file
output.close()

让我们打开输出文件,在第19行进行编写,然后在第22行循环遍历数据集中的所有图像。对于每个图像,我们将提取一个imageID,它只是图像的文件名。对于这个示例搜索引擎,我们将假设所有文件名都是惟一的,但是我们同样可以为每个图像生成一个UUID。然后我们将在第26行从磁盘加载图像。现在已经加载了图像,让我们继续应用图像描述符并从第29行图像中提取特性。ColorDescriptor的describe方法返回一个浮点值列表,用于表示和量化图像。这个数字列表或特征向量包含我们在步骤1中描述的5个图像区域的每个表示。每个部分都用8 x 12 x 3 = 288个条目的直方图表示。给定5个元素,我们的整体特征向量是5x288 = 1440维。因此,每幅图像都用1440个数字进行量化和表示。第32行和第33行简单地将图像的文件名及其相关的特征向量写入文件。要索引我们的度假照片数据集,打开一个shell并发出以下命令:

Shell

$ python index.py --dataset dataset --index index.csv

这个脚本的运行时间不会超过几秒钟。完成后,您将拥有一个新文件index.csv。使用您最喜欢的文本编辑器打开这个文件并查看其中的内容。您将看到,对于.csv文件中的每一行,第一个条目是文件名,后面跟着一个数字列表。这些数字是你的特征向量,用来表示和量化图像。在索引上运行wc,可以看到我们已经成功地为805张图像的数据集建立了索引:

Shell

$ wc -l index.csv
 805 index.csv

步骤3:搜索Step 3: The Searcher

现在我们已经从数据集中提取了特征,我们需要一个方法来比较这些特征的相似性。这就是第3步的作用—我们现在准备创建一个类来定义两个图像之间的实际相似性度量。

创建一个新文件,命名为 searcher.py 看看有什么神奇的事情发生:

Python

# import the necessary packages
import numpy as np
import csv
 
class Searcher:
	def __init__(self, indexPath):
		# store our index path
		self.indexPath = indexPath
 
	def search(self, queryFeatures, limit = 10):
		# initialize our dictionary of results
		results = {}

我们将继续导入NumPy进行数值处理,并导入csv以方便解析index.csv文件。在此基础上,让我们在第5行定义Searcher类。搜索器的构造函数只需要一个参数indexPath,它是index.csv文件位于磁盘上的路径。为了实际执行搜索,我们将调用第10行中的search方法。该方法将获取两个参数,即从查询图像中提取的queryFeatures(即我们将提交给CBIR系统并要求与之相似的图像),并限制返回的结果的最大数量。最后,我们在第12行初始化结果字典。在这种情况下,dictionary是一种很好的数据类型,因为它允许我们使用给定图像的(惟一的)imageID作为键,并使用查询的相似性作为值。好了,注意这里。这就是奇迹发生的地方:

Python

		# open the index file for reading
		with open(self.indexPath) as f:
			# initialize the CSV reader
			reader = csv.reader(f)
 
			# loop over the rows in the index
			for row in reader:
				# parse out the image ID and features, then compute the
				# chi-squared distance between the features in our index
				# and our query features
				features = [float(x) for x in row[1:]]
				d = self.chi2_distance(features, queryFeatures)
 
				# now that we have the distance between the two feature
				# vectors, we can udpate the results dictionary -- the
				# key is the current image ID in the index and the
				# value is the distance we just computed, representing
				# how 'similar' the image in the index is to our query
				results[row[0]] = d
 
			# close the reader
			f.close()
 
		# sort our results, so that the smaller distances (i.e. the
		# more relevant images are at the front of the list)
		results = sorted([(v, k) for (k, v) in results.items()])
 
		# return our (limited) results
		return results[:limit]

我们在第15行打开index.csv文件,在第17行获取CSV阅读器的句柄,然后在第20行开始循环遍历index.csv文件的每一行。对于每一行,我们提取与索引图像关联的颜色直方图,然后使用chi2_distance(第25行)将其与查询图像特性进行比较,我将在稍后定义该特性。我们的结果字典在第32行更新,使用惟一的图像文件名作为键,查询图像与索引图像的相似性作为值。最后,我们所要做的就是按照相似度值的升序对结果字典进行排序。卡方相似度为0的图像将被视为彼此相同。随着卡方相似度值的增大,图像之间的相似度降低。说到卡方相似度,我们来定义这个函数:

Python

	def chi2_distance(self, histA, histB, eps = 1e-10):
		# compute the chi-squared distance
		d = 0.5 * np.sum([((a - b) ** 2) / (a + b + eps)
			for (a, b) in zip(histA, histB)])
 
		# return the chi-squared distance
		return d

我们的chi2_distance函数需要两个参数,这两个参数就是我们要比较的相似性的两个直方图。一个可选的eps值用于防止零除错误。该函数的名称来源于皮尔逊s卡方检验统计量,该统计量用于比较离散概率分布。由于我们在比较颜色直方图,而颜色直方图的定义是概率分布,所以卡方函数是一个很好的选择。一般来说,大方柱和小方柱之间的区别不那么重要,应该这样加权这就是卡方距离函数的作用。你还和我们在一起吗?我们快到了,我保证。最后一步实际上是最简单的,它只是一个将所有部件粘合在一起的驱动程序。

步骤4:执行一个搜索

实际上,它只是一个导入我们前面定义的所有包的驱动程序,并将它们相互结合使用来构建一个完整的基于内容的图像检索系统。

打开最后一个文件,命名为search.py:

Python

# import the necessary packages
from pyimagesearch.colordescriptor import ColorDescriptor
from pyimagesearch.searcher import Searcher
import argparse
import cv2
 
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--index", required = True,
	help = "Path to where the computed index will be stored")
ap.add_argument("-q", "--query", required = True,
	help = "Path to the query image")
ap.add_argument("-r", "--result-path", required = True,
	help = "Path to the result path")
args = vars(ap.parse_args())
 
# initialize the image descriptor
cd = ColorDescriptor((8, 12, 3))

我们要做的第一件事是导入必要的包。我们将从步骤1导入colordescriptor,以便从查询图像中提取特性。我们还将导入我们在步骤3中定义的搜索器以便我们能够执行实际的搜索。argparse和cv2包完成了导入。然后在第8-15行解析命令行参数。我们需要一个——index,它是index.csv文件所在的路径。我们还需要一个——query,它是到查询映像的路径。此图像将与索引中的每个图像进行比较。我们的目标是在索引中找到与查询图像相似的图像。当您进入谷歌并输入术语Python OpenCV教程时,您会发现搜索结果中包含与学习Python和OpenCV相关的信息。同样,如果我们正在为我们的度假照片构建一个图像搜索引擎,并提交一张蓝色海洋和白色浮云上的帆船图片,我们希望从图像搜索引擎中返回类似的海洋视图图像。然后,我们将请求一个——result-path,它是到我们的假期照片数据集的路径。我们需要这个开关,因为我们需要向用户显示实际的结果图像。最后,我们在第18行使用与索引步骤中相同的参数初始化图像描述符。如果我们的目的是比较图像的相似性(确实如此),那么将颜色直方图中的箱子数量从索引改为搜索是没有意义的。简单地说:在步骤4中使用与步骤3中相同数量的箱子来处理颜色直方图。这将确保您的图像以一致的方式描述,从而具有可比性。好了,现在来执行实际的搜索:

Python

# load the query image and describe it
query = cv2.imread(args["query"])
features = cd.describe(query)
 
# perform the search
searcher = Searcher(args["index"])
results = searcher.search(features)
 
# display the query
cv2.imshow("Query", query)
 
# loop over the results
for (score, resultID) in results:
	# load the result image and display it
	result = cv2.imread(args["result_path"] + "/" + resultID)
	cv2.imshow("Result", result)
	cv2.waitKey(0)

我们在第21行从磁盘加载查询图像,并在第22行从中提取特性。然后使用从查询图像中提取的特性在第25行和第26行执行搜索,返回我们的排序结果列表。从这里开始,我们需要做的就是向用户显示结果。我们在第29行向用户显示查询图像。然后在第32-36行循环搜索结果,并将它们显示在屏幕上。经过这么多工作,我相信你已经准备好看到这个系统的运行了,是吗? 继续读下去,我们所有的努力都会有回报的。

看看我们构建的CBIR系统表现怎么样

打开终端,导航到代码所在的目录,并发出以下命令:

Shell

$ python search.py --index index.csv --query queries/108100.png --result-path dataset
用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 18: 搜索我们的假期图像数据集的图片金字塔和埃及。

您将看到的第一个图像是我们的查询图像的埃及金字塔。我们的目标是在数据集中找到相似的图像。正如你所看到的,我们已经清楚地找到了我们访问金字塔时数据集的其他照片。

我们还参观了埃及的其他地区。让我们尝试另一个查询图像:

Shell

$ python search.py --index index.csv --query queries/115100.png --result-path dataset
用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 19: 我们的图像搜索引擎的结果为埃及其他地区。请注意,蓝色的天空始终出现在搜索结果中。

请务必密切关注我们的查询图像图像。请注意,在图像的上部区域,天空是一种明亮的蓝色阴影。注意我们在图片的底部和中心有棕色和棕色的沙漠和建筑。果然,在我们的结果中,返回给我们的图像的上方区域是蓝天,下方是棕黄色的沙漠和建筑物。这是因为我们在本文前面详细介绍了基于区域的颜色直方图描述符。通过使用这个图像描述符,我们已经能够执行一种粗略的本地化形式,为颜色直方图提供关于图像中像素强度发生在何处的提示。接下来的假期我们在海滩停了下来。执行以下命令搜索海滩照片

Shell

$ python search.py --index index.csv --query queries/103300.png --result-path dataset
用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 20: 使用OpenCV构建的基于内容的图像检索系统在数据集中查找海滩图像。

请注意前三个结果是如何来自海滩之旅中完全相同的位置的。其余的结果图像包含蓝色阴影。

当然,没有浮渣潜水的海滩之旅是不完整的:

Shell

$ python search.py --index index.csv --query queries/103100.png --result-path dataset
用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 21: 同样,我们的图像搜索引擎能够返回相关的结果。因此,时间,水下探险

这次搜索的结果尤其令人印象深刻。排名前五的结果都来自于同一条鱼,而排名前十的结果中,除了一条以外,其余都来自于水下游。

经过漫长的一天,终于到了看日落的时间了:

Shell

$ python search.py --index index.csv --query queries/127502.png --result-path dataset
用Python和OpenCV构建属于自己的图像搜索引擎

 

Figure 22: 我们的OpenCV图像搜索引擎能够在我们的度假照片数据集中找到日落的图像。

这些搜索结果也相当不错——返回的所有图像都是黄昏时分的日落。好了! 你的第一个图像搜索引擎搞定了。

总结

在这篇博客文章中,我们探讨了如何构建一个图像搜索引擎,使我们的度假照片可搜索。

我们使用颜色直方图来描述照片的颜色分布。然后,我们使用颜色描述符为数据集建立索引,从数据集中的每个图像中提取颜色直方图。

为了比较图像,我们使用了卡方距离,这是比较离散概率分布时的常用选择。

在此基础上,我们实现了接受查询图像并返回相关结果的必要逻辑。

下一步

那么接下来的步骤是什么呢?

正如您所看到的,与我们的图像搜索引擎交互的唯一方法是通过命令行—这不是很有吸引力。

在下一篇文章中,我们将探索如何在Python web框架中包装我们的图像搜索引擎,使其使用起来更简单、更性感。

原文地址:https://www.pyimagesearch.com/2014/12/01/complete-guide-building-image-search-engine-python-opencv/

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>