在google上面搜索圖像識(shí)別的時(shí)候,搜到一篇好文章,在這里翻譯一下,所有版權(quán)來(lái)源于https://realpython.com/blog/python/fingerPRinting-images-for-near-duplicate-detection/,作者源代碼:https://github.com/realpython/image-fingerprinting/blob/master/code/output.csv雖然英語(yǔ)過(guò)了六級(jí),但是仍然有很多專(zhuān)業(yè)術(shù)語(yǔ)不懂,翻譯水平也只能是湊合而已。
注:本文寫(xiě)作的目的是這篇文章的作者發(fā)現(xiàn)建立網(wǎng)站的時(shí)候,很多用戶(hù)用相同的頭像,這導(dǎo)致識(shí)別度降低,為了防止用戶(hù)上傳相同的圖片作為自己的頭像以及上傳不當(dāng)?shù)膱D像文件,作者研究了這個(gè)圖像指紋的問(wèn)題。(等我翻譯完成竟然發(fā)現(xiàn)網(wǎng)上已經(jīng)有人翻譯好了http://python.jobbole.com/81277/,自愧不如)
不妨想一想,每一個(gè)人都有屬于他自己的指紋一樣,指紋能夠識(shí)別人,那么圖片的指紋也可以用來(lái)識(shí)別圖片。
現(xiàn)在用三個(gè)階段來(lái)實(shí)現(xiàn)算法:
1.將一組不合適的圖像指紋計(jì)算出來(lái),然后將這些圖像指紋存入數(shù)據(jù)庫(kù)中。
2.當(dāng)用戶(hù)上傳一個(gè)新的客戶(hù)資料中的頭像時(shí),我們將會(huì)將其與數(shù)據(jù)庫(kù)中的圖像指紋進(jìn)行對(duì)比。如果發(fā)現(xiàn)數(shù)據(jù)庫(kù)中任意一個(gè)頭像指紋與該用戶(hù)上傳的圖像符合,管理員將會(huì)阻止用戶(hù)上傳該圖像作為自己的頭像。
3.依次類(lèi)推,識(shí)別出色情圖片,根據(jù)色情圖片的指紋創(chuàng)建一個(gè)收集色情圖片指紋的數(shù)據(jù)庫(kù),用來(lái)防止用戶(hù)上傳色情圖片。
我們的程序并不是完美的,有效的。即使效率緩慢但是目的終究是達(dá)到了,雖然沒(méi)有完全的解決問(wèn)題,但是減少了用戶(hù)上傳的80%以上的不當(dāng)文件。
那么接下來(lái)最大的問(wèn)題是如何創(chuàng)建圖像指紋?
請(qǐng)繼續(xù)閱讀并找出答案。。。
我們接下來(lái)需要做什么?
我們將利用圖像指紋進(jìn)行重復(fù)識(shí)別,一般稱(chēng)這種技術(shù)為“圖像感知哈希法”或者“圖像哈希法”。
什么是圖像指紋或者說(shuō)什么是圖像哈希?
圖像哈希的過(guò)程是通過(guò)檢測(cè)圖像的內(nèi)容,然后構(gòu)造一個(gè)值,該值在這些圖像內(nèi)容的基礎(chǔ)上唯一的標(biāo)志這個(gè)圖像
如下圖所示:

在給定的輸入的圖像中, 我們將使用一個(gè)散列函數(shù),并基于圖像視覺(jué)上的外觀計(jì)算它的“圖像散列”值,相似的頭像,它的散列值應(yīng)該也是相似的。使用圖像哈希算法可以大大簡(jiǎn)化重復(fù)圖像檢測(cè)的程序。
在這里,我們將使用“difference hash”,或者僅僅使用dHash算法來(lái)計(jì)算我們的圖像指紋。簡(jiǎn)單來(lái)說(shuō),dHash著力探究相鄰像素之間的區(qū)別。哈希值是在這個(gè)基礎(chǔ)上被創(chuàng)建出來(lái)的。
為什么我們不直接使用md5,sha-1等等算法?
不幸的是,我們不能在本例中使用加密散列算法。這是由于加密散列算法本身的性質(zhì)——輸入文件中非常微小的變化都會(huì)造成顯著不同的哈希值。在本次圖像指紋的案例中,是希望類(lèi)似圖片的輸入能得到類(lèi)似哈希值的輸出。
圖像指紋能應(yīng)用在哪些領(lǐng)域?
就如同上面的例子,你可以用圖像指紋來(lái)管理你的不合適的圖像數(shù)據(jù)庫(kù),當(dāng)用戶(hù)嘗試上傳此類(lèi)圖像的時(shí)候你可以提醒他們。
你可以建立一個(gè)類(lèi)似于TinEye的搜索引擎,用來(lái)跟蹤圖片,并且找出類(lèi)似圖片出現(xiàn)的網(wǎng)頁(yè)。
你甚至可以通過(guò)使用圖像指紋識(shí)別來(lái)管理你的個(gè)人相集。想象一下,你有一個(gè)存儲(chǔ)你個(gè)人圖片的硬盤(pán),但是需要一種方法備份部分修剪的圖片,并能夠保持唯一的副本——圖像指紋可以幫你做到。
簡(jiǎn)單來(lái)說(shuō),你可以在任何與圖像重復(fù)副本檢測(cè)的地方使用圖像指紋或者圖像散列方法。
我們需要哪些庫(kù)?
為了制作我們的圖像指紋識(shí)別方案,將會(huì)用的以下三個(gè)python庫(kù):
你也可以通過(guò)如下命令搭建所有環(huán)境(python2.7):
$ pip install pillow imagehash
第一步:創(chuàng)建圖像指紋數(shù)據(jù)集
我們并不打算使用我日常在約會(huì)網(wǎng)站上遇到的色情圖片,我已經(jīng)找到了一個(gè)我們可以使用的數(shù)據(jù)集。
對(duì)于計(jì)算機(jī)視覺(jué)研究人員來(lái)講,CALTECH-101數(shù)據(jù)集是個(gè)傳奇。它包含7500+張來(lái)自101個(gè)目錄的圖片,包括人、摩托車(chē)和飛機(jī)。
我從這7500張圖片中隨機(jī)抽取了17張。
然后從這17張隨機(jī)抽選的圖片中,我通過(guò)隨機(jī)調(diào)整圖片的尺寸創(chuàng)建了N個(gè)新的圖像。我們的目標(biāo)是找到這些近乎重復(fù)的圖像,這就像是大海撈針。
并且,由于圖片除了寬高尺寸外都是相同的,由于他們都沒(méi)有相同的尺寸,我們不能簡(jiǎn)單的使用MD5校驗(yàn),更重要的是內(nèi)容類(lèi)似的圖片可能具有顯著不同的哈希值,原因已解釋?zhuān)喾?,我們可以使用圖像散列,因?yàn)轭?lèi)似的圖片擁有相似的哈希指紋。
那么現(xiàn)在開(kāi)始編寫(xiě)關(guān)于數(shù)據(jù)集的代碼,并將其命名為index.py:
1 # coding=utf-8 2 # 導(dǎo)入必要的包 3 import argparse 4 import shelve
import imagehash 5 import glob 6 from PIL import Image 7 8 # 構(gòu)建參數(shù)解析,并分析參數(shù) 9 ap = argparse.ArgumentParser()10 ap.add_argument("-d", "--dataset", required=True,11 help="照片數(shù)據(jù)集的路徑")12 ap.add_argument("-s", "--shelve",required=True,13 help="shelve數(shù)據(jù)集的輸出")14 args = vars(ap.parse_args())15 16 # 打開(kāi)shelve數(shù)據(jù)集17 db = shelve.open(args["shelve"], writeback=True)
首先,我們要做的就是導(dǎo)入我們需要的包。我們將使用PIL或者Pillow模塊中的Image類(lèi)從磁盤(pán)中加載圖片。然后用圖像指紋庫(kù)來(lái)構(gòu)建感性序列。
根據(jù)上面的代碼可知,argparse用于解析命令行參數(shù),shelve用來(lái)做python的子典型數(shù)據(jù)庫(kù),并將其存儲(chǔ)在磁盤(pán)上,glob將用來(lái)更加輕易的收集圖片的路徑位置。
接著,分析命令行參數(shù)。第一,--dataset是我們輸入圖像的路徑目錄。第二,--shelve是通往shelve數(shù)據(jù)庫(kù)的輸出路徑。
接下來(lái),我們打開(kāi)shelve數(shù)據(jù)庫(kù)并對(duì)其進(jìn)行寫(xiě)入。db將會(huì)存儲(chǔ)我們的圖像哈希值。代碼如下:
1 # 在圖像數(shù)據(jù)集中循環(huán) 2 for imagePath in glob.glob(args["dataset"] + "/*.jpg"): 3 # 加載圖片并計(jì)算哈希值的差異 4 image = Image.open(imagePath) 5 h = str(imagehash.dhash(image)) 6 7 # 提取路徑中的文件名并更新數(shù)據(jù)庫(kù) 8 # 用散列作為字典的鍵,文件名添加到值列表 9 filename = imagePath[imagePath.rfind("/") + 1:]10 db[h] = db.get(h, []) + [filename]11 12 # 關(guān)閉shelf數(shù)據(jù)集13 db.close()
這些就是我們大致需要的代碼工作了,從磁盤(pán)中加載圖像,并遍歷圖像數(shù)據(jù)集,然后創(chuàng)建圖像指紋。
現(xiàn)在我們觀察以下整個(gè)教程中最重要的兩條代碼:
1 filename = imagePath[imagePath.rfind("/") + 1:]2 db[h] = db.get(h, []) + [filename]
就像我一開(kāi)始在文章中提到的一樣,具有相同指紋的圖像被認(rèn)為是同樣的圖像。
因此,如果我們的目標(biāo)是找出相似圖片,我們需要?jiǎng)?chuàng)建一個(gè)擁有相同指紋值的圖像列表。
以上兩行代碼做的就是這個(gè)工作。
第一行提取圖像的文件名,第二行給圖像創(chuàng)建一個(gè)擁有相同哈希值的列表。
從數(shù)據(jù)庫(kù)中提取圖像指紋并創(chuàng)建我們的散列數(shù)據(jù)庫(kù),使用如下命令:
$ python index.py —dataset images —shelve db.shelve
該腳本將會(huì)運(yùn)行幾秒鐘,一旦完成就好產(chǎn)生一個(gè)文件,里面包含了圖像指紋—文件名對(duì)應(yīng)的鍵值對(duì)。
這個(gè)算法和我?guī)啄昵霸趧?chuàng)建約會(huì)網(wǎng)站時(shí)寫(xiě)的算法一樣,我們將不適合的圖片收集起來(lái),并計(jì)算他們的散列值,存入數(shù)據(jù)庫(kù)。當(dāng)用戶(hù)提交圖像的時(shí)候,我只有計(jì)算圖像的指紋,并將其與數(shù)據(jù)庫(kù)中的指紋進(jìn)行對(duì)比,用來(lái)判斷是否上傳被判無(wú)效的內(nèi)容。
下一步我將會(huì)告訴你如何執(zhí)行搜索,以確定圖片是否在數(shù)據(jù)庫(kù)中有類(lèi)似的散列值的圖像。
第二步:搜索數(shù)據(jù)庫(kù)
既然我們已經(jīng)創(chuàng)建了一個(gè)指紋圖像數(shù)據(jù)庫(kù),是時(shí)候來(lái)搜索數(shù)據(jù)庫(kù)了。
打開(kāi)一個(gè)名字為search.py的新文件,開(kāi)始編寫(xiě)代碼:
1 # coding=utf-8 2 # 導(dǎo)入必要的包 3 from PIL import Image 4 import imagehash 5 import argparse 6 import shelve 7 8 # 構(gòu)建參數(shù)解析,并分析參數(shù) 9 ap = argparse.ArgumentParser()10 ap.add_argument("-d", "--dataset", required=True,11 help="照片數(shù)據(jù)集的路徑")12 ap.add_argument("-s", "--shelve",required=True,13 help="shelve數(shù)據(jù)集的輸出")14 ap.add_argument("-q", "--query", required=True,15 help="搜索圖像的路徑")16 args = vars(ap.parse_args())
像上次一樣,導(dǎo)入我們需要的包,然后解析命令行參數(shù)。接下來(lái)需要三次轉(zhuǎn)換,--dataset,它是原始圖片數(shù)據(jù)集的路徑,--shelve,存放鍵值對(duì)的數(shù)據(jù)庫(kù),以及--query,搜索或者上傳圖片的路徑。我們的目標(biāo)是根據(jù)上傳的圖片對(duì)數(shù)據(jù)庫(kù)進(jìn)行搜索類(lèi)似哈希值的圖片。
接下來(lái),寫(xiě)關(guān)于執(zhí)行搜索的代碼:
1 # 打開(kāi)shelf數(shù)據(jù)集 2 db = shelve.open(args["shelf"]) 3 4 # 加載需要查詢(xún)的圖片,計(jì)算它的圖像差分散列值,并從數(shù)據(jù)庫(kù)中抓取類(lèi)似散列值得圖像 5 query = Image.open(args["query"]) 6 h = str(imagehash.dhash(query)) 7 filenames = db[h] 8 print("Found %d images" % (len(filenames))) 9 10 # 在圖像內(nèi)部循環(huán)11 for filename in filenames:12 image = Image.open(args["dataset"] + "/" + filename)13 image.show()14 15 # 關(guān)閉數(shù)據(jù)集16 db.close()
我們先打開(kāi)數(shù)據(jù)庫(kù),并從磁盤(pán)中加載圖片,計(jì)算圖像指紋,找出擁有相同指紋值得所有圖片。
如果存在任何相同哈希值的圖片,將會(huì)在圖片中循環(huán),依次展示這些圖片。
使用這些代碼,我們將會(huì)判斷是否上傳的圖片已經(jīng)存在于數(shù)據(jù)庫(kù)中。
結(jié)果
正如我在文章前段部分提到的,我已經(jīng)從CALTECH-101數(shù)據(jù)集中隨機(jī)采集了17張圖片,并通過(guò)一些小的尺寸上面的改動(dòng)創(chuàng)建了N張新圖片。
這些圖片的尺寸造成只有小部分的像素不同,因此不能使用MD5哈希算法(這一點(diǎn)將會(huì)在算法改進(jìn)中繼續(xù)探討)。想法我們需要利用圖像散列找到類(lèi)似的圖像。
打開(kāi)終端,并執(zhí)行以下命令:
$ python search.py —dataset images —shelve db.shelve —query images/84eba74d-38ae-4bf6-b8bd-79ffa1dad23a.jpg
如果不報(bào)錯(cuò)的話(huà)將會(huì)出現(xiàn)以下結(jié)果:

上面圖片的左邊就是輸入的圖像,我們將用這張圖片對(duì)數(shù)據(jù)集進(jìn)行搜索,找出擁有相同指紋的所有圖像。
值得肯定的是,在我們的數(shù)據(jù)集中有兩張圖片具有相同的指紋,如圖像右邊的兩張圖片所示。雖然從截圖上面查看并不很明顯,但是他們確實(shí)是具有相同內(nèi)容的不同尺寸的圖片。
讓我們嘗試輸入另外一張圖片:
$ python search.py —dataset images —shelve db.shelve —query images/9d355a22-3d59-465e-ad14-138a4e3880bc.jpg
結(jié)果如下:

perfect!
改進(jìn)算法
有很多方法可以改進(jìn)我們的算法,但是最重要的一種方法時(shí)考慮到散列是相似的并不是完全一樣的。
比如說(shuō),我們這次提交的圖片都是在尺寸上(向上或向下)調(diào)整了幾個(gè)百分點(diǎn)的大小,如果圖像調(diào)整比較大的話(huà),導(dǎo)致縱橫比改變,散列值將不會(huì)完全相同。
但是圖片將是類(lèi)似的。
為了找出相似并不相同的圖片,我們需要進(jìn)一步用到Hamming距離法。Hamming距離法可以計(jì)算出不同哈希值的像素位數(shù)。因此,兩個(gè)具有一位像素相差的圖像基本上比10位相差的的圖新更相似。
但是我們遇到第二個(gè)問(wèn)題,算法的擴(kuò)展性。
試想一下,有一張被輸入的圖像,并需要找到數(shù)據(jù)庫(kù)中所有類(lèi)似的圖像。那么我們可以計(jì)算輸入圖像和每一個(gè)數(shù)據(jù)庫(kù)圖像的Hamming距離。
隨著數(shù)據(jù)集的增大,將會(huì)導(dǎo)致更多的時(shí)間比較所有的哈希值。最后,我們的散列數(shù)據(jù)庫(kù)將會(huì)到達(dá)一定的規(guī)模以至于單純的線(xiàn)性比較是不實(shí)際的。
有一個(gè)解決方案就是,使用Kd樹(shù)分類(lèi)法或者VP樹(shù)分類(lèi),從線(xiàn)性搜索變成亞線(xiàn)性,減少搜索問(wèn)題的復(fù)雜性。
總結(jié)
在這篇文章中,我們學(xué)會(huì)了如何構(gòu)建和利用圖像散列法執(zhí)行近似圖像的檢測(cè)。圖像散列應(yīng)用在圖像視覺(jué)內(nèi)容研究中。
正如指紋可以識(shí)別人一樣,一個(gè)圖像的哈希值也可以唯一的標(biāo)志圖像。
使用我們的質(zhì)問(wèn)圖像知識(shí),再建立一個(gè)查找類(lèi)似圖像的系統(tǒng)無(wú)非是用的圖像的哈希算法。
編后語(yǔ)
ok,這就是昨天晚上和今天上午所翻譯的東西了,翻譯這篇博客讓我認(rèn)識(shí)到了自己語(yǔ)法的不足,例如數(shù)據(jù)庫(kù)的載入以及參數(shù)的解析的不熟悉等等。也使得我的心能夠在五一這種熱鬧的氣氛下安靜下來(lái)翻譯文章,分析算法,并從國(guó)際友人的字里行間感受他的熱情和思想,再次感謝作者。話(huà)說(shuō),今天用的流量出校器一直在燒流量,不知道是什么原因,畢竟昨天用的時(shí)候沒(méi)問(wèn)題,難道谷歌FQ需要消耗大量的流量?還是本身插件具有一定的吸附流量能力?估計(jì)最不可能的是我的電腦被人黑了吧,oh,my god!
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注