vieyahn2017 / iBlog

44 stars 0 forks source link

12.11 基于深度学习实现以图搜图功能 #340

Closed vieyahn2017 closed 4 months ago

vieyahn2017 commented 4 years ago

基于深度学习实现以图搜图功能

vieyahn2017 commented 4 years ago

基于深度学习实现以图搜图功能

https://blog.csdn.net/chenghaoy/article/details/84977406

前记: 深度学习的发展使得在此之前以机器学习为主流算法的相关实现变得简单,而且准确率更高,效果更好,在图像检索这一块儿,目前有谷歌的以图搜图,百度的以图搜图,而百度以图搜图的关键技术叫做“感知哈希算法”,这是一个很简单且快速的算法,其原理在于针对每一张图片都生成一个特定的“指纹”,然后采取一种相似度的度量方式得出两张图片的近似程度,具体见之前的一篇博客哈希算法-图片相似度计算。

而深度学习在图像领域的快速发展,在于它能学习到图片的相关特征,评价一个深度模型的好坏往往在于它学习到有用的特征程度的多少,在提取特征这方面而言,目前神经网络有着不可替代的优势。而图像检索往往也是基于图像的特征比较,看特征匹配的程度有多少,从而检索出相似度高的图片。    基于vgg16网络提取图像特征 我们都知道,vgg网络在图像领域有着广泛的应用,后续许多层次更深,网络更宽的模型都是基于此扩展的,vgg网络能很好的提取到图片的有用特征,本次实现是基于Keras实现的,提取的是最后一层卷积特征。    提取特征

#  extract_cnn_vgg16_keras.py
# -*- coding: utf-8 -*-
import numpy as np
from numpy import linalg as LA

from keras.applications.vgg16 import VGG16
# from keras.applications.resnet50 import ResNet50
# from keras.applications.densenet import DenseNet121
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input as preprocess_input_vgg
# from keras.applications.resnet50 import preprocess_input as preprocess_input_resnet
# from keras.applications.densenet import preprocess_input as preprocess_input_densenet
class VGGNet:
    def __init__(self):
        # weights: 'imagenet'
        # pooling: 'max' or 'avg'
        # input_shape: (width, height, 3), width and height should >= 48
        self.input_shape = (224, 224, 3)
        self.weight = 'imagenet'
        self.pooling = 'max'
        # include_top:是否保留顶层的3个全连接网络
        # weights:None代表随机初始化,即不加载预训练权重。'imagenet'代表加载预训练权重
        # input_tensor:可填入Keras tensor作为模型的图像输出tensor
        # input_shape:可选,仅当include_top=False有效,应为长为3的tuple,指明输入图片的shape,图片的宽高必须大于48,如(200,200,3)
        #pooling:当include_top = False时,该参数指定了池化方式。None代表不池化,最后一个卷积层的输出为4D张量。‘avg’代表全局平均池化,‘max’代表全局最大值池化。
        #classes:可选,图片分类的类别数,仅当include_top = True并且不加载预训练权重时可用。
        self.model_vgg = VGG16(weights = self.weight, input_shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), pooling = self.pooling, include_top = False)
     #    self.model_resnet = ResNet50(weights = self.weight, input_shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), pooling = self.pooling, include_top = False)
     #   self.model_densenet = DenseNet121(weights = self.weight, input_shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), pooling = self.pooling, include_top = False)
        self.model_vgg.predict(np.zeros((1, 224, 224 , 3)))
    #    self.model_resnet.predict(np.zeros((1, 224, 224, 3)))
    #    self.model_densenet.predict(np.zeros((1, 224, 224, 3)))
    '''
    Use vgg16/Resnet model to extract features
    Output normalized feature vector
    '''
    #提取vgg16最后一层卷积特征
    def vgg_extract_feat(self, img_path):
        img = image.load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
        img = image.img_to_array(img)
        img = np.expand_dims(img, axis=0)
        img = preprocess_input_vgg(img)
        feat = self.model_vgg.predict(img)
        # print(feat.shape)
        norm_feat = feat[0]/LA.norm(feat[0])
        return norm_feat
    #提取resnet50最后一层卷积特征
    def resnet_extract_feat(self, img_path):
        img = image.load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
        img = image.img_to_array(img)
        img = np.expand_dims(img, axis=0)
        img = preprocess_input_resnet(img)
        feat = self.model_resnet.predict(img)
        # print(feat.shape)
        norm_feat = feat[0]/LA.norm(feat[0])
        return norm_feat
    #提取densenet121最后一层卷积特征
    def densenet_extract_feat(self, img_path):
        img = image.load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
        img = image.img_to_array(img)
        img = np.expand_dims(img, axis=0)
        img = preprocess_input_densenet(img)
        feat = self.model_densenet.predict(img)
        # print(feat.shape)
        norm_feat = feat[0]/LA.norm(feat[0])
        return norm_feat

将特征以及对应的文件名保存为h5文件

# index.py
# -*- coding: utf-8 -*-
import os
import h5py
import numpy as np
import argparse
from extract_cnn_vgg16_keras import VGGNet

'''
 Returns a list of filenames for all jpg images in a directory. 
'''
def get_imlist(path):
    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]

'''
 Extract features and index the images
'''
if __name__ == "__main__":
    database = './data/picture'
    index = 'vgg_featureCNN.h5'
    img_list = get_imlist(database)

    print("--------------------------------------------------")
    print("         feature extraction starts")
    print("--------------------------------------------------")

    feats = []
    names = []

    model = VGGNet()
    for i, img_path in enumerate(img_list):
        norm_feat = model.vgg_extract_feat(img_path)      #修改此处改变提取特征的网络
        img_name = os.path.split(img_path)[1]
        feats.append(norm_feat)
        names.append(img_name)
        print("extracting feature from image No. %d , %d images in total" %((i+1), len(img_list)))

    feats = np.array(feats)
    # print(feats)
    # directory for storing extracted features
    # output = args["index"]
    output = index
    print("--------------------------------------------------")
    print("      writing feature extraction results ...")
    print("--------------------------------------------------")

    h5f = h5py.File(output, 'w')
    h5f.create_dataset('dataset_1', data = feats)
    # h5f.create_dataset('dataset_2', data = names)
    h5f.create_dataset('dataset_2', data = np.string_(names))
    h5f.close()

选一张测试图片测试检索效果 相似度采用余弦相似度度量

#  test.py
# -*- coding: utf-8 -*-
from extract_cnn_vgg16_keras import VGGNet
import numpy as np
import h5py
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import argparse

query = './data/picture/bird.jpg'
index = 'vgg_featureCNN.h5'
result = './data/picture'
# read in indexed images' feature vectors and corresponding image names
h5f = h5py.File(index,'r')
# feats = h5f['dataset_1'][:]
feats = h5f['dataset_1'][:]
print(feats)
imgNames = h5f['dataset_2'][:]
print(imgNames)
h5f.close()

print("--------------------------------------------------")
print("               searching starts")
print("--------------------------------------------------")

# read and show query image
# queryDir = args["query"]
queryImg = mpimg.imread(query)
plt.title("Query Image")
plt.imshow(queryImg)
plt.show()

# init VGGNet16 model
model = VGGNet()

# extract query image's feature, compute simlarity score and sort
queryVec = model.vgg_extract_feat(query)    #修改此处改变提取特征的网络
print(queryVec.shape)
print(feats.shape)
scores = np.dot(queryVec, feats.T)
rank_ID = np.argsort(scores)[::-1]
rank_score = scores[rank_ID]
# print (rank_ID)
print (rank_score)

# number of top retrieved images to show
maxres = 3          #检索出三张相似度最高的图片
imlist = []
for i,index in enumerate(rank_ID[0:maxres]):
    imlist.append(imgNames[index])
    # print(type(imgNames[index]))
    print("image names: "+str(imgNames[index]) + " scores: %f"%rank_score[i])
print("top %d images in order are: " %maxres, imlist)
# show top #maxres retrieved result one by one
for i,im in enumerate(imlist):
    image = mpimg.imread(result+"/"+str(im, 'utf-8'))
    plt.title("search output %d" %(i+1))
    plt.imshow(image)
    plt.show()

以一张小鸟的图片为例测试结果如下: 在这里插入图片描述 第一张为测试图片,后面三张为检索图片,可以看出效果相当好了。 在这里插入图片描述 如果想用Resnet或者Densenet提取特征,只需针对上述代码做出相应的修改,去掉注释修改部分代码即可。

参考文献: https://github.com/willard-yuan/flask-keras-cnn-image-retrieval https://www.zhihu.com/question/29467370 http://yongyuan.name/blog/layer-selection-and-finetune-for-cbir.html

vieyahn2017 commented 4 years ago

向量检索在闲鱼视频去重的实践

https://blog.csdn.net/weixin_38912070/article/details/93857132

展开 一、背景 闲鱼是一个为二手商品交易提供服务的平台,闲鱼用户可以通过视频更全面直观的展示商品,于此同时也出现了一些视频拷贝、抄袭等不好的现象。为了解决这个问题,我们采用了很多方案,其中一种方案是将商品视频转换成向量,尝试通过向量检索计算商品视频相似性,进而判断商品是否重复。

闲鱼视频去重本质是高维向量检索,基于闲鱼当前商品规模及业务发展的预估,闲鱼向量检索系统需支持检索亿级别平均时长为20秒,每秒向量维度是1024维的视频。如此庞大的量级给我们的技术选型和实现带来了一定的挑战。

二、挑战 闲鱼向量检索系统的挑战主要在几个方面:

数据量大 闲鱼商品的视频总帧数在亿级别;

单帧维度高 为保证向量召回的准确率,目前单帧向量维度是1024维;

高精度召回 为保证效果,向量召回的准确率在95%以上;

高性能 为保证用户体验,单帧单次召回耗时100ms左右,QPS在1000以上。

高维向量检索需要解决在数据维度增加之后导致检索性能急剧下降的问题。高维向量数据的特点主要包括以下三个方面:

稀疏性。随着维度增长,数据在空间分布的稀疏性增强;

空空间现象。对于服从正态分布的数据集,当维数大约增加到10时,只有不到1%的数据点分布在中心附近;

维度效应。随着维数的增加,对索引的维护效率急剧下降,并且高维空间中数据点之间的距离接近于相等。

三、实现方案 基于上述的业务背景及挑战,我们从以下几个方面重新梳理了相关实现。

3.1 视频向量化 向量化是指将视频数据按照一定的算法转换为向量进行表示,转换算法决定了向量表达原始视频数据的准确性。向量化使得对视频数据的检索、去重、计算相似性等操作转化为对向量的求距离或求夹角操作。

视频向量化即在视频中抽取关键帧,对每帧视频进行特征提取,包含局部特征提取和全局特征提取。局部特征提取包含“特征点检测”、“特征点描述”、“特征描述降维”。全局特征提取同样包含多个步骤,我们将这些步骤封装为自定义算子,利用Tensorflow Lite模型将自定义算子组合起来,这样就可以动态更新算子,达到算法实时更新的目的。

将视频转化为向量之后,计算视频的相似性就相当于计算向量的相似性。计算向量相似性有几种方式,分别为计算海明距离、夹角余弦、欧式距离和向量内积等。

3.2 计算向量距离 3.2.1 海明距离 严格来说,海明距离其实和向量没有太大关系,海明距离计算的是两个等长字符串对应位置字符不同的个数。换句话说,海明距离表示将一个字符串变换成另一个字符串所需要替换的字符个数。 对于向量来说,海明距离可以看作是将一个向量变换成另一个向量所需要替换的坐标个数。

3.2.2 向量夹角余弦 计算向量的cos夹角即在坐标系中,向量从原点出发,分别指向不同的方向,计算两个方向之间夹角。若两个向量之间夹角越小则向量越相似。

3.2.3 向量欧式距离 向量欧式距离即向量顶点之间的距离,在N维空间中两点x1和x2之间的距离公式:

欧式距离越小表示向量的顶点之间越近,即向量之间更相似。

3.1.4 向量内积

通过公式可以发现向量内积与向量夹角余弦的含义类似,内积与夹角余弦成正比,内积越大,夹角余弦越大,夹角越小。

不同的距离计算公式反应的是向量不同维度的特征,需要根据向量生成算法选择距离计算公式。我们对上面几种公式进行了召回率测试,测试结果(计算公式与召回率关系)见下表。

3.3 向量检索 对于向量检索来说,通常有三类方法:基于树的方法、hash方法、矢量量化方法,每种方法都有各自的适用范围。

3.3.1 基于树的方法 KD树是其下的经典算法。一般而言,在空间维度比较低时,KD树的查找性能还是比较高效的;但当向量维度较高时,该方法会退化为暴力枚举,性能比较差,这时一般会采用下面的哈希方法或者矢量量化方法。

一个三维k-d树。第一次划分(红色)把根节点(白色)划分成两个节点,然后它们分别再次被划分(绿色)为两个子节点。最后这四个子节点的每一个都被划分(蓝色)为两个子节点。因为没有更进一步的划分,最后得到的八个节点称为叶子节点。

3.3.2 hash方法 LSH(Locality-Sensitive Hashing)是hash方法的代表算法。它是用hash的方法把数据从原空间哈希到一个新的空间中,使得在原始空间的相似的数据,在新的空间中也相似的概率很大,而在原始空间的不相似的数据,在新的空间中相似的概率很小。

对于小数据集和中规模的数据集(几个million-几十个million),基于LSH的方法的效果和性能都很不错。这方面有2个开源工具FALCONN和NMSLIB。

3.3.3 矢量量化方法 矢量量化方法,即vector quantization。矢量量化常用于数据压缩,是将一个向量空间中的点用其中的一个有限子集来进行编码的过程。并用聚类后的中心点的标签替代该簇中其他点的标签,以此达到缩小子集大小,压缩数据的目的。

所以矢量量化方法天生就适合做大数据集处理,在本次项目中我们也使用矢量量化方法处理闲鱼商品视频的超大数据集,缩小检索规模,在保持较高检索准确率的同时大幅度提高检索性能。

在矢量量化编码中,关键是码本的建立和码字搜索算法。比如常见的层次聚类算法,就是一种矢量量化方法。而在相似搜索中,向量量化方法又以PQ方法最为典型。

3.3.3.1 层次聚类算法(HC) 层次聚类(Hierarchical Clustering)是聚类算法的一种,通过计算不同类别数据点间的相似度来创建一棵有层次的嵌套聚类树。在聚类树中,不同类别的原始数据点是树的最低层,树的顶层是一个聚类的根节点。

在超大数据规模,同时要求高准确率情况下,可以通过多层聚类的方法,通过中心点来表示候选集,从而降低触达候选集的计算量。在保证结果的同时,大幅加快计算速度。

3.3.3.2 乘积量化算法(PQ) 乘积量化即Product Quantization,这里的乘积是指笛卡尔积(Cartesian product),意思是指把原来的向量空间分解为若干个低维向量空间的笛卡尔积,并对分解得到的低维向量空间分别做量化(quantization)。这样每个向量就能由多个低维空间的量化code组合表示。

PQ是一种量化(quantization)方法,本质上是数据的一种压缩表达方法,所以该方法除了可以用在相似搜索外,还可以用于模型压缩,特别是深度神经网络的模型压缩上。

PQ算法可以理解为是对vector quantization做了一次分治,首先把原始的向量空间分解为m个低维向量空间的笛卡尔积,并对分解得到的低维向量空间分别做量化,那如何对低维向量空间做量化呢?恰巧又正是用kmeans算法。所以换句话描述就是,把原始D维向量(比如D=128)分成m组(比如m=4),每组就是...

从测试结果来看,HC算法的召回率和延时比PQ算法要好一些,但因为闲鱼视频是高维度的向量数据集,PQ算法的表现更稳定,所以我们最终选用的是PQ算法实现向量检索。

3.4 项目架构 上面说的是本次项目应用到的算法知识,这一章节主要阐述项目整体架构。

3.4.1 应用架构 640?wx_fmt=png应用架构分为几个部分:

客户端

闲鱼客户端在TensorFlow Lite的基础上定制开发了算法容器,实现在端上将视频提取关键帧,对单帧视频进行视频特征计算;根据在端上计算的特征,在云上检索视频向量库,检索相似向量,最后计算出相似视频。

微服务集群

后端封装通用向量统一接入层,屏蔽底层对接不同向量检索引擎对上层的影响,并且预留扩展点,为后续接入图片向量和商品向量打下基础。后端接收到视频向量后调用底层向量检索引擎召回相似结果,并根据商品状态过滤、重新计算视频相似分;后端会将视频向量数据保存到日志中,由日志监听同步系统将向量数据同步到离线数据中心。

日志监听同步系统

日志监听同步系统会实时监听指定日志文件变化,并根据指定格式解析日志数据,将解析后的数据同步到离线数据中心。

离线数据中心

离线数据中心每15分钟生成一个实时增量视频向量数据的分区,每天会对昨日全量数据和当日增量数据做合并,再将合并后的当日全量数据回流到向量检索引擎,构建当日全量数据的索引。

向量检索引擎

我们本次使用的向量检索引擎是阿里内部自研的BE,BE是阿里推荐系统负责在线召回的引擎,向量召回是BE最新集成的召回策略,融合了搜索从离线到在线的全链路技术体系,并依托强大的管控系统,实现了从开发、上线到运维的全生命周期管理。

BE从逻辑上来说,主要负责从多种类型的索引表中召回数据并关联具体的业务信息进行过滤和粗排。其中filter和sorter是算法插件,可以灵活配置在检索流程的各个环节,具体的过滤和排序逻辑由算法同学根据业务场景进行编写,同时BE也内置了大量的通用组件。 BE引擎维护向量数据索引,对外提供向量检索服务,并且会定时从离线数据中心同步向量数据构建索引。

BE引擎深度集成开源的KNN库–FAISS,改造定制使其支持向量索引的分布式构建和查询,实现多种基于量化的方法如粗量化、积量化以及粗量化 + 积量化的组合等方法,并且在线查询的延时、索引构建的性能都很优秀。

3.5 上线效果 通过向量检索方法进行视频去重,通过定制计算向量算法、适配向量内积计算方法和PQ算法优化等方式,上线之后,经过闲鱼线上超大商品视频数据集验证之后,整个系统QPS在1000以上,召回延时在毫秒级别,视频去重整体延时在100毫秒左右;视频召回率最终稳定在95%以上。

四、总结 本文主要介绍闲鱼如何使用向量检索方法实现视频去重业务,描述了视频去重的业务背景、向量检索涉及的相关算法及视频去重的应用架构,希望给各位读者带来一些思考和启发。 项目一期首先实现了在闲鱼视频上的去重能力,我们会继续将去重、搜索能力应用到商品图片甚至是商品本身,后续会继续和大家分享,敬请期待。

五、参考资料 PQ算法

faiss

Large-scale video retrieval using image queries

https://lear.inrialpes.fr/pubs/2011/JDS11/jegousearchingwith_quantization.pdf

https://en.wikipedia.org/wiki/Hamming_distance

https://zh.wikipedia.org/wiki/K-d%E6%A0%91

https://github.com/facebookresearch/faiss

https://github.com/andrefaraujo/videosearch

vieyahn2017 commented 4 years ago

利用 inceptionV3 特征简单实现以图搜图

https://blog.csdn.net/itnerd/article/details/88901877

从百度图片搜集一些素材:

分别有:喵x4,汪x4,猪x4,鸡x4

实验的目的 是希望通过输入一张图片,返回与其最相似的图片,达到以图搜图的效果。

在这里插入图片描述

代码 首先,导入必要的包

import os
import tarfile
import matplotlib.pyplot as plt
import numpy as np
import PIL
import tensorflow as tf
import tensorflow.contrib.slim as slim
import tensorflow.contrib.slim.nets as nets
from six.moves.urllib.request import urlretrieve

获取图片

# 获取图片
images = []
img_dir = '\Download\img'
for idx, img_name in enumerate(os.listdir(img_dir)):
    img_path = os.path.join(img_dir,img_name)
    print('fetching pic:',img_path)
    img = PIL.Image.open(img_path)
    img = img.resize((299, 299))
    images.append(img)
NUM = len(images)

从网上下载别人训练好的模型,也可以手动下载解压到文件夹。

# 下载 inceptionV3 模型
data_dir = '.'
checkpoint_filename = os.path.join(data_dir, 'inception_v3.ckpt')
if not os.path.exists(checkpoint_filename):
    print('downloading inceptionV3 model...')
    inception_tarball, _ = urlretrieve(
        'http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz')
    tarfile.open(inception_tarball, 'r:gz').extractall(data_dir)

# 计算流 (注意:299,299,3,1001是inceptionV3的标准参数
image = tf.Variable(tf.zeros((299, 299, 3)))
preprocessed = tf.multiply(tf.subtract(tf.expand_dims(image, 0), 0.5), 2.0)
arg_scope = nets.inception.inception_v3_arg_scope(weight_decay=0.0)
with slim.arg_scope(arg_scope):
    logits, end_points = nets.inception.inception_v3(
        preprocessed, 1001, is_training=False, reuse=False)
    # logits = logits[:, 1:]  # ignore background class
    # probs = tf.nn.softmax(logits)  # probabilities

#利用 saver 载入网络参数
restore_vars = [
    var for var in tf.global_variables() if var.name.startswith('InceptionV3/')
]
saver = tf.train.Saver(restore_vars)

在 session 中得到所有图片的 inception 特征

with tf.Session() as sess:
    saver.restore(sess, os.path.join(data_dir, 'inception_v3.ckpt')) #加载网络参数
    layer = 'PreLogits'
    features = []
    for img in images:
        img = np.asarray(img).astype(np.float32)/ 255.0
        feature_values = sess.run( end_points, feed_dict={image: img})
        feature = feature_values[layer].squeeze()
        features.append(feature)
    feature_vectors = np.stack(features)
# 计算欧氏距离
# 在这里不做归一化,因为归一化之后和余弦距离等价
distance_euclidean = np.sum(
    np.power(feature_vectors, 2), axis=1, keepdims=True) + np.sum(
        np.power(feature_vectors, 2), axis=1,
        keepdims=True).T - 2 * np.dot(feature_vectors, feature_vectors.T)

# 计算余弦距离
features_norm = feature_vectors / np.linalg.norm(
    feature_vectors, axis=1)[:, np.newaxis]
distance_cosin = np.dot(features_norm, features_norm.T)

到这里,所有图片之间的相似度就已经得到了。

接下来,我们从素材中挑4张图片来展示效果: 找出和输入图片最相似的前6张图片

test_images = [(i,img) for i, img in enumerate(images) if i%4==2]
TEST_NUM = len(test_images)
TOP_NUM = 6

# 测试欧式距离
plt.figure()
for i,(idx_img,plt_img) in enumerate(test_images):
    order_euclidean = np.argsort(distance_euclidean[idx_img])

    plt.subplot(TEST_NUM, TOP_NUM+1, i * (TOP_NUM+1) + 1)
    plt.axis('off')
    plt.title('input')
    plt.imshow(plt_img)

    for idx_sim, j in enumerate(order_euclidean):
        if idx_sim>=TOP_NUM:
            break
        similar_img = images[j]
        plt.subplot(TEST_NUM, TOP_NUM+1, i * (TOP_NUM+1) + idx_sim + 2)
        plt.axis('off')
        plt.title('{:.2}'.format(distance_euclidean[idx_img,j]))
        plt.imshow(similar_img)

# 测试余弦距离
plt.figure()
for i,(idx_img,plt_img) in enumerate(test_images):
    order_cosin = np.argsort(distance_cosin[idx_img])[::-1]

    plt.subplot(TEST_NUM, TOP_NUM+1, i * (TOP_NUM+1) + 1)
    plt.axis('off')
    plt.title('input')
    plt.imshow(plt_img)

    for idx_sim, j in enumerate(order_cosin):
        if idx_sim>=TOP_NUM:
            break
        similar_img = images[j]
        plt.subplot(TEST_NUM, TOP_NUM+1,i* (TOP_NUM+1) + idx_sim + 2)
        plt.title('input')
        plt.axis('off')
        plt.title('{:.2}'.format(distance_cosin[idx_img,j]))
        plt.imshow(similar_img)

结果 结果展示的第一列是输入的图片,显然和其最相似的是它自己,然后是同类

Fig.1 欧氏距离: 在这里插入图片描述 Fig.2 余弦距离:

vieyahn2017 commented 4 years ago

https://github.com/willard-yuan/flask-keras-cnn-image-retrieval Image Retrieval Engine Based on Keras

web界面采用的SoTu https://github.com/willard-yuan/SoTu

vieyahn2017 commented 4 years ago

手把手教你实现一个「以图搜图」

https://blog.csdn.net/jiangjunshow/article/details/100539643

准备工作 老样子,先来准备好我们此次需要使用到的工具:

IDE:Pycharm Python:3.7 Packages:Keras + TensorFlow + Pillow + Numpy keras

Keras是一个高层神经网络API,Keras由纯Python编写而成并基Tensorflow、Theano以及CNTK后端。简单来说,keras就是对TF等框架的再一次封装,使得使用起来更加方便。

基于vgg16网络提取图像特征 我们都知道,vgg网络在图像领域有着广泛的应用,后续许多层次更深,网络更宽的模型都是基于此扩展的,vgg网络能很好的提取到图片的有用特征,本次实现是基于Keras实现的,提取的是最后一层卷积特征。

思路 主要思路是基于CVPR2015的论文《Deep Learning of Binary Hash Codes for Fast Image Retrieval》实现的海量数据下的基于内容图片检索系统。简单说来就是对图片数据库的每张图片抽取特征(一般形式为特征向量),存储于数据库中,对于待检索图片,抽取同样的特征向量,然后并对该向量和数据库中向量的距离(相似度计算),找出最接近的一些特征向量,其对应的图片即为检索结果。如下图所示:

用户请求和预处理部分主要是Web服务端应该做的,这里不加以讨论,接下来我们主要进行红线标注部分的实现。

实操 提取图片特征

keras在其中文文档中提供了一个利用VGG16提取特征的demo

from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np

model = VGG16(weights='imagenet', include_top=False)

img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

features = model.predict(x)

这里我们需要对其进行简单修改,封装成一个类以便后期调用。如下图所示:

考虑到篇幅,文中代码图片已删除较多注释,如需了解详细注释信息,可在微信公众号「01二进制」后台回复「图像检索」获取源代码。下同

将特征以及对应的文件名保存为h5文件 什么是 h5 文件

h5文件是层次数据格式第5代的版本(Hierarchical Data Format,HDF5),用以存储和组织大规模数据。

H5将文件结构简化成两个主要的对象类型:

数据集dataset,就是同一类型数据的多维数组

组group,是一种容器结构,可以包含数据集和其他组,若一个文件中存放了不同种类的数据集,这些数据集的管理就用到了group

直观的理解,可以参考我们的文件系统,不同的文件存放在不同的目录下:

目录就是 hdf5 文件中的 group,描述了数据集 DataSet 的分类信息,通过 group 有效的将多种 dataset 进行管理和划分。文件就是 hdf5 文件中的 dataset,表示具体的数据

下图就是数据集和组的关系:

在 Python 中,我们通常使用 h5py 库对 .h5 文件进行操作,具体的读写方法自行百度,这里不在演示。

抽取数据集中的图像特征保存到 h5 文件中

我们在项目根目录下命名一个database文件夹作为数据集,然后编写一个获取文件夹内图片的方法:

def get_imlist(path):
    return [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.jpg')]

随后我们便可以依次读取数据然后,一一提取其特征保存到文件中了。如下图:

至此,我们就已经算是完成了模型的训练了。

选一张测试图片测试检索效果 经过上述操作,我们已经将数据集中的所有图片的特征保存到模型中了,剩下的就是抽取待测图片的特征,然后和特征集中的特征一一比较向量间的相似度(余弦相似度),然后按照相似度排序返回给用户即可。

Tips:各种相似度的 Python 表示可以参考Python Numpy计算各类距离

以某一个包包为测试图片,输出结果如下所示:

在PyCharm中可以很方便的查看matplotlib生成的图片,第一张为测试图片,后面三张为检索图片,可以看出效果相当好了。

vieyahn2017 commented 4 years ago

Keras下载的数据集以及预训练模型保存在哪里

Keras下载的数据集在以下目录中: root\.keras\datasets

Keras下载的预训练模型在以下目录中: root\.keras\models

在win10系统来说,用户主目录是:C:\Users\user_name,一般化user_name是Administrator 在Linux中,用户主目录是:对一般用户,/home/user_name,对于root用户,/root

vieyahn2017 commented 4 years ago

本地以图搜图识图工具——嗅图狗!(python3.6实现图像比对指路,代码git) https://blog.csdn.net/sinat_27382047/article/details/83040411

1.写在前面 因为之前做完后(当毕业设计做的)为了尽量实用打了个exe,结果好多人反应问题,索性直接把代码、更新exe的链接都写个博客放在这里(这么晚才放出源码就是怕毕设查重……)

全部项目源码git及说明:https://github.com/naturalcaduceus/picSniffer

工程内有:完整功能版、exe版(为了打包不至于上百M)的python源码(见readme),大部分识图算法搜索自网络,用法可以参考(当初做的时候找到源码不会用才是最苦恼的TAT……)