实现一个简单的图像搜索引擎---实现

实现1:图片颜色直方图的获取(图像描述符)
图片由无数个像素点组成,每个像素点可用三原色表示也就是red,green,blue;
在元组的使用中,我们通常建立(r,g,b)三元组来表示一个像素,也就是所谓的rgb像素表示法。我们把图片所有的像素点都有这种方法来获取后,组成一个直方图。这个直方图就十分抽象地代表了目标图片。
图像描述符是RGB颜色空间中的3D颜色直方图,每个红色,绿色,蓝色通道有8个区间。在计算8个区间的3D直方图时,OpenCV会将特征向量存储为(8, 8, 8)数组。由于要计算相似性,需要将特征向量重塑成为一维的向量。
实现代码:

#rgbhistogram.py
import imutils
import cv2
class RGBHistogram:
   def __init__(self, bins):
           self.bins = bins
   def describe(self, image):
         hist = cv2.calcHist([image], [0, 1, 2],
         None, self.bins, [0, 256, 0, 256, 0, 256])
      # OpenCV 3+
      hist = cv2.normalize(hist,hist)
      return hist.flatten()`

第1、2行:导入了“OpenCV,imutils”包;
第3行:定义了一个直方图的类,命名为“RGBHistogram”来完成直方图的获取
第4、5:行是一个构造函数,用来创建直方图类的对象,实现对象的初始化;
第6行:定义了一个用来返回特征向量的函数命名为“describe”;
第7、8行:这里我们提取实际的3D RGB直方图(或实际上,BGR,因为OpenCV将图像存储为NumPy数组,但通道的顺序相反)。我们假设self.bins是一个三个整数的列表,指定每个通道的箱数。
第9行:调用OpenCV的normalize()进行归一化操作,防止数据溢出与错误数据的产生;
第10行:计算3D直方图时,直方图将表示为带有(N, N, N)区间的NumPy数组。为了更容易地计算直方图之间的距离,我们简单地将该直方图展平为具有的形状(N ** 3,)。示例:当我们实例化RGBHistogram时,每个通道将使用8个bin。没有展平我们的直方图,形状就会如此(8, 8, 8)。但是通过展平它,形状就变成了(512,)。

实现2:索引数据集
顾名思义,在此步骤中要实现对所有图片的遍历。在获取到描述符之后,要把图像的描述符应用到每个图像中,简单来说是将每个图像的颜色直方图提取后,存储在字典中,并将字典写入文件。

#index.py
from pyimagesearch.rgbhistogram import RGBHistogram
from imutils.paths import list_images
import argparse
import _pickle as cPickle    
import cv2
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())
index = {}
desc = RGBHistogram([8, 8, 8])
for imagePath in list_images(args["dataset"]):
   imagePath = imagePath.replace("\\","/")
   k = imagePath[imagePath.rfind("/") + 1:]
   image = cv2.imread(imagePath)
   features = desc.describe(image)
   index[k] = features
f = open(args["index"], "wb")
f.write(cPickle.dumps(index))  
f.close()
print("[INFO] done...indexed {} images".format(len(index)))

第1-5行输入必要的argparse、_pickle和OpenCV必要的包;
第6-11行规定了键盘输入的格式,–dataset 参数规定了图像存储的磁盘的路径,–index 参数规定了索引被调用后的存储路径;
第13行实例化RGBHistogram类将分别为红色,绿色和蓝色通道使用8个分档;
第14-19行用list_images来抓取图像路径并开始遍历我们的数据集,数据集中的所以文件名都是唯一的,因此文件名为键;
第21-23行图像从磁盘加载出来,使用RGBHistogram从图像中提取直方图,最后将直方图存储在索引中;
第24行定义输出的格式,其中format()用来获取…的值,并且最终返回给用户。

实验小测试:
在pycharm命令终端输入python index.py --dataset images --index index.cpickle
如果出现结果如下图,说明取得了阶段性胜利!
在这里插入图片描述
实现3:相似性的计算
此次用的相似性公式是卡方距离计算,将两个数值输入到公式当中计算出来的结果越小说明两者越相似。

#searcher.py
import numpy as np
class Searcher:
   def __init__(self, index):
      self.index = index
   def search(self, queryFeatures):
      results = {}
      for (k, features) in self.index.items():
         d = self.chi2_distance(features, queryFeatures)
		  results[k] = d
      results = sorted([(v, k) for (k, v) in results.items()])
      return results
   def chi2_distance(self, histA, histB, eps = 1e-10):
	d = 0.5 * np.sum([((a - b) ** 2) / (a + b + eps)
            for (a, b) in zip(histA, histB)])
	return d

第1行 导入必要的numpy包;
第2行定义一个搜索类,命名为Search;
第3、4行定义类的构造函数,用来调用搜索类时进行对象的初始化操作,必要时可以加上相应参数;
第5-9行 这是实际搜索发生的部分。遍历索引中的图像文件名和相应的功能。然后使用卡方距离来比较我们的颜色直方图。将计算的距离存储在results字典中,指示两个图像彼此有多相似。
第10、11行 按卡方距离从小到大排序,其目的是找出目标图像最相似的图像;
第12-15行这里定义用于比较两个直方图的卡方距离函数。一般来说,大箱与小箱之间的差异不太重要,应该按此加权。这正是卡方距离的作用。我们提供一个epsilon虚拟值来避免那些讨厌的“除以零”错误。如果图像的特征向量的卡方距离为零,则认为图像是相同的。距离越大,它们就越不相似。

#search.py
from pyimagesearch.searcher import Searcher
import numpy as np
import argparse
import os
import _pickle as cPickle
import cv2
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())
index = cPickle.loads(open(args["index"], "rb").read())
searcher = Searcher(index)
for (query, queryFeatures) in index.items():
   results = searcher.search(queryFeatures
   path = os.path.join(args["dataset"], query)
   queryImage = cv2.imread(path)
   cv2.imshow("Query", queryImage)
   print("query: {}".format(query))
   montageA = np.zeros((166 * 5, 400, 3), dtype = "uint8")
   montageB = np.zeros((166 * 5, 400, 3), dtype = "uint8")
   for j in range(0, 10):
      (score, imageName) = results[j]
      path = os.path.join(args["dataset"], imageName)
      result = cv2.imread(path)
      print("\t{}. {} : {:.3f}".format(j + 1, imageName, score))
      if j < 5:
         montageA[j * 166:(j + 1) * 166, :] = result
      else:
         montageB[(j - 5) * 166:((j - 5) + 1) * 166, :] = result
   cv2.imshow("Results 1-5", montageA)
   cv2.imshow("Results 6-10", montageB)
   cv2.waitKey(0)

第1-6行:导入我们需要的包。Searcher类存储在pyimagesearch模块中。
第7-12行 与索引步骤中相同的方式定义键盘输入的参数。
第13-14行 使用cPickle从磁盘加载索引并初始化我们的Searcher。
第15行:我们将把索引中的每个图像视为一个查询,看看我们得到的结果。通常,查询是 外部的,而不是数据集的一部分,但在我们开始之前,让我们只执行一些示例搜索。
第16行:这是进行实际搜索的地方。我们将当前图像视为查询并执行搜索。
第17-20行:加载并显示我们的查询图像。
第21、22行:定义显示框的大小
第23-31行:为了显示前10个结果,使用两个蒙太奇图像。第一个蒙太奇显示结果1-5,第二个蒙太奇显示结果6-10。
第32-34行: 搜索结果显示给用户。

代码测试2:
在pycharm命令终端输入python search.py --dataset images --index index.cpickle

在这里插入图片描述在这里插入图片描述
实现4:外部查询功能
根据项目的目标,最终需要对指定图像进行查询而不是所有图像的遍历。需要对键盘输入进行进一步的规格化修改。同时将要搜索的目标图像通过磁盘读出并展示出来。

#search_external.py
from pyimagesearch.rgbhistogram import RGBHistogram
from pyimagesearch.searcher import Searcher
import numpy as np
import argparse
import os
import _pickle as cPickle
import cv2
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())
queryImage = cv2.imread(args["query"])
cv2.imshow("Query", queryImage)
print("query: {}".format(args["query"]))
desc = RGBHistogram([8, 8, 8])
queryFeatures = desc.describe(queryImage)
index = cPickle.loads(open(args["index"], "rb").read())
searcher = Searcher(index)
results = searcher.search(queryFeatures)
montageA = np.zeros((166 * 3, 400, 3), dtype = "uint8")
montageB = np.zeros((166 * 3, 400, 3), dtype = "uint8")
for j in range(0, 6):  
   (score, imageName) = results[j]
   path = os.path.join(args["dataset"], imageName)
   result = cv2.imread(path)
   print("\t{}. {} : {:.3f}".format(j + 1, imageName, score))
      if j < 3:
      montageA[j * 166:(j + 1) * 166, :] = result
   else:
      montageB[(j - 3) * 166:((j - 3) + 1) * 166, :] = result
cv2.imshow("Results 1", montageA)
cv2.imshow("Results 2", montageB)
cv2.waitKey(0)

第1-7行 关键包numpy、argparse、os、_pickle、OpenCV的导入;
第8-15行 完成对键盘输入格式的限定;
第16-18行 对搜索的目标图像进行展示,以根据两者的结果进行对比;
第19、20行 对彩色直方图进行初始化操作;
第21-23行 对磁盘图像进行读取搜索;
第24-35行 与搜索部分类似,完成对图像以及数据的展示,其中输出格式中#输出:\t表示表格格式 {}表示要显示的位置 {:.3f}表示结果保留三位小数 ,format里的三个值分别对应三个{}里的值。

代码测试3:
在pycharm命令终端输入python search_external.py --dataset images --index index.cpickle --query queries/Lakers-001.png
在这里插入图片描述
在这里插入图片描述

小结:
自此,简单图像搜索引擎已经实现,具体的结构图如下:
在这里插入图片描述

PS:index.cpickle 文件不是代码,所以无法显示。
有兴趣的朋友可以留言免费获取该项目源码!

发布了29 篇原创文章 · 获赞 5 · 访问量 4616

猜你喜欢

转载自blog.csdn.net/guoyihaoguoyihao/article/details/96480267