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

翻译 | AI科技大本营

参与 | 张蔚敏

审校 | 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

原文发布于微信公众号 - AI科技大本营(rgznai100)

原文发表时间:2018-03-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器之心

资源 | 如何通过CRF-RNN模型实现图像语义分割任务

选自GitHub 作者:Shuai Zheng等 机器之心编译 参与:蒋思源 本 Github 项目通过结合 CNN 和 CRF-RNN 模型实现图像的语义分割...

687150
来自专栏专知

【前沿】见人识面,TensorFlow实现人脸性别/年龄识别

【导读】近期,浙江大学学生Boyuan Jiang使用TensorFlow实现了一个人脸年龄和性别识别的工具,首先使用dlib来检测和对齐图片中的人脸,然后使用...

1.7K60
来自专栏机器之心

教程 | 如何利用TensorFlow.js部署简单的AI版「你画我猜」图像识别应用

我们将使用卷积神经网络(CNN)来识别不同类型的手绘图像。这个卷积神经网络将在 Quick Draw 数据集(https://github.com/google...

36540
来自专栏奇点大数据

理解LSTM一种递归神经网络(RNN)

1 递归神经网络结构 一个简单的传统神经网络结构如下图所示: ? 给他一些输入x0,x1,x2 … xt, 经过神经元作用之后得到一些对应的输出h0,h1,h2...

288100
来自专栏新智元

谷歌大脑开源TensorFuzz,自动Debug神经网络!

【新智元导读】众所周知,神经网络难以debug。谷歌大脑的Augustus Odena和Ian Goodfellow提出了一种新方法,能够自动Debug神经网络...

9430
来自专栏CVer

用OpenCV实现图像和视频神经风格迁移(含代码)

2015年,Gatsys等人在论文A Neural Algorithm of Artistic Style中提出了最初的神经风格迁移算法。2016年,Johns...

58630
来自专栏决胜机器学习

机器学习(十) ——使用决策树进行预测(离散特征值)

机器学习(十)——使用决策树进行预测(离散特征值) (原创内容,转载请注明来源,谢谢) 一、绘制决策树 决策树的一大优点是直观,但是前提是其以图像形式展示。如...

40660
来自专栏新智元

手把手教你用OpenCV和Python实现图像和视频神经风格迁移(代码)

2015年,Gatsys等人在论文A Neural Algorithm of Artistic Style中提出了最初的神经风格迁移算法。2016年,Johns...

55320
来自专栏FreeBuf

AI安全初探:利用深度学习检测DNS隐蔽通道

DNS 隐蔽通道简介 DNS 通道是隐蔽通道的一种,通过将其他协议封装在DNS协议中进行数据传输。 由于大部分防火墙和入侵检测设备很少会过滤DNS流量,这就给D...

34650
来自专栏null的专栏

简单易学的机器学习算法——Label Propagation

一、社区划分的概述 对于社区,没有一个明确的定义,有很多对社区的定义,如社区是指在一个网络中,有一组节点,它们彼此都相似,而组内的节点与网络中的其他节点则不相似...

1K80

扫码关注云+社区

领取腾讯云代金券