Goinggogogo 2018-03-21
我认为你们都看到了百度图片搜索,并问自己“它是如何工作的?”,所以今天我们将构建一个简单的脚本,用于在控制台内进行图像搜索。
对于这项任务,首先,我们需要了解什么是图像特征以及我们如何使用它。图像特征是一种简单的图像模式,基于此特征我们可以描述我们在图像上看到的内容。例如猫眼将成为猫的图像上的特征。特征在计算机视觉(而不仅仅是)中的主要作用是将视觉信息转换为向量空间。这给了我们对它们执行数学运算的可能性,例如找到相似的矢量(这导致我们到图像上的相似图像或对象)
从图像中获取特征有两种方法,第一种是图像描述符(白盒算法),第二种是神经网络(黑盒算法)。今天我们将使用第一种。
有许多特征提取算法,其中最流行的是SURF,ORB,SIFT,BRIEF。这些算法大部分基于图像梯度。
今天我们将使用KAZE描述符,因为它基于OpenCV库提供,而另一些则不是,只是为了简化安装。
所以,让我们来编写我们的功能exctractor:
import cv2
import numpy as np
import scipy
from scipy.misc import imread
import cPickle as pickle
import random
import os
import matplotlib.pyplot as plt
# Feature extractor
def extract_features(image_path, vector_size=32):
image = imread(image_path, mode="RGB")
try:
# Using KAZE, cause SIFT, ORB and other was moved to additional module
# which is adding addtional pain during install
alg = cv2.KAZE_create()
# Dinding image keypoints
kps = alg.detect(image)
# Getting first 32 of them.
# Number of keypoints is varies depend on image size and color pallet
# Sorting them based on keypoint response value(bigger is better)
kps = sorted(kps, key=lambda x: -x.response)[:vector_size]
# computing descriptors vector
kps, dsc = alg.compute(image, kps)
# Flatten all of them in one big vector - our feature vector
dsc = dsc.flatten()
# Making descriptor of same size
# Descriptor vector size is 64
needed_size = (vector_size * 64)
if dsc.size < needed_size:
# if we have less the 32 descriptors then just adding zeros at the
# end of our feature vector
dsc = np.concatenate([dsc, np.zeros(needed_size - dsc.size)])
except cv2.error as e:
print 'Error: ', e
return None
return dsc
def batch_extractor(images_path, pickled_db_path="features.pck"):
files = [os.path.join(images_path, p) for p in sorted(os.listdir(images_path))]
result = {}
for f in files:
print 'Extracting features from image %s' % f
name = f.split('/')[-1].lower()
result[name] = extract_features(f)
# saving all our feature vectors in pickled file
with open(pickled_db_path, 'w') as fp:
pickle.dump(result, fp)
OpenCV中的大多数特征提取算法都具有相同的界面,所以如果您想要使用SIFT,那么只需用SIFT_create替换KAZE_create即可。
因此,extract_features 首先检测图像上的关键点(本地图案的中心点)。它们的数量可以根据图像而不同,因此我们添加一些子句以使我们的特征向量总是相同的大小(这是计算所需要的,因为您不能比较不同维度的向量)
然后,我们基于我们的关键点构建向量描述符,每个描述符的大小为64,我们有32个,所以我们的特征向量是2048维。
batch_extractor只需在我们的所有图像中批量运行我们的特征提取器,并将特征矢量保存在腌制文件中以供进一步使用。
现在是时候建立我们的Matcher类,它将匹配我们的搜索图像和数据库中的图像。
class Matcher(object):
def __init__(self, pickled_db_path="features.pck"):
with open(pickled_db_path) as fp:
self.data = pickle.load(fp)
self.names = []
self.matrix = []
for k, v in self.data.iteritems():
self.names.append(k)
self.matrix.append(v)
self.matrix = np.array(self.matrix)
self.names = np.array(self.names)
def cos_cdist(self, vector):
# getting cosine distance between search image and images database
v = vector.reshape(1, -1)
return scipy.spatial.distance.cdist(self.matrix, v, 'cosine').reshape(-1)
def match(self, image_path, topn=5):
features = extract_features(image_path)
img_distances = self.cos_cdist(features)
# getting top 5 records
nearest_ids = np.argsort(img_distances)[:topn].tolist()
nearest_img_paths = self.names[nearest_ids].tolist()
return nearest_img_paths, img_distances[nearest_ids].tolist()
这里我们加载前一步的特征向量,并从它们中创建一个大矩阵,然后计算我们的搜索图像的特征向量和特征向量数据库之间的余弦距离,然后输出Top N结果。
当然,这仅仅是一个演示,对于制作使用更好的一些算法来快速计算数百万图像的余弦距离。我建议使用使用简单且速度相当快的Annoy Index(在1M图像中搜索约需2ms)
现在把它放在一起并运行
def show_img(path):
img = imread(path, mode="RGB")
plt.imshow(img)
plt.show()
def run():
images_path = 'resources/images/'
files = [os.path.join(images_path, p) for p in sorted(os.listdir(images_path))]
# getting 3 random images
sample = random.sample(files, 3)
batch_extractor(images_path)
ma = Matcher('features.pck')
for s in sample:
print 'Query image =========================================='
show_img(s)
names, match = ma.match(s, topn=3)
print 'Result images ========================================'
for i in range(3):
# we got cosine distance, less cosine distance between vectors
# more they similar, thus we subtruct it from 1 to get match value
print 'Match %s' % (1-match[i])
show_img(os.path.join(images_path, names[i]))
run()
当您运行此代码时,您会看到类似的图像并不总是与我们所了解的类似。这是因为这种算法是上下文无关的,所以他们更好地找到相同的图像,甚至修改过,但不相似。如果我们想找到上下文相似的图像,那么我们应该使用卷积神经网络,