浅谈以图搜图及 milvus 2.2 集群以图搜图实践

浅谈以图搜图及 milvus 2.2 集群以图搜图实践

Cocytus Elias 65 2022-12-18

搜图这件事有不同的实现方案,但其实最底层的逻辑都是一致的:提取归一化后的特征进行距离计算

image-20230204173327008



搜图原理

图像组成

在了解搜图算法之前,我们需要先了解下图片的组成:像素RGB像素分布元数据

image-20230204173356496


像素:像素是一张图片的最小组成单位,一个个像素拼接为最终的图片。

image-20230204173531996

RGB:颜色组成基础就是 RGB 三原色,一张图片的每个像素都承载了不同的 RGB 数值代表颜色。

image-20230204173601716

像素分布:一张图片要想表达信息(比如一只猫或者一个茶杯),那就需要将承载不同 RGB 数值的像素块按一定序列排列并组合在一起。从信息的角度来说这种组合又分为两种:

  • 结构数据:结构数据就是一个图片的骨架,也叫做基础信息,它把整个图片切割为不同的区域,就像是画素描一样,要先去勾勒轮廓。
  • 筋肉数据:筋肉数据用于填充一个图片的骨架,它主要是提供细节,诸如某个具体区域的颜色,些微的光泽(光泽本质其实就是颜色)等等。

而我们一般进行诸如无损图像压缩、搜图等等,都是从结构数据入手,尽可能多的提取结构数据,抛弃筋肉数据和元数据。当然不同场景对于提取和抛弃程度不一样,但是底层逻辑是相通的。这个提取出来的就是图片的特征值。

元数据:诸如创建/拍摄时间、相机厂牌、定位信息、盲水印等等。

image-20230204173656998


归一化/标准化

而什么是归一化/标准化呢?通俗来讲就是尽可能让不同的图片在同一标准下提取特征数据

搜图处理常用的手段有:灰度化图像缩小边框去除

image-20230204173717650

灰度化:这样能确保即使图片调了曝光、加了滤镜,灰度化以后,特征提取是稳定的。

image-20230204173810326

图像缩小:这种方式能够确保同一张图片在执行不同的缩放或纵横比后,能回归一致。

image-20230204173852970

边框去除:去除一行或者一列都是同一极端颜色的行或列,比如纯黑或纯白。这种方式能够保证不受边框干扰。

image-20230204173828890


两类搜图算法:HASH 和 向量

image-20230204173951286

HASH

HASH 搜索中最简单的是直接把图片进行简单归一化后 HASH。但是这种 HASH 方式只适用于简单的去重或搜索操作,并且很容易受到干扰。只需要加下水印,两张肉眼相同或相近的图片他们的 HASH 值就完全不一样了。

除此之外还有三种图像感知 HASH 算法:AHASHDHASHPHASH

image-20230204174025256

AHASH:图像均值 HASH 算法。这种算法主要步骤是:

  1. 将图片灰度化并缩小为 8 X 8 的图像。
  2. 计算图像平均颜色。
  3. 根据每个像素颜色与平均颜色的比较来决定对应的 64HASH 中的每一个值。

image-20230204174114520

DHASH:图像梯度 HASH 算法。这种算法的主要步骤是:

  1. 将图片灰度化并缩小为 9 X 8 的图像。
  2. 计算相邻像素之前的差异,这个差异就是相对梯度。每行 9 个像素能计算出 8 个差异。88 个差异就变成了 64HASH

image-20230204174148642

PHASH:图像频率 HASH 算法。这种算法的主要步骤是:

  1. 将图片灰度化并缩小为 32 x 32 的图像。
  2. 使用 离散余弦变换(DCT) 计算出图像内 频率和标量 的集合。
  3. 保留左上角 8 X 8 的低频数据。
  4. 排除第一个 CD 项后,计算 DCT 平均值。
  5. 根据 64DCTDCT 平均值的对比设置来转换为 64HASH

image-20230204174210719

上述三种方式的底层逻辑是一致的。先进行标准化将其变为 指定范围内的像素块。之后对每个像素块采用不同的方式进行计算,得到最终的 HASH。而判断两个图片是否相似,只需要计算两个图片的最终 HASH 值的汉明距离是否在一定范围内。

关于汉明距离,通俗理解就是:两个字符串他们最小需要更改多少个字符能变成同一个字符串,这个更改字符串的次数就是汉明距离。

image-20230204174230819

常用于错字推测,比如 错误单词 CPT 可以推测为真实想要输入的是 OPTCAT,这两个单词只需要更改一个字符就可以,CPTOPT 的汉明距离是 1,CPTCAT 的汉明距离也是 1,但 OPTCAT 的汉明距离是 2 。

图像感知 HASH 算法很大程度上可以解决搜图中遇到的许多问题以及图像干扰。但是还不够好,对于诸如:特征搜索(商品搜索)、高近似度(包括颜色相近) 等场景下,精准度就下来了。这个时候我们就可以使用向量进行特征提取。


向量

相对于 HASH 来说,向量可以提取多个维度的特征值。常用的有 cnn 下的 alexNetvgg16googLeNetresNet 等。

image-20230204174317011

将一张图片的 RGB 序列化为多维矩阵,这个多维矩阵就是向量值。而我们一般只使用中低维度的向量,比如 512 维256 维等等。

而使用不同的模型,会有不同的提取原理和步骤。

使用向量搜索的特点在于,它会去匹配相近、相似的图片,比如搜索一个机器人, HASH 的搜图只能根据这张图片去搜索相近的,而向量甚至可以帮你搜索出来各种各样的相似机器人。这是他们两者之前最大的差别。

而判断两个图片的向量是否相似,则是直接计算两个向量之间的距离。


Milvus 向量库

目前我们的搜图系统在用的是 milvus 向量库。在聊 milvus 之前我们先说说,为什么要使用专门的向量检索库:

  1. 向量检索库一般都内置了常用的矩阵计算方法,拿来即用,能极大的降低开发和维护成本
  2. 向量检索库本身就是为了向量而生,所以对于一些大规模向量数据集,它的搜索性能和可靠性会更好。不使用普通数据库进行搜索的原因是,普通数据库的搜索侧重与向量数据库并不一样。向量偏向于矩阵间的差值,普通数据库偏重于字符或纯数间的比较、计算。
  3. milvus 目前支持多节点集群,确保 milvus 的高可用。对于节点也方便横向扩展。
  4. 而选择 milvus ,还有一个原因是:支持国产,并且社区响应及时。目前来看基本上官方是有问必答。

image-20230204174649733


以图搜图实践

目前 milvus 主要是用于以图搜图的场景。

在版本上采用的是最新的 2.2 版本进行集群化部署。

这主要是考虑到数据量比较大,单机的数据库支撑起来比较吃力,并且单节点的稳定性是不如集群的。

稳定性这里指的并非是 milvus ,而是主要考虑到机器故障、网络故障等一系列因素。

在整体的技术架构上,我将图片特征向量提取单独地作为一个服务进行部署,这一块主要是为了将图片特征向量丛业务中抽离出来,方面后续有其他业务使用图片向量提取。

image-20230204174711822


Milvus 架构简述

在使用 milvus 中我也遇到了一些问题,在分享之前,我想简单聊一下,milvus 各个组件的用途,这在问题排查时很有帮助。

Architecture_diagram

milvus 整体分为两部分,外部依赖及内部组件。外部依赖主要有:etcdpulsar/kafkaminio/s3

etcd 的作用有两个:存储元数据(主要是 collection 等结构相关数据)和服务发现。

如果 etcd 出现问题则可能导致:

  • 元数据丢失,会导致整体的数据自动被删除,因为元数据与存储的向量数据都是对应的。如果没有元数据,那么向量数据也是无法直接用的。
  • 服务不稳定,会导致整体的 milvus 服务不稳定。

minio/s3 的作用只有一个:存储向量数据的 log 文件。minio/s3 内存储的 log 文件设计的有点像是 mysqlbinlog。如果 minio/s3 的数据丢失了,那向量数据就是彻底丢失了。

pulsar/kafka 是作为 milvus 的血管,他的主要作用就是任务执行以及流式处理平台。如果你的请求走不动了,请务必看看 pulsar/kafka 有没有问题。

image-20230204174735746


索引选型

image-20230204174811867


Collection 创建/搜索步骤

collection 的整体创建步骤如下:

  • 创建指定数据结构的 collection,创建数据结构时,需要至少指定一个主键字段和一个向量字段。
  • 创建 collection 后需要创建索引,或者创建数据后创建索引都行。
  • collection 创建后只需要创建一次索引即可,collection 不论是否加载,数据插入都会根据配置制定的时间去创更新索引。
  • 如果想更改 collection 的索引类型,需要先释放 collection ,删除索引、创建新索引,才能再加载搜索。

image-20230204175012830


问题及解决方法

链接/访问/搜索问题


查询/访问比较慢:对于集群服务部署后,查询特别慢的问题,如果是数据量很小就查询很慢,一般因为 pulsar / kafka 的性能问题。如果是数据量小查询快,数据量大查询慢,则需要去看一下资源占用情况,相应的可以做扩容或者按 官方文档 来去调节限额。

attu / 客户端链接attu 工具无法登陆的问题,可以排查下 attumilvusproxy 节点是否网络相通,milvus 是否有节点异常。milvus 内部或依赖如果有部分节点异常(尤其是 coordpulsaretcd 等都可能导致 attu 无法登陆)。客户端同理。

查询/搜索异常: 查询/搜索前,必须确保:

  • collection 已经创建了索引并已经被加载了。
  • 只有创建了索引的情况下,才能确保 collection 被加载。只有被加载的情况下,才能对 collection 内容进行搜索/查询。

非预期内查询结果:有时候搜索数据,会出现前几个是匹配的,后面的全是不匹配的。这是因为查询结果只会按差异得分从低到高来排序,并返回指定的 TopK 条内的数据,暂时不支持 score 分值范围筛选,需要拿到结果后对差异得分 score 进行筛选,建议 score <= 0.5score 超过 1 的结果基本是不相干的。


数据相关问题


数据丢失

  • 检查下你的代码,如果你使用的是 python,并且在创建或更新 collection 设置了 collection.ttl.seconds ,请把它去掉或更改为你需要的值。目前仅 python 客户端支持 ttl 设置。
  • 检查下 etcd 的文件是否丢失了,milvus 会根据配置的定时,自动去清理不存在 etcd 中的元数据对应的所有文件。
  • 检查下 minio/s3 和其他服务节点有没有异常。

数据删除:数据在 milvus 中删除后,不会立即释放磁盘,会到指定的过期时间之后从磁盘层面删除已删除的数据。这个时间默认是一天。

数据写入后搜索延时:如果数据写入后搜索不到,需要检查下是否配置了延时搜索。默认是插入后立即就能搜索。


其他


**配置**:官方镜像中默认会自带一个标准配置,如果需要进行自自定义配置的话,可以把配置文件挂载到 `/milvus/configs/milvus.yaml` 上。如果你使用的 `minio/etcd` 需要指定 `bucket` ,也是需要去在配置文档里制定的。

依赖服务:对于依赖服务建议:

  • etcd 组集群,运行于 ssd 硬盘上,并定期备份。
  • pulsarbookkeeper,建议对 journalledgers 进行多磁盘挂载,可以较好的提高吞吐量。