海量数据下以图搜图实现方案

以图搜图原理

海量数据下以图搜图实现方案

图文无关

首先介绍一下以图搜图的实现原理,弄明白我们是怎样将一张图片转化为可以量化计算的哈希值。

我们知道图片本身是二进制数据,是一系列像素值的集合,一张彩色图片可以用[h,w,3]的三维数组来表示,要直接比较两个三维数组的相似度,非常困难,我们要对这个数组进行简化,以便于我们计算。

  1. 缩小尺寸

通常图片的h和w在800-1200之间,我们需要缩小图片的尺寸,具体缩小到多大,需要根据具体情况而定,既不损失过多信息,也能减小计算量

  1. 简化色彩

一般而言,图片色彩对我们比较相似度来说,不会有影响,所以将三通道转化为单通道

简化完之后,[h,w,3]的三维数组就变成h’*w’(h’和w’为缩小后的图片尺寸)个像素值,象素值取值范围为0-255之间的整数,继续简化成0/1

  1. 计算所有像素平均值
  2. 将每个像素的值与平均值进行比较,小于平均值标记为0,大于平均值标记为1

转化完之后,我们就得到一个h’*w’位的二进制数值,这个值就是我们能够直接比较的哈希值,通过计算两个哈希值的汉明距离,就能计算出两张图片的相似度。

在实际生产中,我们通过神经网络CNN,将一张图片转化为512位的哈希值,最大限度减少图片信息的损失。

海量数据下的思考

在了解完以图搜图原理之后,想要用到生产上,我们还需要解决很多其他问题,图片变成512位的二进制数值,怎样存储效率最高,图片本身还有一些描述信息,我们同样需要保存下来,这些描述信息可能会作为查询条件,生产环境图片总量在几千万的数量级,如何在这么大数据量的情况下,快速进行检索与哈希值的计算。

在这里我们选取了Elasticsearch(以下简称ES)来作为存储数据库,主要原因有以下几点:

  1. 能平行扩展

ES集群扩展起来十分方便,随着数据量的不断增加,可以添加机器来满足存储查询的性能要求。

  1. 支持自定义插件

我们通过自定义插件,可以实现两个哈希值汉明距离的计算。

  1. 快速检索

ES检索速度非常快,能根据图片描述信息快速检索出我们需要的信息。

实现

我们将图片的512位二进制数值转为8个long类型数值存储到ES,每个long类型的存储空间为64bit,8*64刚好存下512位哈希值信息。

ES插件实现方式如下:

1、自定义我们自己的插件,继承Plugin然后实现接口ScriptPlugin

ublic class HammingPlugin extends Plugin implements ScriptPlugin {

@Override

public ScriptEngine getScriptEngine(Settings settings, Collection<scriptcontext>> contexts) {/<scriptcontext>

return new MyScriptEngine();

}

}

2、我们的HammingPlugin主要实现以下逻辑(读取参数,逐一与存储的特征值计算汉明距离,求和,计算相似度,把相似度作为score返回)

@Override

public SearchScript newInstance(LeafReaderContext context) {

return new

SearchScript(p, lookup, context) {

@Override

public double runAsDouble() {

try {

double max = 0;

for (Map<string> map : list) {/<string>

final HashMap<string> h = (HashMap<string>) lookup.source().get("h");/<string>/<string>

double sum = 0D;

for (int i = 1; i < 9; i++) {

String name = "h" + i;

sum += hammingDistance(map.get(name), h.get(name));

}

double mid = 512D - sum;

max = mid > max ? mid : max;

}

return max;

} catch (Exception e) {

logger.error(e.getMessage());

return 0D;

}

}

};

}

3、汉明距离计算函数:

private double hammingDistance(long x, long y) {

double cnt = 0D;

if ((x > 0 && y < 0) || (x < 0 && y > 0)) {

cnt += 1;

}

long hamming = Math.abs(x) ^ Math.abs(y);

while (hamming > 0) {

hamming = hamming & (hamming - 1);

cnt += 1;

}

return cnt;

}

4、查询结果展示

"took": 247,

"timed_out": false,

"_shards": {

"total": 5,

"successful": 5,

"skipped": 0,

"failed": 0

},

"hits": {

"total": 38700,

"max_score": 512,

"hits": [

{

"_index": "scene",

"_type": "scene",

"_id": "awspvWkBcIg61crkWqEQ",

"_score": 512,

… …

}

]

}

}

这个就是ES返回给我们的数据格式,took代表查询耗时247毫秒,shards里面信息表示我们总共有5个分片,total表示本次查询总共命中38700条数据max_score表示最大得分(512/512=100%,其实就是我拿了其中一张图片的哈希值作为查询条件,相似度为100%),hits为json数组,每一条就是我们存储的一张图片的所有信息,_score就是该图片与我们给的查询条件的相似度得分


分享到:


相關文章: