Python影象識別,圖片相似度計算!
1.背景
要識別兩張圖片是否相似,首先我們可能會區分這兩張圖是人物照,還是風景照等……對應的風景照是藍天還是大海……做一系列的分類。
從機器學習的的角度來說,首先要提取圖片的特徵,將這些特徵進行分類處理,訓練並建立模型,然後在進行識別。
但是讓計算機去區分這些圖片分別是哪一類是很不容易的,不過計算機可以知道影象的畫素值的,因此,在影象識別過程中,透過顏色特徵來識別是相似圖片是我們常用的(當然還有其特徵還有紋理特徵、形狀特徵和空間關係特徵等,這些有分為直方圖,顏色集,顏色局,聚合向量,相關圖等來計算顏色特徵),
Python資源共享群:626017123
為了得到兩張相似的圖片,在這裡透過以下幾種簡單的計算方式來計算圖片的相似度:
直方圖計算圖片的相似度
透過雜湊值,漢明距離計算
透過圖片的餘弦距離計算
透過圖片結構度量計算
一、直方圖計算圖片的相似度
上三張圖片,分別是img1。png, img2。jpg,img。png:
可以看出上面這三張圖是挺相似的,在顏色上是差不多的,最相似的是哪兩張大家可以猜猜看,看和我們計算的是否一樣。
在python中利用opencv中的calcHist()方法獲取其直方圖資料
,返回的結果是一個列表:
# 計算圖img1的直方圖
H1 = cv2。calcHist([img1], [1], None, [256], [0, 256])
H1 = cv2。normalize(H1, H1, 0, 1, cv2。NORM_MINMAX, -1) # 對圖片進行歸一化處理
先計算img1的直方圖,在對其歸一化,最後在分別對img2,img3計算,做歸一化,然後在利用python自帶的compareHist()進行相似度的比較:
利用compareHist()進行比較相似度
similarity1 = cv2。compareHist(H1, H2, 0)
最後得到三張圖片的直方圖如下:
影象的x軸是指的圖片的0~255之間的畫素變化,y軸指的是在這0~255畫素所佔的比列。
我們可以明顯的看出img2與img3的直方圖的變化趨勢是相符的有重合態的,執行結果如下:
透過執行結果知道img2和img3是值是最為相似的(程式碼calcImage。py)
上面的是直接呼叫opencv中的方法來實現的,下面還有自己寫的方法:
首先是將圖片轉化為RGB格式,在這裡是用的pillow中的Image來對圖片做處理的:
# 將圖片轉化為RGB
def make_regalur_image(img, size=(64, 64)):
gray_image = img。resize(size)。convert(‘RGB’)
return gray_image
在計算兩圖片的直方圖:
# 計算直方圖
def hist_similar(lh, rh):
assert len(lh) == len(rh)
hist = sum(1 - (0 if l == r else float(abs(l - r)) / max(l, r)) for l, r in zip(lh, rh)) / len(lh)
return hist
在計算其相似度:
# 計算相似度
def calc_similar(li, ri):
calc_sim = hist_similar(li。histogram(), ri。histogram())
return calc_sim
得到最終的執行結果:
兩種方法的的結果還是有點差距的,可以看到img1和img3的結果相似度高些。
不過兩者的相似度計算方法如下:
gi和si分別指的是兩條曲線的第i個點。
總結:
利用直方圖計算圖片的相似度時,是按照顏色的全域性分佈情況來看待的,無法對區域性的色彩進行分析,同一張圖片如果轉化成為灰度圖時,在計算其直方圖時差距就更大了。
為了解決這個問題,可以將圖片進行等分,然後在計算圖片的相似度。不過在這裡我就不敘述了,大家自行探討!!!
二、雜湊演算法計算圖片的相似度
在計算之前我們先了解一下影象指紋和漢明距離:
影象指紋:
影象指紋和人的指紋一樣,是身份的象徵,而影象指紋簡單點來講,就是將影象按照一定的雜湊演算法,經過運算後得出的一組二進位制數字。
漢明距離:
假如一組二進位制資料為101,另外一組為111,那麼顯然把第一組的第二位資料0改成1就可以變成第二組資料111,所以兩組資料的漢明距離就為1。簡單點說,漢明距離就是一組二進位制資料變成另一組資料所需的步驟數,顯然,這個數值可以衡量兩張圖片的差異,漢明距離越小,則代表相似度越高。漢明距離為0,即代表兩張圖片完全一樣。
感知雜湊演算法
是一類演算法的總稱,包括aHash、pHash、dHash。顧名思義,感知雜湊不是以嚴格的方式計算Hash值,而是以更加相對的方式計算雜湊值,因為“相似”與否,就是一種相對的判定。
幾種hash值的比較:
aHash:平均值雜湊。速度比較快,但是常常不太精確。
pHash:感知雜湊。精確度比較高,但是速度方面較差一些。
dHash:差異值雜湊。精確度較高,且速度也非常快
1。 平均雜湊演算法(aHash):
該演算法是基於比較灰度圖每個畫素與平均值來實現。
aHash的hanming距離步驟:
先將圖片壓縮成8*8的小圖
將圖片轉化為灰度圖
計算圖片的Hash值,這裡的hash值是64位,或者是32位01字串
將上面的hash值轉換為16位的
透過hash值來計算漢明距離
# 均值雜湊演算法
def ahash(image):
# 將圖片縮放為8*8的
image = cv2。resize(image, (8, 8), interpolation=cv2。INTER_CUBIC)
# 將圖片轉化為灰度圖
gray = cv2。cvtColor(image, cv2。COLOR_RGB2GRAY)
# s為畫素和初始灰度值,hash_str為雜湊值初始值
s = 0
# 遍歷畫素累加和
for i in range(8):
for j in range(8):
s = s + gray[i, j]
# 計算畫素平均值
avg = s / 64
# 灰度大於平均值為1相反為0,得到圖片的平均雜湊值,此時得到的hash值為64位的01字串
ahash_str = ‘’
for i in range(8):
for j in range(8):
if gray[i, j] > avg:
ahash_str = ahash_str + ‘1’
else:
ahash_str = ahash_str + ‘0’
result = ‘’
for i in range(0, 64, 4):
result += ‘’。join(‘%x’ % int(ahash_str[i: i + 4], 2))
# print(“ahash值:”,result)
return result
2。感知雜湊演算法(pHash):
均值雜湊雖然簡單,但是受均值影響大。如果對影象進行伽馬校正或者進行直方圖均值化都會影響均值,從而影響雜湊值的計算。所以就有人提出更健壯的方法,透過離散餘弦(DCT)進行低頻提取。
離散餘弦變換(DCT)是種影象壓縮演算法,它將影象從畫素域變換到頻率域。然後一般影象都存在很多冗餘和相關性的,所以轉換到頻率域之後,只有很少的一部分頻率分量的係數才不為0,大部分系數都為0(或者說接近於0)。
pHash的計算步驟:
縮小圖片:32 * 32是一個較好的大小,這樣方便DCT計算轉化為灰度圖
計算DCT:利用Opencv中提供的dct()方法,注意輸入的影象必須是32位浮點型,所以先利用numpy中的float32進行轉換
縮小DCT:DCT計算後的矩陣是32 * 32,保留左上角的8 * 8,這些代表的圖片的最低頻率
計算平均值:計算縮小DCT後的所有畫素點的平均值。
進一步減小DCT:大於平均值記錄為1,反之記錄為0。
得到資訊指紋:組合64個資訊位,順序隨意保持一致性。
最後比對兩張圖片的指紋,獲得漢明距離即可。
# phash
def phash(path):
# 載入並調整圖片為32*32的灰度圖片
img = cv2。imread(path)
img1 = cv2。resize(img, (32, 32),cv2。COLOR_RGB2GRAY)
# 建立二維列表
h, w = img。shape[:2]
vis0 = np。zeros((h, w), np。float32)
vis0[:h, :w] = img1
# DCT二維變換
# 離散餘弦變換,得到dct係數矩陣
img_dct = cv2。dct(cv2。dct(vis0))
img_dct。resize(8,8)
# 把list變成一維list
img_list = np。array()。flatten(img_dct。tolist())
# 計算均值
img_mean = cv2。mean(img_list)
avg_list = [‘0’ if i return ‘’。join([‘%x’ % int(‘’。join(avg_list[x:x+4]),2) for x in range(0,64,4)]) 3. 差異值雜湊演算法(dHash): 相比pHash,dHash的速度要快的多,相比aHash,dHash在效率幾乎相同的情況下的效果要更好,它是基於漸變實現的。 dHash的hanming距離步驟: 先將圖片壓縮成9*8的小圖,有72個畫素點 將圖片轉化為灰度圖 計算差異值:dHash演算法工作在相鄰畫素之間,這樣每行9個畫素之間產生了8個不同的差異,一共8行,則產生了64個差異值,或者是32位01字串。 獲得指紋:如果左邊的畫素比右邊的更亮,則記錄為1,否則為0。 透過hash值來計算漢明距離 # 差異值雜湊演算法 def dhash(image): # 將圖片轉化為8*8 image = cv2。resize(image, (9, 8), interpolation=cv2。INTER_CUBIC) # 將圖片轉化為灰度圖 gray = cv2。cvtColor(image, cv2。COLOR_RGB2GRAY) dhash_str = ‘’ for i in range(8): for j in range(8): if gray[i, j] > gray[i, j + 1]: dhash_str = dhash_str + ‘1’ else: dhash_str = dhash_str + ‘0’ result = ‘’ for i in range(0, 64, 4): result += ‘’。join(‘%x’ % int(dhash_str[i: i + 4], 2)) # print(“dhash值”,result) return result 4. 計算雜湊值差異 # 計算兩個雜湊值之間的差異 def campHash(hash1, hash2): n = 0 # hash長度不同返回-1,此時不能比較 if len(hash1) != len(hash2): return -1 # 如果hash長度相同遍歷長度 for i in range(len(hash1)): if hash1[i] != hash2[i]: n = n + 1 return n 最終的執行結果: aHash: dhash: p_hsah: 透過上面執行的結果可以看出來,img1和img2的相似度高一些。 三、餘弦相似度(cosin) 把圖片表示成一個向量,透過計算向量之間的餘弦距離來表徵兩張圖片的相似度。 1. 對圖片進行歸一化處理 # 對圖片進行統一化處理 def get_thum(image, size=(64, 64), greyscale=False): # 利用image對影象大小重新設定, Image。ANTIALIAS為高質量的 image = image。resize(size, Image。ANTIALIAS) if greyscale: # 將圖片轉換為L模式,其為灰度圖,其每個畫素用8個bit表示 image = image。convert(‘L’) return image 2. 計算餘弦距離 # 計算圖片的餘弦距離 def image_similarity_vectors_via_numpy(image1, image2): image1 = get_thum(image1) image2 = get_thum(image2) images = [image1, image2] vectors = [] norms = [] for image in images: vector = [] for pixel_tuple in image。getdata(): vector。append(average(pixel_tuple)) vectors。append(vector) # linalg=linear(線性)+algebra(代數),norm則表示範數 # 求圖片的範數?? norms。append(linalg。norm(vector, 2)) a, b = vectors a_norm, b_norm = norms # dot返回的是點積,對二維陣列(矩陣)進行計算 res = dot(a / a_norm, b / b_norm) return res 最終執行結果: 結果顯示img1和img2的相似度高一些,和計算hash值的漢明距離得到的結果是相一致的。 四、圖片SSIM( 結構相似度量 ) SSIM是一種全參考的影象質量評價指標,分別從亮度、對比度、結構三個方面度量影象相似性。 SSIM取值範圍[0, 1],值越大,表示影象失真越小。在實際應用中,可以利用滑動窗將影象分塊,令分塊總數為N,考慮到視窗形狀對分塊的影響,採用高斯加權計算每一視窗的均值、方差以及協方差,然後計算對應塊的結構相似度SSIM,最後將平均值作為兩影象的結構相似性度量,即平均結構相似性SSIM。 ssim1 = compare_ssim(img1, img2, multichannel=True) 這個是scikit-image庫自帶的一種計算方法 執行結果: 可以看到img1和img2的相似度高。 好了,以上就是到目前為止我接觸到的圖片相似度的計算方法,肯定還有許多我沒有接觸到的計算方法,大家有需要的可以參考一下,有其他方法的大家可以留言一起探討!!!