以前写过一篇概述: 以图搜图系统概述 。html
以图搜图系统须要解决的主要问题是:es6
对应的工程实践,具体为:数据库
使用卷积神经网路 CNN 去提取图像特征是一种主流的方案,具体的模型则可使用 VGG16 ,技术实现上则使用 Keras + TensorFlow ,参考 Keras 官方示例:api
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)app
features = model.predict(x)
这里提取出来的 feature 就是特性向量。数据库设计
一、归一化工具
为了方便后续操做,咱们经常会将 feature 进行归一化的处理:性能
from numpy import linalg as LA
norm_feat = feat[0]/LA.norm(feat[0])
后续实际使用的也是归一化后的 norm_feat 。
二、Image 说明
这里加载图像使用的是 keras.preprocessing 的 image.load_img 方法即:
from keras.preprocessing import image
img_path ='elephant.jpg'
img = image.load_img(img_path, target_size=(224,224))
其实是 Keras 调用的 TensorFlow 的方法,详情见 TensorFlow 官方文档 ,而最后获得的 image 对象实际上是一个 PIL Image 实例( TensorFlow 使用的 PIL )。
三、Bytes 转换
实际工程中图像内容经常是经过网络进行传输的,所以相比于从 path 路径加载图片,咱们更但愿直接将 bytes 数据转换为 image 对象即 PIL Image :
import io
from PIL importImage
img =Image.open(io.BytesIO(img_bytes))
img = img.convert('RGB')
img = img.resize((224,224),Image.NEAREST)
以上 img 与前文中的 image.load_img 获得的结果相同,这里须要注意的是:
四、黑边处理
有时候图像会有比较多的黑边部分(例如截屏),而这些黑边的部分即没有实际价值,又会产生比较大的干扰,所以去除黑边也是一项常见的操做。
所谓黑边,本质上就是一行或一列的像素点所有都是 (0, 0, 0) ( RGB 图像),去除黑边就是找到这些行或列,而后删除,实际是一个 numpy 的 3-D Matrix 操做。
移除横向黑边示例:
import numpy as np
from keras.preprocessing import image
defRemoveBlackEdge(img):
"""移除图片横向黑边
Args:
img: PIL image 实例
Returns:
PIL image 实例
"""
width = img.width img = image.img_to_array(img) img_without_black = img[~np.all(img == np.zeros((1, width,3), np.uint8), axis=(1,2))] img = image.array_to_img(img_without_black)
return img
CNN 提取图像特征以及图像的其它相关处理先写这么多,咱们再看向量搜索引擎。
只有图像的特征向量是远远不够的,咱们还须要对这些特征向量进行动态的管理(增删改),以及计算向量的类似度并返回最邻近范围内的向量数据,而开源的向量搜索引擎 Milvus 则很好的完成这些工做。
下文将会讲述具体的实践,以及要注意的地方。
一、对 CPU 有要求
想要使用 Milvus ,首先必需要求你的 CPU 支持 avx2 指令集,如何查看你的 CPU 支持哪些指令集呢?对于 Linux 系统,输入指令
cat /proc/cpuinfo | grep flags
你将会看到形如如下的内容:
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti intel_ppin tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts
flags 后面的这一大堆就是你的 CPU 支持的所有指令集,固然内容太多了,我只想看是否支持具体的某个指令集,好比 avx2 , 再加一个 grep 过滤一下便可:
cat /proc/cpuinfo | grep flags | grep avx2
若是执行结果没有内容输出,就是不支持这个指令集,你只能换一台知足要求的机器。
二、容量规划
系统设计时,容量规划是须要首先考虑的地方,咱们须要存储多少数据,这些数据须要多少内存以及多大的磁盘空间?
速算,上文中特征向量的每个维度都是 float32 的数据类型,一个 float32 须要占用 4 byte,那么一个 512 维的向量就须要 2 KB ,依次类推:
若是咱们但愿能将数据所有存在内存中,那么系统就至少须要对应大小的内存容量。
这里推荐你使用官方的大小计算工具: milvus tools
实际上咱们的内存可能并无那么大(内存不够不要紧,milvus 会将数据自动刷写到磁盘上),另外除了这些原始的向量数据以外,还会有一些其余的数据例如日志等的存储也是咱们须要考虑的地方。
三、系统配置
关于系统配置,官方文档有比较详细的说明:
四、数据库设计
collection & partition
在 Milvus 中,数据会按照 collection 和 partition 进行划分:
partition 分区在底层实现上其实与 collection 集合是一致的,只是前者从属于后者,可是有了分区以后,数据的组织方式变得更加灵活,咱们也能够指定集合中某个特定分区进行查询,从而达到一个更高的查询性能,更多内容参考 分区表详细说明 。
咱们可使用多少个 collection 和 partition ?因为 collection 和 partition 的基本信息都属于元数据,而 milvus 内部进行元数据管理须要使用 SQLite( milvus 内部集成)或者 MySQL (须要外部链接) 其中之一,若是你使用默认的 SQLite 去管理元数据的话,当集合和分区的数量过多时,性能损耗会很严重,所以集合和分区总数不要超过 50000(0.8.0 版本将会限制为 4096),须要设置更多的数量则建议使用外接 MySQL 的方式。
Milvus 的 collection 和 partition 内部支持的数据结构很是简单,只支持 ID + vector ,换句话说,表只有两列,一列是 ID ,一列是向量数据。
注意:
条件过滤
咱们使用一些传统的数据库时,每每能够指定字段进行条件过滤,可是 Milvus 并不能直接支持这项功能,然而咱们是能够经过集合和分区的设计去实现简单的条件过滤,例如,咱们有不少图片数据,可是这些图片数据都明确的属于具体的用户,那么咱们就能够按照用户去划分 partition ,这样查询的时候以用户做为过滤条件其实就是指定 partition 便可。
结构化数据与向量的映射
因为 milvus 只支持 ID + vector 的数据结构,而实际业务上咱们最终须要的每每是具备业务意义的结构化数据,也就是说,咱们须要经过 vector 向量最终找到结构化数据,所以咱们须要经过 ID 去维护结构化数据与向量之间的映射关系:
结构化数据 ID <-->映射表<-->Milvus ID
索引类型选择
请参考如下文档:
五、搜索结果处理
Milvus 的搜索结果是 ID + distance 的集合:
ID : collection 中的 ID 。
distance : 0 ~ 1 的距离值,表示类似性程度,越小越类似。
过滤 ID 为 -1 的数据
当数据集过少的时候,搜索结果可能会包含 ID 为 -1 的数据,咱们须要本身去过滤掉。
翻页
向量的搜索比较特别,查询的结果是按照类似性顺序,从最类似开始日后选取 topK 个数据( topK 须要搜索时由用户指定)。
Milvus 的搜索不支持翻页,若是咱们但愿在业务上实现这个功能,那么只能由咱们本身去处理,好比,我想要每页 10 条数据,只显示第 3 页的数据,那么咱们须要去取 topK = 30 的数据,而后只返回最后 10 条。
业务上的类似性阈值
两张图片的特征向量的距离 distance 范围是 0 ~ 1 ,有些时候咱们须要在业务上去断定两张图片是否类似,这时就须要咱们本身去设置一个距离的阈值,当 distance 小于阈值时就能够断定为类似,大于阈值时断定为不类似,这个也是须要根据具体的业务本身去处理。
本文讲述了以图搜图系统进行工程实践时比较常见的内容,最后强烈推荐一下 Milvus 。