聲明:本文最初發表於賴勇浩(戀花蝶)的部落格http://blog.csdn.net/lanphaday,如蒙轉載,敬請確保全文完整,未經同意,不得用於商業用途。
關於《Python也可以》系列:這是我打算把這幾年裡做的一些實驗和代碼寫出來,涉及的面比較廣,也比較雜,可能會有影像處理、檢索等方面的內容,也會有中文分詞、文本分類、拼音、錯誤修正等內容。毫不掩飾地說:在部落格發這系列文章的原因在於宣傳 python ,所以這系列文章都會帶有源碼和相關的測試案例,這也是特色之一。但這系列文章都是“淺嘗輒止”的,不會深入到專屬領域,只是為了表明 python 功能很強大,不僅適合於web 或者game 開發,也適合於科學研究。
要計算映像的相似性,肯定是要找出映像的特徵。這樣跟你描述一個人的面貌:國字臉,濃眉,雙眼皮,直鼻樑,大而厚的嘴唇。Ok,這些特徵決定了這個人跟你的同事、朋友、家人是不是有點像。映像也一樣,要計算相似性,必須抽象出一些特徵比如藍天白雲綠草。常用的映像特徵有顏色特徵、紋理特徵、形狀特徵和空間關係特徵等。顏色特徵的算是最常用的,在其中又分為長條圖、顏色集、顏色矩、彙總向量和相關圖等。長條圖能夠描述一幅映像中顏色的全域分布,而且容易理解和實現,所以入門級的映像相似性計算都是使用它的;作為一篇樣本性的“淺嘗輒止”的文章,我們也不例外。
在進行我們實驗之前,我們需要找到一批圖片來作為測試案例。我上窮碧落下黃泉,最後終於在我的前同事西門的部落格(http://blog.163.com/johnal1 )找到了一系列他在公司組織的年度旅遊時去西藏林芝拍的一組風光圖片(http://blog.163.com/johnal1/blog/static/9394912200812105654784 ),實在是難得之佳品,簡直可以說得到了它們我們的實驗已經完成了90%。哦耶!下面來看一下我們最重要的一組照片(兩張):
找到一組很好的測試圖片之後,我們需要再給 Python 環境安裝一個映像庫,我的選擇是PIL(Python image library)。PIL 為 Python 提供了影像處理功能,並且支援數十種映像格式。(關於 PIL 的介紹,可以查看我之前的文章《用Python做影像處理》http://blog.csdn.net/lanphaday/archive/2007/10/28/1852726.aspx )
雖然這兩張圖片大小都是一樣的,但為了通用性,我們有必要把所有的圖片都統一到特別的規格,在這裡我選擇是的256x256的解析度。
因為 PIL 為 RGB 模式的映像計算的 histogram 樣點數為 768,計算量並不算太大,所以本文就直接使用,沒有再作降維處理了。
6 def make_regalur_image(img, size = (256, 256)):
7 return img.resize(size).convert('RGB')
轉化為規則映像之後,可以調用 img.histogram() 方法獲得長條圖資料,如上文兩圖的長條圖如下:
得到規則映像之後,映像的相似性計算就轉化為長條圖的距離計算了,本文依照如下公式進行長條圖相似性的定量度量:
Sim(G,S)=,其中G,S為長條圖,N 為色彩空間樣點數
轉換為相應的 Python 代碼如下:
19 def hist_similar(lh, rh):
20 assert len(lh) == len(rh)
21 return sum(1 - (0 if l == r else float(abs(l - r))/max(l, r)) for l, r in zip(lh, rh))/len(lh)
22
23 def calc_similar(li, ri):
24 return hist_similar(li.histogram(), ri.histogram())
短短十行代碼不到就完成了圖片相似性的計算,再加上從硬碟讀取映像的函數和測試代碼,也不過二十行上下:
28 def calc_similar_by_path(lf, rf):
29 li, ri = make_regalur_image(Image.open(lf)), make_regalur_image(Image.open(rf))
30 return calc_similar(li, ri)
31
32 if __name__ == '__main__':
33 path = r'test/TEST%d/%d.JPG'
34 for i in xrange(1, 7):
35 print 'test_case_%d: %.3f%%'%(i, calc_similar_by_path('test/TEST%d/%d.JPG'%(i, 1), 'test/TEST%d/%d.JPG'%(i, 2))*100)
那麼這樣做的效果到底怎麼樣呢?且來看看測試結果(測試案例和代碼請猛擊這裡下載):
test_case_1: 63.322%
test_case_2: 66.950%
test_case_3: 51.990%
test_case_4: 70.401%
test_case_5: 32.755%
test_case_6: 42.203%
結合我們肉眼對測試案例的觀察,這個程式工作得還算可以。不過 test_case_4 就暴露了長條圖的缺點:它只是映像中顏色的全域分布的描述,無法描述顏色的局部分布和色彩所處的位置。test_case_4 的規則圖如下:
可以看到它們的色彩局部分布有相當大的不同,但事實上它們的全域長條圖相當相似:
雖然從長條圖來看兩圖是極其相似的,但上述演算法計算出相似性為70.4%的結果肯定是不可接受的。那麼,怎麼樣才能克服長條圖的缺點呢?答案是把規則映像分塊,再對相應的小塊進行相似性計算,最後根據各小塊的平均相似性來反映整個圖片的相似性。在實驗中,我們把規則映像分為 4x4 塊,每塊的解析度為 64x64:
分割映像的代碼為:
9 def split_image(img, part_size = (64, 64)):
10 w, h = img.size
11 pw, ph = part_size
12
13 assert w % pw == h % ph == 0
14
15 return [img.crop((i, j, i+pw, j+ph)).copy() /
16 for i in xrange(0, w, pw) /
17 for j in xrange(0, h, ph)]
相應地,把計算相似圖的函數calc_similar()修改為:
23 def calc_similar(li, ri):
24 # return hist_similar(li.histogram(), ri.histogram())
25 return sum(hist_similar(l.histogram(), r.histogram()) for l, r in zip(split_image(li), split_image(ri))) / 16.0
進行這樣的改進後,演算法已經能夠在一定的程式上反映色彩的局倍分布和顏色所處的位置,可以比較好的彌補全域長條圖演算法的不足。新的演算法計算出來的結果如下:
test_case_1: 56.273%
test_case_2: 54.925%
test_case_3: 49.326%
test_case_4: 40.254%
test_case_5: 30.776%
test_case_6: 39.460%
可以看到,test_case_4的相似性由 70.4% 下降到 40.25%,基本上跟肉眼的判斷是切合的;另外其它映像的相似性略有下降,這是因為加入了位置因子之的影響。從而可見基於分塊的長條圖相似演算法是簡單有效。
映像的相似性計算是映像檢索、識別的基礎,本文只是淺嘗輒止地介紹了其中最基本的計算方法,如果你要學習和研究更好的演算法,也請記住 Python 也能協助你哦~
本實驗的所有代碼和測試案例請猛擊這裡下載,再次感謝提供圖片支援的西門同學。