影像搜尋引擎一般有三種實現方式:
(1)Search By Metadata,這種方式不會考慮圖片本身內容(圖片包含物體,以及影像畫素分佈等),純粹根據影像標籤來進行檢索。如果某個網頁中有一張賽馬的圖片,並且網頁文字內容中包含“賽馬”(或者相關詞彙)的文字,當使用者搜尋“賽馬”、“馬”、“horse”等關鍵字時,搜尋引擎就會把這張圖當作檢索結果返回給使用者。換句話說,此時的影像搜尋引擎乾的事情跟普通搜尋引擎差不多,匹配關鍵詞,並將對應圖片返回給使用者。這種工作方式的優點是速度快,在普通搜尋引擎的技術基礎之上很容易改進去實現。缺點也很明顯,它完全依賴於描述圖片的文字(標籤),如果描述圖片的文字不對或者相關性不大時,搜尋準確性可想而知,比如我這篇部落格中如果插入一張“貓”的照片,但是整篇部落格文章對“貓”隻字不提,那麼基於Search By Metadata的搜尋引擎很難找到部落格中貓的圖片。有一類圖片分享網站要求使用者在上傳圖片時,人工用幾個詞彙描述圖片中有什麼(標籤),便於後面基於Metadata的搜尋。當然也不排除一些基於深度學習的圖片分類自動打標籤的方式。
(2)Search By Example,這種方式考慮圖片本身內容(圖片包含物體,以及圖片畫素分佈等等),使用者輸入圖片,搜尋引擎根據圖片內容,返回與該圖片相似的圖片結果。這種方式相比Search By Metadata要複雜一些,當然如果實現恰當,準確性也更能得到保障。如果使用者上傳一張包含“馬”的圖片,那麼搜尋引擎會返回包含馬的其他圖片,或者,當使用者上傳一張“沙灘”的風景圖片,搜尋引擎會返回一堆海邊沙灘的圖片,這些圖片包含類似的內容:沙灘、天空、大海、遊客。Search By Example的方式就是我們熟知的“以圖搜圖”,透過一張圖找出網上相似的其他圖片。實現方式有很多,有基於深度學習的方式,也有傳統的影像分析演算法,後者也是本篇文章後面會詳細介紹的部分。
(3)第三種就是前兩種的結合體,具體就不多說了,技術互補,到達最佳效果。
本篇文章主要介紹利用傳統影像分析演算法如何實現Search By Example的搜尋引擎(以圖搜尋)。
原始碼地址:https://github.com/sherlockchou86/cbir-image-search
影像指紋
人類有指紋,透過指紋對比可以判斷兩個人是否是同一個人,換句話說,指紋可以當作人類獨一無二的識別符號。圖片也有類似的概念,成千上萬的圖片中,每張都有自己的特點,透過某種方式提取它的特徵,可以作為影像對比時的關鍵依據。
影像指紋提取通常稱為“特徵提取”,在程式程式碼中,提取到的影像特徵通常用一個多維向量或者一串64/126位二進位制數字表示。特徵提取的方式有很多種(後面會介紹三類),不管透過哪種方式提取影像特徵,都應該保證:透過特徵對比,我們可以反推原始圖片的相似度。
透過影像特徵評價圖片相似度
假設我們已經為兩張圖片提取到了合適的影像特徵,如何操作我們可以透過特徵來反應原始圖片的相似度呢?一般我們計算兩個特徵之前的距離,透過距離反映原始影像的相似度,如果距離越近,兩張原始圖片相似度更高,反之亦然。
計算距離的方式有很多,需要根據特徵提取的方式來選擇不同的距離計算方法,通常有以下幾個距離計算方法:
(1)歐氏距離。就是歐幾里德距離,這個距離應該是我們最熟悉的,在小學就接觸了。我們在計算直線上兩點的距離就是指歐式距離,上初中時計算平面座標系中兩點的直線距離也是指歐式距離,上高中時計算三維座標系中兩點的直線距離同樣指歐式距離,四維、五維甚至更高維都是這種計算方式。每個點的座標用多維向量表示(a1, a2,..., an)和(b1,b2,...,bn),那麼兩個向量之間的歐式距離為:(a1-b1)**2 + (a2-b2)**2 + ... + (an -bn)**2,然後開方。
(2)漢明距離。這個距離其實普通程式設計師應該也比較熟悉,它指兩個字串(或者兩個位數相同的二進位制數)對應位置不相同的字元個數,漢明距離越大,代表兩個串不相同的字元越多,兩個串相似度越低,反之亦然。如果是兩個二進位制串計算漢明距離(1001010110)和(1001010100),非常簡單,直接使用XOR(異或運算子)即可,1001010110 xor 1001010100,然後計算結果中1的個數。
(3)餘弦距離。餘弦距離一般程式設計師可能用不到,它指兩個向量之間夾角的餘弦值。二維平面座標系中,向量(a1,a2)和向量(b1,b2)之間的夾角餘弦值很好計算,同理,三維、四維也一樣。餘弦距離代表兩個向量中各個分量的佔比程度,比如(3,3)和(5,5)兩個向量的餘弦距離為0,因為每個向量中各個分量佔比都為50%(3/6,3/6 和 5/10,5/10)。同理三維向量(2,3,4)和(4,6,8)之間的餘弦距離也是0。兩個向量的餘弦距離為零,不代表它們之間的歐式距離為零。但是反過來卻成立。
(4)卡方距離。卡方距離主要用於衡量兩個機率分佈的相似性。可以假設兩個多維向量中的每個分量都代表機率(分量的和為1),那麼卡方距離就是計算這兩個機率分佈的相似度。具體內容請網路搜尋。
以圖搜圖實現原理
以圖搜圖的過程其實很簡單,跟普通的檢索流程差不多。關鍵有兩個:
(1)一個是特徵提取方法,這個是重點,如何合理提取影像特徵是保證準確性的第一要素;
(2)二個就是如何提高特徵對比的速度,成千上萬張圖片源,如何快速從中找出相同/相似的圖片?
下面介紹三種傳統影像特徵提取的方式。
影像特徵點
任何一張數字圖片,微觀上看,畫素之前總是能找到一些規律的,畫素分佈、畫素值、畫素密度等等。OpenCV內建很多特徵點提取演算法,比如ORB、SURF以及SIFT等等。以SIFT舉例,輸入圖片,輸出該圖片中具有某些特徵的點(可以存在多個),從一定程度上講,這些點可以被當作圖片的標識,每個特徵點用一個128維的向量表示。如果要比較兩張圖片的相似度,先分別提取兩張圖片的SIFT特徵點,然後進行特徵點匹配,看有多少對特徵點能夠匹配上,如果能匹配的點超過一定數量,那麼認為這兩張圖片相似。這個匹配的過程可能採用前面提到的各種距離比較。下面兩張圖透過特徵點提取、特徵點匹配後得到的結果,可以判斷兩張圖片相似:
可以看到,雖然圖片拍攝角度不一樣,但是包含的物體一致、場景類似,透過特徵點匹配,可以找到很多匹配到的特徵點。
這裡的影像特徵用128維向量表示,可以包含多個,如果一張圖找到了100個特徵點,那麼每張圖有128*100個float資料需要儲存。
影像感知雜湊
雜湊演算法、雜湊函式我們經常聽說,對於不同長度的輸入,雜湊演算法可以產生固定長度的輸出。那麼我們能否直接透過計算圖片檔案的雜湊值來判斷圖片是否相似/相同呢?答案是不能。這種方式計算出來的雜湊值對評價原始圖片相似度沒有任何參考價值,因為即使是相似的圖片透過這種方式計算出來的雜湊值之間並不相似(可能相差十萬八千里),換句話說,我們不能透過雜湊值之間的距離來反推原始圖片的相似度是多少。假如在一張圖片上修改了一點點,最後生成的雜湊值跟原來的雜湊值相差很遠。
因此我們需要用到影像感知雜湊演算法,這種方法將圖片內容(更準確地講,應該是畫素)考慮進來了。透過這種方式計算得到的雜湊值可以反推出原始圖片之間的相似度。影像感知雜湊演算法常見分三類:ahash,phash,dhash。計算方式基本差不多,下面以ahash為例說明如何計算:
(1)縮放圖片。直接將原始圖片縮小到8*8的尺寸,不用考慮原始圖片的長寬比;
(2)灰度化。將8*8的圖片灰度化,這樣處理後,這張圖片一共包含64個畫素;
(3)計算平均值。計算這64個畫素值的平均值;
(4)構建雜湊。將64個畫素值依次與(3)中的平均值進行比較,大於平均值為1,否則為0,這樣從左到右、從上到下就可以組合得到一個64位的二進位制數(順序無所謂,只要保證都按這個順序即可)。這個二進位制串就是原始圖片計算出來的ahash值,格式為110010001...001001,一共64位,將其轉換成16進位制,得到的格式為8f373714acfcf4d0。
如何比較相似度?很簡單,計算兩張圖ahash值的漢明距離,如果距離(不相同的位數)超過10則認為兩張原始圖片不相似,小於10表示相似,具體閾值需要調整。這種方式非常簡單並且很好實現,而且很湊效,尤其是在一堆圖片中找相同圖片的時候(縮放無所謂),非常適合用縮圖查詢原始圖。
三種感知雜湊計算方式:https://github.com/JohannesBuchner/imagehash
三種感知雜湊原理說明:http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
三種感知雜湊區別和優劣勢請參照網路。
這裡的影像特徵用64位二進位制表示,每張圖片的特徵需要8個位元組儲存。
基於區域的顏色直方圖
顏色直方圖統計整張圖片中各種畫素的佔比情況,嚴格來講它並不考慮顏色分佈在圖片中的位置。而基於區域的顏色直方圖在計算畫素佔比時,先將整張圖片劃分成不同的子區域,然後依次計運算元區域的顏色佔比情況,最後考慮子區域合併之後的結果。這種方式彌補了之前忽略顏色分佈位置的缺陷,透過直方圖資料評價圖片的相似度更加可靠。
如上圖,將原始圖片劃分成5個子區域,依次計算每個子區域的顏色直方圖資料。圖片是RGB三通道,每個通道依次分成(8,3,12)個區間,那麼每個子區域顏色直方圖就可以使用8*3*12=288維向量表示。而我們又將原始圖片劃分成了5個子區域,那麼整張圖的特徵就需要用 8*3*12*5 = 1440維向量來表示了。
這裡如何計算直方圖特徵距離呢?由於這裡288維向量中每個分量代表對應區間畫素出現的機率,那麼距離計算就要用到前面提到過的卡方距離。透過卡方距離公式計算兩個288維特徵向量之間的距離,從而反推原始圖片之間的相似度。
注意:使用顏色直方圖的方式提取影像特徵的前提是:接受“如果兩張圖片顏色分佈差不多,則認為它們相似”這一假設,這一假設不考慮圖片中具體包含內容,比如包含馬、狗、人之類的目標,而只考慮顏色。事實上,經過實踐,大部分時候該假設確實成立。
如何提高查詢速度
給定一個影像特徵,如何從一堆影像特徵中快速找到與之相同、或者與之距離最近的N個、或者與之距離在M之內的所有特徵呢?一個個的比較肯定不可接受,我們需要提前給這些特徵建立索引,提高查詢的速度。
VP-Tree是一種很好的資料結構,能夠解決緊鄰搜尋的問題,這裡是它的Python實現:https://github.com/RickardSjogren/vptree,它能夠先用影像特徵構建出一個二叉樹,提高查詢速度。具體原理請參照網路。
感知雜湊+基於區域的顏色直方圖demo
最後有一個demo,基於影像感知雜湊(ahash、phash以及dhash)和基於區域的顏色直方圖,完成了一個簡單的圖片搜尋demo。服務端採用Python、Flask開發。
原始碼地址:https://github.com/sherlockchou86/cbir-image-search