利用Python實現簡單的類似圖片搜索的教程

大概五年前吧,我那時還在爲一家約會網站作開發工做。他們是早期創業公司,但他們也開始擁有了一些穩定用戶量。不像其餘約會網站,這家公司向來以潔身自好爲主要市場形象。它不是一個供你鬼混的網站——是讓你能找到忠實伴侶的地方。python

因爲投入了數以百萬計的風險資本(在US大蕭條以前),他們關於真愛並找尋靈魂伴侶的在線廣告勢如破竹。Forbes(福布斯,美國著名財經雜誌)採訪了他們。全國性電視節目也對他們進行了專訪。早期的成功促成了事業起步時讓人垂涎的指數級增加現象——他們的用戶數量以每個月加倍的速度增加。對他們而言,一切都彷佛順風順水。git

但他們有一個嚴重的問題——色情問題。github

該約會網站的用戶中會有一些人上傳色情圖片,而後設置爲其我的頭像。這種行爲破壞了不少其餘用戶的體驗——致使不少用戶取消了會員。算法

可能對於如今的一些約會網站隨處可見幾張色情圖片也許並不能稱之爲是問題。或者能夠說是習覺得常甚至有些期待,只是一個被接受而後被無視的在線約會的副產品。數據庫

然而,這樣的行爲既不該該被接受也應該被忽視。bash

別忘了,此次創業但是將本身定位在優秀的約會天堂,免於用戶受到困擾其餘約會網站的污穢和垃圾的煩擾。簡而言之,他們擁有很實在的以風險資本做爲背後支撐的名聲,而這也正是他們須要保持的風格。網絡

該約會網站爲了能迅速阻止色情圖片的爆發能夠說是不顧一切了。他們僱傭了圖片論壇版主團隊,真是不作其餘事只是天天盯着監管頁面8個小時以上,而後移除任何被上傳到社交網絡的色情圖片。app

絕不誇張的說,他們投入了數萬美圓(更不用說數不清的人工小時)來解決這個問題,然而也僅僅只是緩解,控制狀況不變嚴重而不是在源頭上阻止。函數

色情圖片的爆發在2009年的七月達到了臨界水平。8個月來第一次用戶量沒能翻倍(甚至已經開始減小了)。更糟糕的是,投資者聲稱若該公司不能解決這個問題將會撤資。事實上,污穢的潮汐早已開始衝擊這座象牙塔了,將它推翻流入大海也不過是時間問題。oop

正在這個約會網站巨頭快要撐不住時,我提出了一個更魯棒的長期解決方案:若是咱們使用圖片指紋來與色情圖片的爆發鬥爭呢?

你看,每張圖片都有一個指紋。正如人的指紋能夠識別人,圖片的指紋能識別圖片。

這促使了一個三階段算法的實現:

1. 爲不雅圖片創建指紋,而後將圖片指紋存儲在一個數據庫中。

2. 當一個用戶上傳一份新的頭像時,咱們會將它與數據庫中的圖片指紋對比。若是上傳圖片的指紋與數據庫任意一個不雅圖片指紋相符,咱們就阻止用戶將該圖片設置爲我的頭像。

3. 當圖片監管人標記新的色情圖片時,這些圖片也被賦予指紋並存入咱們的數據庫,創建一個能用於阻止非法上傳且不斷進化的數據庫。

咱們的方法,儘管不十分完美,可是也卓有成效。慢慢地,色情圖片爆發的狀況有所減慢。它永遠不會消失——但這個算法讓咱們成功將非法上傳的數量減小了80%以上。

這也挽回了投資者的心。他們繼續爲咱們提供資金支持——直到蕭條到來,咱們都失業了。

回顧過去時,我不由笑了。個人工做並沒持續過久。這個公司也沒有堅持過久。甚至還有幾個投資者捲鋪蓋走人了。

但有同樣確實存活了下來。提取圖片指紋的算法。幾年以後,我把這個算法的基本內容分享出來,指望大家能夠將它應用到大家本身的項目中。

但最大的問題是,咱們怎麼才能創建圖片指紋呢?

繼續讀下去一探究竟吧。
即將要作的事情

咱們打算用圖片指紋進行類似圖片的檢測。這種技術一般被稱爲「感知圖像hash」或是簡單的「圖片hash」。
什麼是圖片指紋/圖片哈希

 

圖片hash是檢測一張圖片的內容而後根據檢測的內容爲圖片創建一個惟一值的過程。

好比,看看本文最上面的那張圖片。給定一張圖片做爲輸入,應用一個hash函數,而後基於圖片的視覺計算出一個圖片hash。類似的圖片也應當有類似的hash值。圖片hash算法的應用使得類似圖片的檢測變得至關簡單了。

特別地,咱們將會使用「差異Hash」或簡單的DHash算法計算圖片指紋。簡單來講,DHash算法着眼於兩個相鄰像素之間的差值。而後,基於這樣的差值,就創建起一個hash值了。
爲何不使用md5,sha-1等算法?

不幸的是,咱們不能在實現中使用加密hash算法。因爲加密hash算法的本質使然,輸入文件中很是微小的差異也能形成差別極大的hash值。而在圖片指紋的案例中,咱們實際上但願類似的輸入能夠有類似的hash輸出值。
圖片指紋能夠用在哪裏?

正如我上面舉的例子,你可使用圖片指紋來維護一個保存不雅圖片的數據庫——當用戶嘗試上傳相似圖片時能夠發出警告。

你能夠創建一個圖片的逆向搜索引擎,好比TinEye,它能夠記錄圖片以及它們出現的相關網頁。

你還可使用圖片指紋幫助管理你我的的照片收集。假設你有一個硬盤,上面有你照片庫的一些局部備份,但須要一個方法刪除局部備份,一張圖片僅保留一份惟一的備份——圖片指紋能夠幫你作到。

簡單來講,你幾乎能夠將圖片指紋/哈希用於任何須要你檢測圖片的類似副本的場景中。
須要的庫有哪些?

爲了創建圖片指紋方案,咱們打算使用三個主要的Python包:

  1. PIL/Pillow用於讀取和載入圖片
  2. ImageHash,包括DHash的實現
  3. 以及NumPy/SciPy,ImageHash的依賴包

你可使用下列命令一鍵安裝所須要的必備庫:
 

?
1
$ pip install pillow imagehash

第一步:爲一個圖片集創建指紋

第一步就是爲咱們的圖片集創建指紋。

也許你會問,但咱們不會,咱們不會使用那些我爲那家約會網站工做時的色情圖片。相反,我建立了一個可供使用的人工數據集。

對計算機視覺的研究人員而言,數據集CALTECH-101 是一個傳奇般的存在。它包含來自101個不一樣分類中的至少7500張圖片,內容分別有人物,摩托車和飛機。

從這7500多張圖片中,我隨機的挑選了17張。

而後,從這17張隨機挑選的圖片中,以幾個百分點的比例隨機放大/縮小並建立N張新圖片。這裏咱們的目標是找到這些近似副本的圖片——有點大海撈針的感受。

你也想建立一個相似的數據集用於工做嗎?那就下載CALTECH-101 數據集,抽取大概17張圖片便可,而後運行repo下的腳本文件gather.py。

迴歸正題,這些圖片除了寬度和高度,其餘各方面都是同樣的。並且由於他們沒有相同的形狀,咱們不能依賴簡單的md5校驗和。最重要的是,有類似內容的圖片可能有徹底不相同的md5哈希。然而,採起圖片哈希,類似內容的圖片也有類似的哈希指紋。

因此趕忙開始寫代碼爲數據集創建指紋吧。建立一個新文件,命名爲index.py,而後開始工做:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# import the necessary packages
from PIL import Image
import imagehash
import argparse
import shelve
import glob
  
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument( "-d" , "--dataset" , required = True ,
help = "path to input dataset of images" )
ap.add_argument( "-s" , "--shelve" , required = True ,
help = "output shelve database" )
args = vars (ap.parse_args())
  
# open the shelve database
db = shelve. open (args[ "shelve" ], writeback = True )

要作的第一件事就是引入咱們須要的包。咱們將使用PIL或Pillow中的Image類載入硬盤上的圖片。這個imagehash庫能夠被用於構建哈希算法。

Argparse庫用於解析命令行參數,shelve庫用做一個存儲在硬盤上的簡單鍵值對數據庫(Python字典)。glob庫能很容易的獲取圖片路徑。

而後傳遞命令行參數。第一個,—dataset是輸入圖片庫的路徑。第二個,—shelve是shelve數據庫的輸出路徑。

下一步,打開shelve數據庫以寫數據。這個db數據庫存儲圖片哈希。更多的以下所示:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# loop over the image dataset
for imagePath in glob.glob(args[ "dataset" ] + "/*.jpg" ):
   # load the image and compute the difference hash
   image = Image. open (imagePath)
   h = str (imagehash.dhash(image))
  
   # extract the filename from the path and update the database
   # using the hash as the key and the filename append to the
   # list of values
   filename = imagePath[imagePath.rfind( "/" ) + 1 :]
   db[h] = db.get(h, []) + [filename]
  
# close the shelf database
db.close()

以上就是大部分工做的內容了。開始循環從硬盤讀取圖片,建立圖片指紋並存入數據庫。

如今,來看看整個範例中最重要的兩行代碼:
 

?
1
2
filename = imagePath[imagePath.rfind( "/" ) + 1 :]
db[h] = db.get(h, []) + [filename]

正如本文早些時候提到的,有相同指紋的圖片被認爲是同樣的。

所以,若是咱們的目標是找到近似圖片,那就須要維護一個有相同指紋值的圖片列表。

而這也正是這幾行代碼作的事情。

前一個代碼段提取了圖片的文件名。然後一個代碼片斷維護了一個有相同指紋值的圖片列表。

爲了從咱們的數據庫中提取圖片指紋並創建哈希數據庫,運行下列命令:
 

?
1
$ python index.py —dataset images —shelve db.shelve

這個腳本會運行幾秒鐘,完成後,就會出現一個名爲db.shelve的文件,包含了圖片指紋和文件名的鍵值對。

這個基本算法正是幾年前我爲這家約會創業公司工做時使用的算法。咱們得到了一個不雅圖片集,爲其中的每張圖片構建一個圖片指紋並將其存入數據庫。當來一張新圖片時,我只需簡單地計算它的哈希值,檢測數據庫查看是否上傳圖片已被標識爲非法內容。

下一步中,我將展現實際如何執行查詢,斷定數據庫中是否存在與所給圖片具備相同哈希值的圖片。
第二步:查詢數據集

 

既然已經創建了一個圖片指紋的數據庫,那麼如今就該搜索咱們的數據集了。

打開一個新文件,命名爲search.py,而後開始寫代碼:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# import the necessary packages
from PIL import Image
import imagehash
import argparse
import shelve
  
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument( "-d" , "--dataset" , required = True ,
   help = "path to dataset of images" )
ap.add_argument( "-s" , "--shelve" , required = True ,
   help = "output shelve database" )
ap.add_argument( "-q" , "--query" , required = True ,
   help = "path to the query image" )
args = vars (ap.parse_args())

咱們須要再一次導入相關的包。而後轉換命令行參數。須要三個選項,—dataset初始圖片集的路徑,—shelve,保存鍵值對的數據庫的路徑,—query,查詢/上傳圖片的路徑。咱們的目標是對於每一個查詢圖片,斷定數據庫中是否已經存在。

如今,寫代碼執行實際的查詢:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# open the shelve database
db = shelve. open (args[ "shelve" ])
  
# load the query image, compute the difference image hash, and
# and grab the images from the database that have the same hash
# value
query = Image. open (args[ "query" ])
h = str (imagehash.dhash(query))
filenames = db[h]
print "Found %d images" % ( len (filenames))
  
# loop over the images
for filename in filenames:
   image = Image. open (args[ "dataset" ] + "/" + filename)
   image.show()
  
# close the shelve database
db.close()

首先打開數據庫,而後載入硬盤上的圖片,計算圖片的指紋,找到具備相同指紋的全部圖片。

若是有圖片具備相同的哈希值,會遍歷這些圖片並展現在屏幕上。

這段代碼使咱們僅僅使用指紋值就能斷定圖片是否已在數據庫中存在。
結果

正如本文早些時候提到的,我從CALTECH-101數據集的7500多張圖片中隨機選取17張,而後經過任意縮放一部分點產生N張新的圖片。

這些圖片在尺寸上僅僅是少數像素不一樣—但也是由於這一點咱們不能依賴於文件的md5哈希(這一點已在「優化算法」部分進行了詳盡的描述)。然而,咱們可使用圖片哈希找到近似圖片。

打開你的終端並執行下述命令:
 

?
1
$ python search.py —dataset images —shelve db.shelve —query images /84eba74d-38ae-4bf6-b8bd-79ffa1dad23a .jpg

若是一切順利你就能夠看到下述結果:

2015423113341305.jpg (690×431)


左邊是輸入圖片。載入這張圖片,計算它的圖片指紋,在數據庫中搜索指紋查看是否存在有相同指紋的圖片。

固然——正如右邊所示,咱們的數據集中有其餘兩張指紋相同的圖片。儘管從截圖中還不能十分明顯的看出,這些圖片,雖然有徹底相同的視覺內容,也不是徹底相同!這三張圖片的高度寬度各不相同。

嘗試一下另一個輸入圖片:
 

?
1
$ python search.py —dataset images —shelve db.shelve —query images /9d355a22-3d59-465e-ad14-138a4e3880bc .jpg

下面是結果:

2015423113456081.jpg (690×431)


左邊仍然是咱們的輸入圖片。正如右邊展現的,咱們的圖片指紋算法可以找出具備相同指紋的三張徹底相同的圖片。

最後一個例子:
 

?
1
$ python search.py —dataset images —shelve db.shelve —query images /5134e0c2-34d3-40

2015423113538460.jpg (690×431)


這一次左邊的輸入圖片是一個摩托車。拿到這張摩托車圖片,計算它的圖片指紋,而後在指紋數據庫中查找該指紋。正如咱們在右邊看到的,咱們也能判斷出數據庫中有三張圖片具備相同指紋。
優化算法

有不少能夠優化本算法的方法——但最關鍵性的是要考慮到類似但不相同的哈希。

好比,本文中的圖片僅僅是一小部分點重組了(依比例增大或減少)。若是一張圖片以一個較大的因素調整大小,或者縱橫比被改變了,對應的哈希就會不一樣了。

然而,這些圖片應該仍然是類似的。

爲了找到類似但不相同的圖片,咱們須要計算漢明距離(Hamming distance).漢明距離被用於計算一個哈希中的不一樣位數。所以,哈希中只有一位不一樣的兩張圖片天然比有10位不一樣的圖片更類似。

然而,咱們遇到了第二個問題——算法的可擴展性。

考慮一下:咱們有一張輸入圖片,又被要求在數據庫中找到全部類似圖片。而後咱們必須計算輸入圖片和數據庫中的每一張圖片之間的漢明距離。

隨着數據庫規模的增加,和數據庫比對的時間也隨着延長。最終,咱們的哈希數據庫會達到一個線性比對已經不實際的規模。

解決辦法,雖然已超出本文範圍,就是利用K-d treesVP trees將搜索問題的複雜度從線性減少到次線性。
總結

本文中咱們學會了如何構建和使用圖片哈希來完成類似圖片的檢測。這些圖片哈希是使用圖片的視覺內容構建的。

正如一個指紋能夠識別一我的,圖片哈希也能惟一的識別一張圖片。

使用圖片指紋的知識,咱們創建了一個僅使用圖片哈希就能找到和識別具備類似內容的圖片的系統。

而後咱們又演示了圖片哈希是如何應用於快速找到有類似內容的圖片。

repo目錄下下載代碼。

 

from: http://www.jb51.net/article/64723.htm

相關文章
相關標籤/搜索