百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

使用Python构建您的第一个图像搜索引擎

toyiye 2024-05-25 20:12 29 浏览 0 评论

在本文中,我们将使用(可以说)最基本的图像描述符之一(颜色直方图)来量化和描述我们的图片 。

我们的数据集中共有25个不同的图像,每个类别有5个。

我们要做的第一件事是索引数据集中的25个图像。索引(index)是通过使用图像描述符从每个图像中提取特征来量化我们的数据集的过程,同时将我们得到的特征存储以供以后使用(例如执行搜索)

图像描述符定义了我们如何量化图像,因此从图像中提取特征被称为描述图像(describing an image)。图像描述符的输出是特征向量,是图像本身的抽象。简而言之,它是用于表示图像的数字列表。

可以使用距离度量来比较两个特征向量。距离度量用于通过检查两个特征向量之间的距离来确定两个图像的“相似”程度。在图像搜索引擎的情况下,我们给脚本提供一个查询图像,并要求它根据图像与查询的相关性对索引中的图像进行排序。

这样想吧。当您访问Google并在搜索框中输入“指环王”时,您希望Google向您返回与Tolkien的图书和电影特许经营相关的网页。类似地,如果我们给图像搜索引擎提供我们的查询图像,我们希望它返回与图像内容相关的图像——因此,我们有时将图像搜索引擎称为学术界中更常见的基于内容的图像检索(CBIR)系统。

我们的图像搜索引擎的目的:给定来自五个不同类别之一的查询图像,在前10个结果中返回类别的相应图像。

接下来让我们建立我们的Image Search Engine

The 4 Steps to Building an Image Search Engine

  1. 定义描述符:你要使用什么类型的描述符?
  2. 索引我们的数据集:将描述符应用于数据集中的每个图像,提取一系列特征。
  3. 定义我们的相似度量:你将如何定义两个图像的“相似”程度? 您可能会使用某种距离指标。常见的选择包括Euclidean,Cityblock(Manhattan),余弦和卡方等等。
  4. 搜索:要执行搜索,先将描述符应用于我们要查询图像,然后询问距离指标,以便对索引中的图像与查询图像的相似程度进行排名。通过相似性对结果进行排序,然后检查它们。

Step1:The Descriptor——A 3D RGB Color Histogram

我们的图像描述符是RGB颜色空间中的3D颜色直方图,每个红色,绿色和蓝色通道有8个区间(bins)。

解释3D直方图的最佳方法是使用连接AND(conjunctive AND)。该图像描述符将询问给定图像有多少红色像素具有落入bin#1和多少绿色像素落入bin#1的以及有多少蓝色像素落入bin#1。对于每个bins的组合将重复该过程; 但是,它将以计算有效的方式完成。

当计算具有8个bins的3D直方图时,OpenCV将特征向量存储为(8,8,8)数组。我们简单地将它展平并重塑为(512,)。一旦它被展平(flattened),我们就可以轻松地将特征向量进行比较以获得相似性。

rgbHistogram.py

import imutils
import cv2 
class RGBHistogram:
 def __init__(self,bins):
 # store the number of bins the histogram will use
 self.bins = bins
 def describe(self,image):
 # compute a 3D histogram in the RGB colorspace,
 # then normalize the histogram so that images
 # with the same content, but either scaled larger
 # or smaller will have (roughly) the same histogram
 hist = cv2.calcHist([image],[0,1,2],None,self.bins,[0,256,0,256,0,256])
 # normalize with openCV 2.4
 if imutils.is_cv2():
 hist = cv2.normalize(hist)
 # otherwise normalize with OpenCV 3+
 else:
 hist = cv2.normalize(hist,hist)
 # return out 3D histogram as a flattened array
 return hist.flatten()

如您所见,我已经定义了RGBHistogram类。我倾向于将我的图像描述符定义为类而不是函数。这是因为您很少单独从单个图像中提取特征。您改为从整个图像数据集中提取特征。此外,您希望从所有图像中提取的特征使用相同的参数——在这种情况下,是直方图的bins数目。如果您打算比较它们的相似性,从一个图像中使用32个bins来提取特征,而另外一个图像使用128个bins来提取特征,最后来比较它们的相似性是没有意义的。

这里我定义了RGBHistogram的构造函数。我们需要的唯一参数是直方图中每个通道的bins数。同样,这就是为什么我更喜欢使用类而不是图像描述符的函数——通过在构造函数中放置相关参数,可以确保为每个图像使用相同的参数。

接下来是describe方法,它用于“描述”图像并返回特征向量

使用cv2.calcHist函数,我们提取实际的3D RGB直方图(或实际上是BGR,因为OpenCV将图像存储为NumPy数组,但通道的顺序相反)。我们假设self.bins是三个整数的列表,指定每个通道的bin数。

重要的是我们根据像素数量对直方图进行标准化。如果我们使用图像的原始(整数)像素计数,然后将其缩小50%并再次描述它,我们将为相同的图像提供两个不同的特征向量。在大多数情况下,您希望避免这种情况。我们通过将原始整数像素计数转换为实值百分比来获得尺度不变性。例如,我们不说bin#1中有120个像素,而是说bin#1中有20%的像素。同样,通过使用像素计数的百分比而不是原始的整数像素计数,我们可以确保两个相同的图像(仅在大小上不同)将具有(大致)相同的特征向量。

当计算3D直方图时,直方图将表示为具有(N,N,N)个bins的NumPy数组。为了更容易地计算直方图之间的距离,我们简单地将该直方图展平为具有(N ** 3,)的形状。例如:当我们实例化RGBHistogram时,每个通道将使用8个bin。没有展平(flatten)我们的直方图,形状将是(8,8,8)。但是通过展平它,形状变为(512,)。

现在我们已经定义了图像描述符,接下来可以进行数据集的索引处理。

Step2:Indexing our Dataset

前面我们已经确定我们的图像描述符是3D RGB直方图。下一步是将我们的图像描述符应用于数据集中的每个图像。

这仅仅意味着我们将遍历我们的25个图像数据集,从每个图像中提取3D RGB直方图,将特征存储在字典中,并将字典写入文件。

index.py

from rgbHistogram import RGBHistogram
from imutils.paths import list_images
import os.path
import argparse
import pickle
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 index dictionary to store our our quantifed
# images, with the 'key' of the dictionary being the image
# filename and the 'value' our computed features
index = {}

我们将使用cPickle将索引转储到磁盘。我们将使用list_images来获取我们要索引的图像的路径。

--dataset参数是我们的图像存储在磁盘上的路径,而--index选项是我们在计算索引后存储索引的路径。

最后,我们将初始化索引为字典类型。字典的key是图像文件名。我们假设所有文件名都是唯一的,事实上,对于这个数据集,它们是唯一的。字典的value将是图像的计算直方图。

# initialize our image descriptor -- a 3D RGB histogram with
# 8 bins per channel
desc = RGBHistogram([8, 8, 8])

这里我们实例化我们的RGBHistogram。同样,我们将分别为红色,绿色和蓝色通道使用8个bins。

# use list_images to grab the image paths and loop over them
for imagePath in list_images(args["dataset"]):
 # extract our unique image ID(here is our image filename)
 j, k = os.path.split(imagePath)
 # load the image, describe it using our RGB histogram
 # descriptor, and update the index
 image = cv2.imread(imagePath)
 features = desc.describe(image)
 index[k] = features

我们使用list_images来抓取图像路径并开始遍历我们的数据集。我们提取图像的名字作为我们字典的key。因为数据集中的所有文件名都是唯一的,因此文件名本身就足以作为key。然后将图像从磁盘加载,然后我们使用RGBHistogram从图像中提取直方图。然后将直方图存储在index中。

# we are now done indexing our image -- now we can write our
# index to disk 
f = open(args["index"],'wb')
f.write(pickle.dumps(index))
f.close()
# show how many images we indexed 
print("[INFO] done... indexed {} images".format(len(index)))

现在已经计算了我们的索引,我们将它写入磁盘,以便我们以后可以使用它进行搜索。

要为图像搜索引擎编制索引,只需在终端中输入以下内容即可

python index.py -d images --index index.cpickle

Step3:The Search

我们的磁盘上有我们的index,接下来准备进行搜索。

我们如何比较两个特征向量以及我们如何确定它们的相似程度?先看代码的实现。

searcher.py

import numpy as np
class Searcher:
 def __init__(self, index):
 # store our index of images
 self.index = index
 def search(self, queryFeatures):
 # initialize our dictionary of results
 results = {}
 # loop over the index
 for (k,features) in self.index.items():
 # compute the chi-squared distance between the features
 # in our index and our query features -- using the
 # chi-squared distance which is normally used in the
 # computer vision field to compare histograms
 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[k] = d
 # 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 results
 return results
 def chi2_distance(self,histA,histB,eps=1e-10):
 # conpute 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

我们首先定义Searcher类和一个带有单个参数的构造函数——index。假定该index是我们在index步骤中写入文件的index字典。

我们定义一个字典来存储我们的结果。字典的key是图像文件名,value是给定图像与查询图像的相似程度。

这是我们实际执行searching的部分。我们遍历索引中的图像文件名和相应特征。然后我们使用卡方距离(Chi-square distance)来比较我们的颜色直方图。然后将计算的距离存储在结果字典中,指示两个图像彼此有多相似。最后结果按照相关性(卡方距离越小,越相关)进行排序并返回。

最后,我们定义用于比较两个直方图的卡方距离函数。一般来说,大垃圾箱与小垃圾箱之间的差异不那么重要,应该按此加权。这正是卡方距离的作用。我们提供epsilon以避免那些讨厌的“除以零”错误。如果图像的特征向量的卡方距离为零,则认为图像是相同的。距离越大,它们就越不相似。

Step4:Performing a Search

接下来我们从磁盘加载图像并执行搜索:

search.py

from searcher import Searcher
import numpy as np
import argparse
import os
import pickle
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 we just indexed")
ap.add_argument("-i", "--index", required = True,
 help = "Path to where we stored our index")
args = vars(ap.parse_args())
# load the index and initialize our searcher
index = pickle.loads(open(args["index"],'rb').read())
searcher = Searcher(index)

我们使用cPickle从磁盘加载index并初始化我们的Searcher。

# loop over images in the index -- we will use each one as
# a query image
for (query,queryFeatures) in index.items():
 # perform the search using the current query
 results = searcher.search(queryFeatures)
 # load the query image and display it
 path = os.path.join(args["dataset"],query)
 queryImage = cv2.imread(path)
 cv2.imshow("Query",queryImage)
 print("query: {}".format(query))
 # initialize the two montages to display our results --
 # we have a total of 25 images in the index, but let's only
 # display the top 10 results; 5 images per montage, with
 # images that are 400x166 pixels
 montageA = np.zeros((166 * 5,400,3),dtype="uint8")
 montageB = np.zeros((166 * 5, 400, 3), dtype="uint8")
 # loop over the top ten results
 for j in range(0,10):
 # grab the result (we are using row-major order) and
 # load the result image
 (score,imageName) = results[j]
 path = os.path.join(args["dataset"],imageName)
 result = cv2.imread(path)
 print("\t{} . {} : {:.3f}".format(j+1,imageName,score))
 # check to see if the first montage should be used
 if j < 5:
 montageA[j * 166:(j + 1) * 166,:] = result
 # otherwise , the second montage should be used
 else:
 montageB[(j - 5) * 166:((j - 5) + 1) * 166,:] = result
 # show the results
 cv2.imshow("Results 1-5",montageA)
 cv2.imshow("Result 6-10",montageB)
 cv2.waitKey(0)

我们将把index中的每个图像视为一个query,看看我们得到的结果。通常,查询是外部的而不是数据集的一部分,但在我们开始之前,让我们只执行一些示例搜索。

我们将当前图像视作为query并执行searh方法。

然后加载并显示我们的query图像。

为了显示前10个结果,我决定使用两个montage图像。第一个montage显示结果1-5,第二个montage显示结果6-10。最后显示我们search的results给用户。

执行脚本

python search.py --dataset images --index index.cpickle

最终结果:

query: Mordor-002.png
 1. Mordor-002.png : 0.000
 2. Mordor-004.png : 0.296
 3. Mordor-001.png : 0.532
 4. Mordor-003.png : 0.564
 5. Mordor-005.png : 0.711
 6. Goblin-002.png : 0.825
 7. Rivendell-002.png : 0.838
 8. Rivendell-004.png : 0.980
 9. Goblin-001.png : 0.994
 10. Rivendell-005.png : 0.996

Bonus: External Queries

截至目前,我只向您展示了如何使用索引中已有的图像执行搜索。 但显然,这不是所有图像搜索引擎的工作方式。 Google允许您上传自己的图片。我们为什么不能? 让我们看看我们如何使用尚未编入索引的图像执行搜索:

search_external.py

from rgbhistogram import RGBHistogram
from searcher import Searcher
import numpy as np
import argparse
import os
import pickle
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 we just indexed")
ap.add_argument("-i", "--index", required = True,
 help = "Path to where we stored our index")
ap.add_argument("-q", "--query", required = True,
 help = "Path to query image")
args = vars(ap.parse_args())
# load the query image and show it
queryImage = cv2.imread(args["query"])
cv2.imshow("Query",queryImage)
print("query: {}".format(args["query"]))
# describe the query in the same way that we did in
# index.py -- a 3D RGB histogram with 8 bins per channel
desc = RGBHistogram([8,8,8])
queryFeatures = desc.describe(queryImage)
# load the index perform the search
index = pickle.loads(open(args["index"],"rb").read())
searcher = Searcher(index)
results = searcher.search(queryFeatures)
# initialize the two montages to display our results --
# we have a total of 25 images in the index, but let's only
# display the top 10 results; 5 images per montage, with
# images that are 400x166 pixels
montageA = np.zeros((166 * 5, 400, 3), dtype = "uint8")
montageB = np.zeros((166 * 5, 400, 3), dtype = "uint8")
# loop over the top ten results
for j in range(0,10):
 # grab the result (we are using row-major order) and
 # load the result image
 (score,imageName) = results[j]
 path = os.path.join(args["dataset"],imageName)
 result = cv2.imread(path)
 print("\t{} . {} : {:.3f}".format(j+1,imageName,score))
 # check to see if the first montage should be used
 if j < 5:
 montageA[j * 166:(j + 1) * 166, :] = result
 # otherwise, the second montage should be used
 else:
 montageB[(j - 5) * 166:((j - 5) + 1) * 166, :] = result
# show the results
cv2.imshow("Results 1-5", montageA)
cv2.imshow("Results 6-10", montageB)
cv2.waitKey(0)

--query是我们将要query的图像的路径。然后载入我们的query image并显示出来。

使用与索引步骤中完全相同的bin数来实例化我们的RGBHistogram。然后,我们从查询图像中提取特征。

使用cPickle将我们的索引加载到磁盘上并执行搜索。最后显示我们的结果。

在query文件夹中有我们索引中没有的两张图片。这两个图像将是我们的查询。

执行我们的脚本:

python search_external.py --dataset images --index index.cpickle --query queries\rivendell-query.png

结果:

query: queries\rivendell-query.png
 1 . Rivendell-002.png : 0.195
 2 . Rivendell-004.png : 0.449
 3 . Rivendell-001.png : 0.643
 4 . Rivendell-005.png : 0.757
 5 . Rivendell-003.png : 0.769
 6 . Mordor-001.png : 0.809
 7 . Mordor-003.png : 0.858
 8 . Goblin-002.png : 0.875
 9 . Mordor-005.png : 0.894
 10 . Mordor-004.png : 0.909

觉得有用可以关注加收藏转发,完整代码和图集请私信我。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码