
給你10萬張圖片,讓你找出與其中某張圖片最為近似的10張,你會怎么做?不要輕言放棄,也不用一張張瀏覽。使用Python,你也可以輕松搞定這個任務(wù)。
疑問
《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》一文寫完后,我收到了不少讀者的反饋。其中一個很普遍的疑問是:
識別相同或相似的圖像,有什么好的方法么?

我雖然樂于幫助讀者解決問題,但實話實說,一開始不太理解這種需求。
我文章里的樣例圖片(哆啦a夢和瓦力),都是從網(wǎng)絡(luò)搜集來的。如果你需要從網(wǎng)上找到跟某張圖片近似的圖像,可以使用Google的“以圖搜圖”功能啊。

很快,我突然醒悟過來。
這種需求,往往不是為了從互聯(lián)網(wǎng)上大海撈針,尋找近似圖片。而是在一個私有海量圖片集合中,找到近似圖像。
這種圖片集合,也許是你團(tuán)隊的科研數(shù)據(jù)。例如你研究鳥類。某天瀏覽野外拍攝設(shè)備傳回來的圖像時,突然發(fā)現(xiàn)一個新奇品種。

你于是很想搞清楚這種鳥類的出現(xiàn)時間、生活狀態(tài)等。這就需要從大量圖片里,找到與其近似的圖片(最有可能是拍到了同一種鳥)。
這種圖片集合,也許是社會安全數(shù)據(jù)。例如你在反恐部門,系統(tǒng)突然發(fā)現(xiàn)某個疑似恐怖分子出現(xiàn)在敏感區(qū)域。這家伙每一次現(xiàn)身,都伴隨著惡性刑事案件的發(fā)生,給人民群眾的生命財產(chǎn)安全帶來嚴(yán)重威脅。

這時候無論對其衣著、外貌還是交通工具的相似度搜索,就顯得至關(guān)重要了。
上述例子中,因為你都沒有把圖像上傳到互聯(lián)網(wǎng),Google的“以圖搜圖”引擎功能再強(qiáng)大,也無能為力。
好吧,解決這個問題,很有意義。
下一個問題自然是:這種需求,解決起來復(fù)雜嗎?
是不是需要跨過很高的技術(shù)門檻才能實現(xiàn)?是不是需要花大量經(jīng)費(fèi)雇傭?qū)<也拍芡瓿桑?/p>
本文,我為你展示如何用10幾行Python代碼,解決這個問題。
數(shù)據(jù)
為了講解的方便,我們依然采用《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》一文中使用過的哆啦a夢和瓦力圖片集合。
我給你準(zhǔn)備好了119張哆啦a夢的照片,和80張瓦力的照片。圖片已經(jīng)上傳到了這個Github項目。
請點(diǎn)擊這個鏈接,下載壓縮包。然后在本地解壓。作為咱們的演示目錄。
解壓后,你會看到目錄下有個image文件夾,其中包含兩個子目錄,分別是doraemon和walle。

doraemon的目錄下,都是各式各樣的藍(lán)胖子圖片。

瓦力目錄下的圖片是這個樣子的:

數(shù)據(jù)已經(jīng)有了,下面我們來準(zhǔn)備一下環(huán)境配置。
環(huán)境
本文中,我們需要使用到蘋果公司的機(jī)器學(xué)習(xí)框架TuriCreate。
請注意TuriCreate發(fā)布時間不久,目前支持的操作系統(tǒng)列表如下:

這就意味著,如果你用的操作系統(tǒng)是Windows 7及以下版本,那么目前TuriCreate還不支持。如需使用,有兩種辦法:
第一種,請升級到Windows 10,并且使用WSL。關(guān)于如何使用WSL,我?guī)湍阏业搅艘粋€中文教程。請按照教程一步步完成安裝。

第二種,采用虛擬機(jī)。推薦采用Virtualbox虛擬機(jī),開源免費(fèi)。同樣地,我也幫你找到了很詳盡的Virtualbox安裝Ubuntu Linux的中文教程。你可以參照它安裝好Linux。

解決了系統(tǒng)兼容性問題,下面我們在TuriCreate支持的系統(tǒng)中,安裝Python集成運(yùn)行環(huán)境Anaconda。
請到這個網(wǎng)址 下載最新版的Anaconda。下拉頁面,找到下載位置。根據(jù)你目前使用的系統(tǒng),網(wǎng)站會自動推薦給你適合的版本下載。我使用的是macOS,下載文件格式為pkg。

下載頁面區(qū)左側(cè)是Python 3.6版,右側(cè)是2.7版。請選擇2.7版本。
雙擊下載后的pkg文件,根據(jù)中文提示一步步安裝即可。

裝好Anaconda后,我們安裝TuriCreate。
請到你的“終端”下面,進(jìn)入咱們剛剛下載解壓后的樣例目錄。
執(zhí)行以下命令,我們來創(chuàng)建一個Anaconda虛擬環(huán)境,名字叫做turi。如果你之前跟隨我在《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》一文中創(chuàng)立過這個虛擬環(huán)境,此處請?zhí)^。
conda create -n turi python=2.7 anaconda
然后,我們激活turi虛擬環(huán)境。
source activate turi
在這個環(huán)境中,我們安裝(或者升級到)最新版的TuriCreate。
pip install -U turicreate
安裝完畢后,執(zhí)行:
jupyter notebook

這樣就進(jìn)入到了Jupyter筆記本環(huán)境。我們新建一個Python 2筆記本。

瀏覽器里出現(xiàn)了一個空白筆記本。

點(diǎn)擊左上角筆記本名稱,修改為有意義的筆記本名“demo-python-image-similarity”。

準(zhǔn)備工作完畢,下面我們就可以開始編寫程序了。
代碼
首先,我們讀入TuriCreate軟件包。
import turicreate as tc
我們指定圖像所在的文件夾image。讓TuriCreate讀取所有的圖像文件,并且存儲到data數(shù)據(jù)框。
data = tc.image_analysis.load_images('./image/')
我們來看看,data數(shù)據(jù)框的內(nèi)容:
data

data包含兩列信息,第一列是圖片的地址,第二列是圖片的長寬描述。
下面我們要求TuriCreate給數(shù)據(jù)框中每一行添加一個行號。這將作為圖片的標(biāo)記,好在后面查找圖片時使用。
data = data.add_row_number()
再看看此時的data數(shù)據(jù)框內(nèi)容:
data

我們來看看數(shù)據(jù)框里面的這些信息對應(yīng)的圖片。
data.explore()
TuriCreate會彈出一個頁面,給我們展示數(shù)據(jù)框里面的內(nèi)容。

把鼠標(biāo)懸停在某張縮略圖上面,就可以看到對應(yīng)清晰大圖。
第一張圖片,是哆啦a夢:

第二張圖片,是瓦力:

下面,是重頭戲。我們讓TuriCreate根據(jù)輸入的圖片集合,建立圖像相似度判別模型。
model = tc.image_similarity.create(data)
這個語句執(zhí)行起來,可能需要一些時間。如果你是第一次使用TuriCreate,它可能還需要從網(wǎng)上下載一些數(shù)據(jù)。請耐心等待。
Resizing images...
Performing feature extraction on resized images...
Completed 199/199
注意這里的提示,TuriCreate自動幫我們做了圖片尺寸調(diào)整等預(yù)處理工作,并且對每一張圖片,都做了特征提取。
經(jīng)過或長或短的等待,模型已經(jīng)成功建立。
下面,我們來嘗試給模型一張圖片,讓TuriCreate幫我們從目前的圖片集合里,挑出最為相似的10張來。
為了方便,我們就選擇第一張圖片作為查詢輸入。
我們利用show()函數(shù)展示一下這張圖片。
tc.Image(data[0]['path']).show()

確認(rèn)無誤,還是那張哆啦a夢。
下面我們來查詢,我們讓模型尋找出與這張圖片最相似的10張。
similar_images = model.query(data[0:1], k=10)
很快,系統(tǒng)提示我們,已經(jīng)找到了。

我們把結(jié)果存儲在了similar_images變量里面,下面我們來看看其中都有哪些圖片。
similar_images

返回的結(jié)果一共有10行。跟我們的要求一致。
每一行數(shù)據(jù),包含4列。分別是:
- 查詢圖片的標(biāo)記
- 獲得結(jié)果的標(biāo)記
- 結(jié)果圖片與查詢圖片的距離
- 結(jié)果圖片與查詢圖片近似程度排序值
有了這些信息,我們就可以查看到底哪些圖片與輸入查詢圖片最為相似了。
注意其中的第一張結(jié)果圖片,其實就是我們的輸入圖片本身??紤]它沒有意義。
我們提取全部結(jié)果圖片的標(biāo)記(索引)值,忽略掉第一張(自身)。
similar_image_index = similar_images['reference_label'][1:]
剩余9張圖片的標(biāo)記都在結(jié)果中:
similar_image_index
dtype: int
Rows: 9
[194, 158, 110, 185, 5, 15, 79, 91, 53]
下面我們希望TuriCreate能夠可視化幫我們展示這9張圖片的內(nèi)容。
我們要把上面9張圖片的標(biāo)記在所有圖片的索引列表中過濾出來:
filtered_index = data['id'].apply(lambda x : x in similar_image_index)
看看過濾后的索引結(jié)果:
filtered_index
dtype: int
Rows: 199
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ... ]
你可以自己數(shù)一數(shù),其中標(biāo)為1的那些圖片位置,和我們存儲在similar_image_index中的數(shù)字是否一致。
驗證完畢以后,請執(zhí)行以下語句。我們再次調(diào)用TuriCreate的explore()函數(shù),展現(xiàn)相似度查詢結(jié)果圖片。
data[filtered_index].explore()
系統(tǒng)會彈出以下對話框:

我們可以看到,全部查詢結(jié)果圖片中,只出現(xiàn)了哆啦a夢。瓦力的圖片,一張都沒有出現(xiàn)。
近似圖片查找成功!
隨著本文操作樣例數(shù)據(jù)后,你不妨換用自己的數(shù)據(jù),來動手嘗試一番。
原理
展示了如何用10幾行Python代碼幫你查找相似圖形后,我們來聊聊這種強(qiáng)大、簡潔背后的原理。
如果你對原理不感興趣,請?zhí)^這一部分,看“小結(jié)”。
雖然我們剛剛只是用了一條語句構(gòu)建模型:
model = tc.image_similarity.create(data)
然而實際上,TuriCreate在后臺為我們做了很多事情。
首先,它調(diào)用了一個非常復(fù)雜的,在龐大數(shù)據(jù)集上訓(xùn)練好的模型。

《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》一文中,我們介紹過,這個模型就是上圖中的最后一行。它的名字叫做Resnet-50,足足有50層,訓(xùn)練的圖片數(shù)以百萬計,訓(xùn)練時長也很久。
這里,機(jī)智的你一定會問個問題:那些數(shù)以百萬計的預(yù)訓(xùn)練圖片里面,是否有哆啦a夢和瓦力呢?
沒有。
那就怪了,不是嗎?
如果這個復(fù)雜的模型之前根本就沒有見過哆啦a夢和瓦力,那它怎么知道如何區(qū)分它們呢?又怎么能夠判別兩張哆啦a夢之間的差別,就一定比哆啦a夢和瓦力之間更小呢?
《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》一文里,我已經(jīng)提示給你一個關(guān)鍵詞:遷移學(xué)習(xí)(transfer learning)。
這里咱們就不深入技術(shù)細(xì)節(jié)了。我只給你在概念層次講解一下。
還記得這張描述計算機(jī)視覺(卷積神經(jīng)網(wǎng)絡(luò))的示意圖嗎?

在全連接層(Fully Connected Layer)之前,你可能進(jìn)行了多次的卷積、抽樣、卷積、抽樣……這些中間層次,幫我們描繪了圖片的一些基本特征,例如邊緣大概是個什么形狀,某個區(qū)塊主要的顏色是哪些等。
到了全連接層,你只剩下了一組數(shù)據(jù),這組數(shù)據(jù)可能很長,它抽取了你輸入數(shù)據(jù)的全部特征。

如果你的輸入是一只貓,此時的全連接層里就描述了這只貓的各種信息,例如毛發(fā)顏色、面部組成部分排列方式、邊緣的形狀……
這個模型可以幫你提取貓的特征,但它并不知道“貓”的概念是什么。
你自然可以用它幫你提取一條狗的特征。
同理,哆啦a夢的照片,與貓咪的照片一樣,都是二維圖片,都是用不同顏色分層。

那用其他圖片訓(xùn)練的模型,能否提取哆啦a夢照片里的特征呢?

當(dāng)然也可以!
使用遷移學(xué)習(xí)的關(guān)鍵,在于凍結(jié)中間過程的全部訓(xùn)練結(jié)果,直接把一幅圖,利用在其他圖片集合上訓(xùn)練的模型,轉(zhuǎn)化為一個特征描述結(jié)果。
后面的工作,只把這個最后的特征描述(全連接層),用來處理分類和相似度計算。
前面的好幾十層參數(shù)迭代訓(xùn)練,統(tǒng)統(tǒng)都被省卻了。

難怪可以利用這么小的數(shù)據(jù)集獲得如此高的準(zhǔn)確度;也難怪可以在這么短的時間里,就獲得整合后的模型結(jié)果。
把在某種任務(wù)上積累下的經(jīng)驗與認(rèn)知,遷移到另一種近似的新任務(wù)上,這種能力就叫做遷移學(xué)習(xí)。

比起機(jī)器來,人的遷移學(xué)習(xí)能力更為強(qiáng)大。
雨果獎作者郝景芳在最近的一篇文章里,描述了人的這種強(qiáng)大學(xué)習(xí)能力:
小孩子可以快速學(xué)習(xí),進(jìn)行小數(shù)據(jù)學(xué)習(xí),而且可以得到「類」的概念。小孩子輕易分得清「鴨子」這個概念,和每一只具體不同的鴨子,有什么不同。前者是抽象的「類」,后者是具體的東西。小孩子不需要看多少張鴨子的照片,就能得到「鴨子」這個抽象「類」的概念。
用成語來描述,大概就是“觸類旁通”吧。
如果人類不善于遷移學(xué)習(xí),把生活中的所有事物,全都當(dāng)成新的東西從頭學(xué)起,那后果簡直不堪設(shè)想。對比我們一生中所能處理的信息總量,這種認(rèn)知負(fù)荷將是無法承受的。
回到我們的問題里,如果模型可以幫我們把每一張圖片,都變成全連接層上的那一長串?dāng)?shù)字(特征),那么我們分辨這些圖片的相似程度,就變得太簡單了。因為這變成了一個簡單的空間向量距離問題。

處理這種簡單的數(shù)值計算,我們?nèi)祟惪赡苡X得很繁瑣。但是計算機(jī)算起來,那就很歡快了。
根據(jù)距離大小排序,找出其中最小的幾個向量,它們描述的圖片,就被模型判定為相似度最高的。
小結(jié)
在《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》一文的基礎(chǔ)上,本文進(jìn)一步介紹了以下內(nèi)容:
- 如何利用TuriCreate快速構(gòu)建圖片相似度模型;
- 如何查詢與某張圖片最為相似的k張圖片;
- 如何可視化展示查詢圖片集合結(jié)果;
- TuriCreate圖形分類與相似度計算背后的原理;
- 遷移學(xué)習(xí)的基礎(chǔ)概念。
如果你沒有讀過《如何用Python和深度神經(jīng)網(wǎng)絡(luò)識別圖像?》,強(qiáng)烈建議你讀一讀。閱讀過程可以幫助你更好地理解基于深度神經(jīng)網(wǎng)絡(luò)的計算機(jī)視覺工作原理。
討論
你之前遭遇過大海撈針,尋找近似圖片的工作嗎?你是如何處理的?使用過哪些好的工具與方法?與本文相比較,它們的優(yōu)勢有哪些?歡迎留言,把你的經(jīng)驗和思考分享給大家,我們一起交流討論。
喜歡請點(diǎn)贊。還可以微信關(guān)注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)。
如果你對數(shù)據(jù)科學(xué)感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數(shù)據(jù)科學(xué)?》,里面還有更多的有趣問題及解法。