OpenCV特征提取与图像检索实现(附代码)

543 篇文章 13 订阅



翻译 | AI科技大本营(ID:rgznai100)

参与 | 张蔚敏

审校 | reason_W


“拍立淘”“一键识花”“街景匹配”……不知道大家在使用这些神奇的功能的时候,有没有好奇过它们背后的技术原理?其实这些技术都离不开最基本的图像检索技术。本篇文章我们就将对这一技术的原理进行介绍,并通过一个简单的Python脚本来实现一个最基本的图像检索demo。




图像特征 


首先我们需要明白图像特征是什么以及它的使用方法。


图像特征是一种简单的图像模式,基于这种模式我们可以描述我们在图像上所看到的内容。 例如,在一张跟猫有关的图片中,猫咪的眼睛就可以作为这幅图像的特征。特征在(包括但不限于)计算机视觉中的主要作用是将视觉信息转换为向量空间表示。这种向量空间表示让我们可以利用数学运算对其进行处理,例如通过计算寻找相似向量(这可以用来寻找相似图像或图像中的相似目标)。


如何从图像中获取特征?


从图像中获取特征的方法有两种,第一种是通过提取图像描述符实现(白盒算法);第二种通过基于神经网络的方法实现(黑盒算法)。本文主要介绍第一种方法。


特征提取的算法有很多,最常用的有:SURF、ORB、SIFT、BRIEF等。这些算法大多是基于图像梯度的。为了简化安装需求,本教程使用的是KAZE描述符,因为其他描述符在python的基础OpenCV库中没有提供。


下面是特征提取器的实现代码:


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
       #此处为了简化安装步骤,使用KAZE,因为SIFT/ORB以及其他特征算子需要安
#装额外的模块
       alg = cv2.KAZE_create()
       # Finding image keypoints
       #寻找图像关键点
       kps = alg.detect(image)
       # Getting first 32 of them.
       #计算前32个
       # 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
       #描述符向量的大小为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
#如果少于32个描述符,则在特征向量后面补零
           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
# 将特征向量存于pickled 文件
   with open(pickled_db_path, 'w') as fp:
       pickle.dump(result, fp)


OpenCV中的大多数特征提取算法的python接口都相同,所以如果你想要使用SIFT特征,只需要用SIFT_create替换KAZE_create就行。


首先,程序会用extract_features检测图像上的关键点(局部模式的中心点)。 因为关键点数量随图像的不同有所不同,因此我们需要添加一些规则,以确保所得到的特征向量大小始终相同(这是因为在计算时,我们无法对维度不同的向量进行比较,所以必须保证相同的大小)。


然后是根据关键点构建向量描述符,每个描述符的大小为64,我们有32个这样的描述符,所以我们的特征向量是2048维。


batch_extractor是在所有的图像中批量运行特征提取器,并将特征向量保存在pickled文件中以供后续使用。


现在我们来建立类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
               # 获得前5个记录
           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()


这里要加载前一步得到的特征向量,并从它们中创建一个大矩阵,然后计算待搜索图像的特征向量和特征向量数据库之间的余弦距离,然后输出最近的前N个结果。


当然,这仅仅是一个demo,在实际计算中,还可以用一些算法来快速计算数百万图像间的余弦距离。你可以使用简单且运行速度相当快的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
           # 随机获取3张图

       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

#我们得到了余弦距离,向量之间的余弦距离越小表示它们越相似,因此我们从1中减去它以得到匹配值

               print 'Match %s' % (1-match[i])
               show_img(os.path.join(images_path, names[i]))
   run()


大家可以在我的 github上下载源码,或者在Google Colab上运行(Google Colab是一种提供GPU在线计算的免费服务):

https://colab.research.google.com/drive/1BwdSConGugBlGzPLLkXHTz2ahkdzEhQ9


总结


在运行上述代码的过程中,你可能会发现搜索到的相似图像并不总能达到我们想象中的那种相似程度。这是因为我们所用的这种算法是上下文无关(context-unaware)的,所以该算法在寻找相同(即使是被修改过的)图像方面表现更好,而不是在相似图像方面。如果是要寻找上下文相关的相似图像,那就要使用卷积神经网络了,我的下一篇文章会对这方面的知识进行详细介绍。


作者:Andrey Nikishaev

原文链接:https://towardsdatascience.com/feature-extraction-and-similar-image-search-with-opencv-for-newbies-3c59796bf774  





  • 10
    点赞
  • 156
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
OpenCV提供了cv2.findContours()函数来寻找图像的轮廓。该函数有三个参数:输入图像、轮廓检索模式和轮廓逼近方法。输出是一个包含轮廓的列表和一个包含层次结构的数组。 下面是一个简单的示例代码,演示如何使用cv2.findContours()函数来提取图像轮廓: ``` import cv2 # 读取输入图像 img = cv2.imread('input.jpg') # 将图像转换为灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 进行二值化处理 ret, thresh = cv2.threshold(gray, 127, 255, 0) # 寻找图像的轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓 cv2.drawContours(img, contours, -1, (0, 255, 0), 3) # 显示结果 cv2.imshow('Contours', img) cv2.waitKey(0) ``` 在上面的代码中,我们首先读取输入图像并将其转换为灰度图像。然后我们对灰度图像进行二值化处理。接下来,我们使用cv2.findContours()函数来找到图像的轮廓。最后,我们使用cv2.drawContours()函数来绘制轮廓,并将结果显示在屏幕上。 注意,cv2.findContours()函数的第二个参数指定轮廓检索模式。有四种模式可供选择:cv2.RETR_EXTERNAL、cv2.RETR_LIST、cv2.RETR_CCOMP和cv2.RETR_TREE。在本示例中,我们使用了cv2.RETR_TREE模式,它会返回所有轮廓,并且每个轮廓都有其自己的父子关系。 cv2.findContours()函数的第三个参数指定轮廓逼近方法。有三种方法可供选择:cv2.CHAIN_APPROX_NONE、cv2.CHAIN_APPROX_SIMPLE和cv2.CHAIN_APPROX_TC89_L1。在本示例中,我们使用了cv2.CHAIN_APPROX_SIMPLE方法,它会仅保留轮廓的端点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值