海量數據下以圖搜圖實現方案

以圖搜圖原理

海量數據下以圖搜圖實現方案

圖文無關

首先介紹一下以圖搜圖的實現原理,弄明白我們是怎樣將一張圖片轉化為可以量化計算的哈希值。

我們知道圖片本身是二進制數據,是一系列像素值的集合,一張彩色圖片可以用[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就是該圖片與我們給的查詢條件的相似度得分


分享到:


相關文章: