利用Python實現簡單的相似圖片搜尋
寫作本文的目是發現建立網站的時候,很多使用者用相同的頭像,這導致識別度降低,為了防止使用者上傳相同的圖片作為自己的頭像以及上傳不當的影象檔案,作者研究了這個影象指紋的問題。
每個人都有屬於他自己的指紋,指紋能夠識別人,那麼圖片的指紋也可以用來識別圖片。這促使了一個三階段演算法的實現:
1. 為圖片建立指紋,然後將圖片指紋儲存在一個數據庫中。
2. 當一個使用者上傳一份新的頭像時,我們會將它與資料庫中的圖片指紋對比。如果上傳的圖片的指紋與資料任意一個圖片指紋相符,我們就阻止使用者將該圖片設定為個人頭像。
3. 當圖片監管人標記新的圖片時,這些圖片也被賦予指紋並存入我們的資料庫,建立一個能用於阻止使用與庫內相同圖片且不斷進化的資料庫。
現在,我把這個演算法的基本內容分享出來,期望可以將它應用到你們自己的專案中。
但最大的問題是,我們怎麼才能建立圖片指紋呢?繼續讀下去一探究竟吧。
即將要做的事情
我們打算用圖片指紋進行相似圖片的檢測。這種技術通常被稱為“感知影象hash”或是簡單的“圖片hash”。 【參閱《感知影象hash》,問題:為什麼hash是圖片的唯一?】
什麼是圖片指紋/圖片雜湊
圖片hash是檢測一張圖片的內容然後根據檢測的內容為圖片建立一個唯一值的過程。
比如,看看本文最上面的那張圖片。給定一張圖片作為輸入,應用一個hash
特別地,我們將會使用“差別Hash”或簡單的DHash演算法計算圖片指紋。簡單來說,DHash演算法著眼於兩個相鄰畫素之間的差值。然後,基於這樣的差值,就建立起一個hash值了。
【閱讀:DHash演算法計算圖片指紋】
為什麼不使用md5,sha-1等演算法?
不幸的是,我們不能在實現中使用加密hash演算法。由於加密hash演算法的本質使然,輸入檔案中非常微小的差別也能造成差異極大的hash值。而在圖片指紋的案例中,我們實際上希望相似的輸入可以有相似的hash
圖片指紋可以用在哪裡?
1、正如我上面舉的例子,你可以使用圖片指紋來維護一個儲存不雅圖片的資料庫——當用戶嘗試上傳類似圖片時可以發出警告。
2、你可以建立一個圖片的逆向搜尋引擎,比如TinEye,它可以記錄圖片以及它們出現的相關網頁。
3、你還可以使用圖片指紋幫助管理你個人的照片收集。假設你有一個硬碟,上面有你照片庫的一些區域性備份,但需要一個方法刪除區域性備份,一張圖片僅保留一份唯一的備份——圖片指紋可以幫你做到。
簡單來說,你幾乎可以將圖片指紋/雜湊用於任何需要你檢測圖片的相似副本的場景中。
需要的庫有哪些?
為了建立圖片指紋方案,我們打算使用三個主要的Python包:
你可以使用下列命令一鍵安裝所需要的必備庫:
pip install pillow imagehash第一步:為一個圖片集建立指紋
第一步就是為我們的圖片集建立指紋。
資料集collect包含n多張圖片,我隨機的挑選了幾張。然後,從這幾張隨機挑選的圖片中,以幾個百分點的比例隨機放大/縮小並建立N張新圖片。這裡我們的目標是找到這些近似副本的圖片——有點大海撈針的感覺。這些圖片除了寬度和高度,其他各方面都是一樣的。採取圖片雜湊,相似內容的圖片也有相似的雜湊指紋。
所以趕緊開始寫程式碼為資料集建立指紋吧。建立一個新檔案,命名為index.py,然後開始工作:
# coding=utf-8
# 匯入必要的包
import argparse
import shelve
import imagehash
import glob
from PIL import Image
# 構建引數解析,並分析引數
ap =argparse.ArgumentParser()
ap.add_argument("-d","--dataset", required=True, help="照片資料集的路徑")
ap.add_argument("-s","--shelve",required=True, help="shelve資料集的輸出")
args =vars(ap.parse_args())
# 開啟shelve資料集
db = shelve.open(args["shelve"],writeback=True)
要做的第一件事就是引入我們需要的包。我們將使用PIL或Pillow中的Image類載入硬碟上的圖片。這個imagehash庫可以被用於構建雜湊演算法。Argparse庫用於解析命令列引數,shelve庫用作一個儲存在硬碟上的簡單鍵值對資料庫(Python字典)。glob庫能很容易的獲取圖片路徑。然後傳遞命令列引數。第一個,--dataset是輸入圖片庫的路徑。第二個,--shelve是shelve資料庫的輸出路徑。
下一步,開啟shelve資料庫db以寫資料,這個db資料庫儲存圖片雜湊。更多的如下所示:
# 在影象資料集中迴圈
for imagePath in glob.glob(args["dataset"] + "/*.jpg"):
# 載入圖片並計算雜湊值的差異
image =Image.open(imagePath)
h = str(imagehash.dhash(image))
# 提取路徑中的檔名並更新資料庫
# 用雜湊作為字典的鍵,檔名新增到值列表
filename = imagePath[imagePath.rfind("/") + 1:]
db[h] = db.get(h, []) + [filename]
# 關閉shelf資料集
db.close()
以上就是大部分工作的內容了。開始迴圈從硬碟讀取圖片,建立圖片指紋並存入資料庫。
現在,來看看整個範例中最重要的兩行程式碼:
1 2 |
filename = imagePath[imagePath.rfind("/") + 1:] db[h] = db.get(h, []) + [filename] |
正如本文提到的,有相同指紋的圖片被認為是一樣的。
因此,如果我們的目標是找到近似圖片,那就需要維護一個有相同指紋值的圖片列表。
而這也正是這幾行程式碼做的事情。
前一個程式碼段提取了圖片的檔名。而後一個程式碼片段維護了一個有相同指紋值的圖片列表。
為了從我們的資料庫中提取圖片指紋並建立雜湊資料庫,命令提示符下執行下列命令:
python index.py -d image -s db這個指令碼會執行幾秒鐘,完成後,就會出現一個名為db的幾個相關檔案,它包含了圖片指紋和檔名的鍵值對。
我們獲得了一個圖片集,為其中的每張圖片構建一個圖片指紋並將其存入資料庫。當來一張新圖片時,我只需簡單地計算它的雜湊值,檢測資料庫檢視是否上傳圖片已被標識為非法內容。
下一步中,我將展示實際如何執行查詢,判定資料庫中是否存在與所給圖片具有相同雜湊值的圖片。
第二步:查詢資料集
既然已經建立了一個圖片指紋的資料庫,那麼現在就該搜尋我們的資料集了。
開啟一個新檔案,命名為search.py,然後開始寫程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 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(filename) print(image)#為了便於觀察,這裡打印出來 image.show() # close the shelve database db.close() |
首先開啟資料庫,然後載入硬碟上的圖片,計算圖片的指紋,找到具有相同指紋的所有圖片。
如果有圖片具有相同的雜湊值,會遍歷這些圖片並展示在螢幕上。
這段程式碼使我們僅僅使用指紋值就能判定圖片是否已在資料庫中存在。
結果
正如本文早些時候提到的,我從collect資料集的n多張圖片中隨機選取幾張,然後通過任意縮放一部分點產生幾張新的圖片。
這些圖片在尺寸上僅僅是少數畫素不同—但也是因為這一點我們不能依賴於檔案的md5雜湊(這一點已在“優化演算法”部分進行了詳盡的描述)。然而,我們可以使用圖片雜湊找到近似圖片。
開啟你的終端並在命令提示符下執行下述命令:
python search.py -d image -s db -q 1.jpg如果一切順利你就可以看到下述結果:
執行後的結果:
優化演算法
有很多可以優化本演算法的方法——但最關鍵性的是要考慮到相似但不相同的雜湊。
比如,本文中的圖片僅僅是一小部分點重組了(依比例增大或減小)。如果一張圖片以一個較大的因素調整大小,或者縱橫比被改變了,對應的雜湊就會不同了。
然而,這些圖片應該仍然是相似的。
為了找到相似但不相同的圖片,我們需要計算漢明距離(Hammingdistance).漢明距離被用於計算一個雜湊中的不同位數。因此,雜湊中只有一位不同的兩張圖片自然比有10位不同的圖片更相似。
然而,我們遇到了第二個問題——演算法的可擴充套件性。
考慮一下:我們有一張輸入圖片,又被要求在資料庫中找到所有相似圖片。然後我們必須計算輸入圖片和資料庫中的每一張圖片之間的漢明距離。
隨著資料庫規模的增長,和資料庫比對的時間也隨著延長。最終,我們的雜湊資料庫會達到一個線性比對已經不實際的規模。
解決辦法,雖然已超出本文範圍,就是利用和將搜尋問題的複雜度從線性減小到次線性。
總結
本文中我們學會了如何構建和使用圖片雜湊來完成相似圖片的檢測。這些圖片雜湊是使用圖片的視覺內容構建的。
正如一個指紋可以識別一個人,圖片雜湊也能唯一的識別一張圖片。
使用圖片指紋的知識,我們建立了一個僅使用圖片雜湊就能找到和識別具有相似內容的圖片的系統。
然後我們又演示了圖片雜湊是如何應用於快速找到有相似內容的圖片。
環境說明:本程式在python3.5下執行的,我將檔案全部放在d:/code下,包括index.py、search.py、1.jpg,以及圖片資料夾image。image下面有5張照片image_0001.jpg、image_0002.jpg、image_0011.jpg、image_0047.jpg和image_0055.jpg,其中1.jpg、image_0001.jpg和image_0002.jpg是相同的圖片,僅僅是檔名不一樣。
code下載:http://pan.baidu.com/s/1skSHcBV