1. 介紹
感知哈希算法(Perceptual Hash Algorithm,簡稱pHash) 是哈希算法的一種,主要可以用來做以圖搜索/相似圖片搜索工作。
?文章來源:http://www.zghlxwxcb.cn/news/detail-744967.html
2. 原理
感知哈希算法(pHash)首先將原圖像縮小成一個固定大小的像素圖像,然后將圖像轉(zhuǎn)換為灰度圖像,通過使用離散余弦變換(DCT)來獲取頻域信息。然后,根據(jù)DCT系數(shù)的均值生成一組哈希值。最后,利用兩組圖像的哈希值的漢明距離來評估圖像的相似度。
魔法: 概括地講,感知哈希算法一共可細分八步:
- 縮小圖像: 將目標圖像縮小為一個固定的大小,通常為32x32像素。作用是去除各種圖像尺寸和圖像比例的差異,只保留結(jié)構(gòu)、明暗等基本信息,目的是確保圖像的一致性,降低計算的復(fù)雜度。
- 圖像灰度化: 將縮小的圖像轉(zhuǎn)換為灰度圖像。
- 離散余弦變換(DCT): 感知哈希算法的核心是應(yīng)用離散余弦變換。DCT將圖像從空間域(像素級別)轉(zhuǎn)換為頻域,得到32×32的DCT變換系數(shù)矩陣,以捕獲圖像的低頻信息。
- 縮小DCT: 經(jīng)過DCT變換后,圖像的頻率特征集中在圖像的左上角,保留系數(shù)矩陣左上角的8×8系數(shù)子矩陣(因為雖然DCT的結(jié)果是32×32大小的矩陣,但左上角8×8的矩陣呈現(xiàn)了圖片中的最低頻率)。
- 計算灰度均值: 計算DCT變換后圖像塊的均值,以便后面確定每個塊的明暗情況。
- 生成二進制哈希值: 如果塊的DCT系數(shù)高于均值,表示為1,否則表示為0(由于我們只提取了DCT矩陣左上角的8×8系數(shù)子矩陣,所以,最后會得到一個64位的二進制值(8x8像素的灰度圖像))。
- 生成哈希值: 由于64位二進制值太長,所以按每4個字符為1組,由2進制轉(zhuǎn)成16進制。這樣就轉(zhuǎn)為一個長度為16的字符串。這個字符串也就是這個圖像可識別的哈希值,也叫圖像指紋,即這個圖像所包含的特征。
- 哈希值比較: 通過比較兩個圖像的哈希值的漢明距離(Hamming Distance),就可以評估圖像的相似度,距離越小表示圖像越相似。
?
3. 實驗
第一步:縮小圖像
將目標圖像縮小為一個固定的大小,通常為32x32像素。作用是去除各種圖像尺寸和圖像比例的差異,只保留結(jié)構(gòu)、明暗等基本信息,目的是確保圖像的一致性,降低計算的復(fù)雜度。
1)讀取原圖
# 測試圖片路徑
img_path = 'img_test/apple-01.jpg'
# 通過OpenCV加載圖像
img = cv2.imread(img_path)
# 通道重排,從BGR轉(zhuǎn)換為RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
2)縮放原圖
使用 OpenCV 的 resize 函數(shù)將圖像縮放為32x32像素。
# 縮小圖像:使用OpenCV的resize函數(shù)將圖像縮放為32x32像素,采用Cubic插值方法進行圖像重采樣
img_32 = cv2.resize(img, (32, 32), cv2.INTER_CUBIC)
OpenCV 的 cv2.resize() 函數(shù)提供了4種插值方法,以根據(jù)圖像的尺寸變化來進行圖像重采樣。
- cv2.INTER_NEAREST: 最近鄰插值,也稱為最近鄰算法。它簡單地使用最接近目標像素的原始像素的值。雖然計算速度快,但可能導(dǎo)致圖像質(zhì)量下降。
- cv2.INTER_LINEAR: 雙線性插值,通過對最近的4個像素進行線性加權(quán)來估計目標像素的值。比最近鄰插值更精確,但計算成本略高。
- cv2.INTER_CUBIC: 雙三次插值,使用16個最近像素的加權(quán)平均值來估計目標像素的值。通常情況下,這是一個不錯的插值方法,適用于圖像縮小。
- cv2.INTER_LANCZOS4: Lanczos插值,一種高質(zhì)量的插值方法,使用Lanczos窗口函數(shù)。通常用于縮小圖像,以保留圖像中的細節(jié)和紋理。
第二步:圖像灰度化
將縮小的圖像轉(zhuǎn)換為灰度圖像。
# 圖像灰度化:將彩色圖像轉(zhuǎn)換為灰度圖像。
img_gray = cv2.cvtColor(img_32, cv2.COLOR_BGR2GRAY)
print(f"縮放32x32的圖像中每個像素的顏色=\n{img_gray}")
輸出打印:
縮放32x32的圖像中每個像素的顏色=
[[253 253 253 ... 253 253 253]
[253 253 253 ... 253 253 253]
[253 253 253 ... 253 253 253]
...
[253 253 253 ... 253 253 253]
[253 253 253 ... 253 253 253]
[253 253 253 ... 253 253 253]]
第三步:離散余弦變換(DCT)
感知哈希算法的核心是應(yīng)用離散余弦變換。DCT將圖像從空間域(像素級別)轉(zhuǎn)換為頻域,得到32×32的DCT變換系數(shù)矩陣,以捕獲圖像的低頻信息。這里我們使用 OpenCV 的 cv2.dct 函數(shù)來執(zhí)行DCT。
# 離散余弦變換(DCT):計算圖像的DCT變換,得到32×32的DCT變換系數(shù)矩陣
img_dct = cv2.dct(np.float32(img_gray))
print(f"灰度圖像離散余弦變換(DCT)={img_dct}")
這行代碼執(zhí)行了離散余弦變換(DCT),它將圖像數(shù)據(jù)從空間域(像素級別)轉(zhuǎn)換為頻域,以便在頻域上分析圖像。
- cv2.dct: 這是 OpenCV 庫中的函數(shù),用于執(zhí)行離散余弦變換。DCT是一種數(shù)學變換,類似于傅里葉變換,它將圖像分解為不同頻率的分量。
- np.float32(img_gray): 這是將灰度圖像 img_gray 轉(zhuǎn)換為32位浮點數(shù)的操作。DCT通常需要浮點數(shù)作為輸入。
- img_dct: 這是存儲DCT變換后結(jié)果的變量。在執(zhí)行DCT后,img_dct 將包含圖像的頻域表示。
基于DCT的圖像感知哈希算法是一種能夠有效感知圖像全局特征的算法,將圖片認為是一個二維信號,包含了表現(xiàn)大范圍內(nèi)的亮度變化小的低頻部分與局部范圍亮度變化劇烈的高頻部分,而高頻部分一般存在大量的冗余和相關(guān)性。通過DCT變換,可以將高能量信息集中到圖像的左上角區(qū)域??梢岳斫鉃閳D像的特征頻率區(qū)域。
# 離散余弦變換(DCT):計算圖像的DCT變換,得到32×32的DCT變換系數(shù)矩陣
img_dct = cv2.dct(np.float32(img_gray))
print(f"灰度圖像離散余弦變換(DCT)={img_dct}")
# 縮放DCT系數(shù)
dct_scaled = cv2.normalize(img_dct, None, 0, 255, cv2.NORM_MINMAX)
img_dct_scaled = dct_scaled.astype(np.uint8)
# 顯示DCT系數(shù)的圖像
plt.imshow(img_dct_scaled, cmap='gray')
plt.show()
如下圖,將圖像進行DCT后得到其變換結(jié)果,圖像左上角變化明顯,而右下角幾乎沒有變化。
第四步:縮小DCT
經(jīng)過DCT變換后,圖像的頻率特征集中在圖像的左上角,保留系數(shù)矩陣左上角的8×8系數(shù)子矩陣(因為雖然DCT的結(jié)果是32×32大小的矩陣,但左上角8×8的矩陣呈現(xiàn)了圖片中的最低頻率)。
備注: 這里為什么要縮放DCT?以及其它縮放方式有哪些?不同縮放方式結(jié)果有何不同?不進行縮放DCT會怎么樣?等等問題,我們在文末對比解答。
# 離散余弦變換(DCT):計算圖像的DCT變換,得到32×32的DCT變換系數(shù)矩陣
img_dct = cv2.dct(np.float32(img_gray))
# print(f"灰度圖像離散余弦變換(DCT)={img_dct}")
# 縮放DCT:將DCT系數(shù)的大小顯式地調(diào)整為8x8,然后它計算調(diào)整后的DCT系數(shù)的均值,并生成哈希值。
img_dct.resize(8, 8)
# 縮放DCT系數(shù)
dct_scaled = cv2.normalize(dct_roi, None, 0, 255, cv2.NORM_MINMAX)
img_dct_scaled = dct_scaled.astype(np.uint8)
# 顯示DCT系數(shù)的圖像
plt.imshow(img_dct_scaled, cmap='gray')
plt.show()
第五步:計算灰度均值
計算DCT變換后圖像塊的均值,以便后面確定每個塊的明暗情況。
# 計算灰度均值:計算DCT變換后圖像塊的均值
img_avg = np.mean(img_dct)
print(f"DCT變換后圖像塊的均值={img_avg}")
輸出打?。?/p>
DCT變換后圖像塊的均值=7.814879417419434
第六步:生成二進制哈希值
如果塊的DCT系數(shù)高于均值,表示為1,否則表示為0。
由于我們只提取了DCT矩陣左上角的8×8系數(shù)子矩陣(圖片特征頻率區(qū)域),所以,最后會得到一個64位的二進制值(8x8像素的灰度圖像)。
# 生成二進制哈希值
img_hash_str = ''
for i in range(8):
for j in range(8):
if img_dct[i, j] > img_avg:
img_hash_str += '1'
else:
img_hash_str += '0'
print(f"圖像的二進制哈希值={img_hash_str}")
或者,使用等價的 lambda 表達式。效果一樣。
img_hash_str = ""
for i in range(8):
img_hash_str += ''.join(map(lambda i: '0' if i < img_avg else '1', img_dct[i]))
print(f"圖像的二進制哈希值={img_hash_str}")
輸出打印:
圖像的二進制哈希值=1011000010001000100000100010000000001000000000001000000000000000
第七步:圖像可識別的哈希值
由于64位二進制值太長,所以按每4個字符為1組,由2進制轉(zhuǎn)成16進制。這樣就轉(zhuǎn)為一個長度為16的字符串。這個字符串也就是這個圖像可識別的哈希值,也叫圖像指紋,即這個圖像所包含的特征。
# 生成圖像可識別哈希值
img_hash = ''
for i in range(0, 64, 4):
img_hash += ''.join('%x' % int(img_hash_str[i: i + 4], 2))
print(f"圖像可識別的哈希值={img_hash}")
輸出打?。?/p>
圖像可識別的哈希值=b088822008008000
第八步:哈希值比較
通過比較兩個圖像的哈希值的漢明距離(Hamming Distance),就可以評估圖像的相似度,距離越小表示圖像越相似。
def hamming_distance(s1, s2):
# 檢查這兩個字符串的長度是否相同。如果長度不同,它會引發(fā) ValueError 異常,因為漢明距離只適用于等長的字符串
if len(s1) != len(s2):
raise ValueError("Input strings must have the same length")
distance = 0
for i in range(len(s1)):
# 遍歷兩個字符串的每個字符,比較它們在相同位置上的值。如果發(fā)現(xiàn)不同的字符,將 distance 的值增加 1
if s1[i] != s2[i]:
distance += 1
return distance
漢明距離:兩個長度相同的字符串在相同位置上的字符不同的個數(shù)。即一組二進制數(shù)據(jù)變成另一組數(shù)據(jù)所需要的步驟數(shù)。漢明距離越小,則相似度越高。漢明距離為0,即兩張圖片完全一樣。
?
4. 測試
實驗場景
我們來簡單測試一下基于感知哈希算法的以圖搜圖 – 基于一張原圖找最相似圖片,看看效果如何。
這里,我準備了10張圖片,其中9張是蘋果,但形態(tài)不一,1張是梨子。
實驗素材
實驗代碼
"""
以圖搜圖:感知哈希算法(Perceptual Hash Algorithm,簡稱pHash)的原理與實現(xiàn)
測試環(huán)境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1
實驗時間:2023-10-31
"""
import os
import cv2
import time
import numpy as np
import matplotlib.pyplot as plt
def get_pHash(img_path):
# 讀取圖像:通過OpenCV的imread加載RGB圖像
img_rgb = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
# 縮小圖像:使用OpenCV的resize函數(shù)將圖像縮放為32x32像素,采用Cubic插值方法進行圖像重采樣
img_resize = cv2.resize(img_rgb, (32, 32), cv2.INTER_CUBIC)
# 圖像灰度化:將彩色圖像轉(zhuǎn)換為灰度圖像
img_gray = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)
# print(f"縮放32x32的圖像中每個像素的顏色=\n{img_gray}")
# 離散余弦變換(DCT):計算圖像的DCT變換,得到32×32的DCT變換系數(shù)矩陣
img_dct = cv2.dct(np.float32(img_gray))
# print(f"灰度圖像離散余弦變換(DCT)={img_dct}")
# 縮放DCT:將DCT系數(shù)的大小顯式地調(diào)整為8x8。然后它計算調(diào)整后的DCT系數(shù)的均值,并生成哈希值。
img_dct.resize(8, 8)
# 計算灰度均值:計算DCT變換后圖像塊的均值
img_avg = np.mean(img_dct)
# print(f"DCT變換后圖像塊的均值={img_avg}")
img_hash_str = ""
for i in range(8):
img_hash_str += ''.join(map(lambda i: '0' if i < img_avg else '1', img_dct[i]))
# print(f"圖像的二進制哈希值={img_hash_str}")
# 生成圖像可識別哈希值
img_hash = ''.join(map(lambda x:'%x' % int(img_hash_str[x : x + 4], 2), range(0, 64, 4)))
# print(f"圖像可識別的哈希值={img_hash}")
"""
# # 版本二
# # 生成二進制哈希值
# img_hash_str = ''
# for i in range(8):
# for j in range(8):
# if img_dct[i, j] > img_avg:
# img_hash_str += '1'
# else:
# img_hash_str += '0'
# print(f"圖像的二進制哈希值={img_hash_str}")
# # 生成圖像可識別哈希值
# img_hash = ''
# for i in range(0, 64, 4):
# img_hash += ''.join('%x' % int(img_hash_str[i: i + 4], 2))
# print(f"圖像可識別的哈希值={img_hash}")
"""
return img_hash
# 漢明距離:計算兩個等長字符串(通常是二進制字符串或位字符串)之間的漢明距離。用于確定兩個等長字符串在相同位置上不同字符的數(shù)量。
def hamming_distance(s1, s2):
# 檢查這兩個字符串的長度是否相同。如果長度不同,它會引發(fā) ValueError 異常,因為漢明距離只適用于等長的字符串
if len(s1) != len(s2):
raise ValueError("Input strings must have the same length")
distance = 0
for i in range(len(s1)):
# 遍歷兩個字符串的每個字符,比較它們在相同位置上的值。如果發(fā)現(xiàn)不同的字符,將 distance 的值增加 1
if s1[i] != s2[i]:
distance += 1
return distance
# ------------------------------------------------- 測試 -------------------------------------------------
time_start = time.time()
# 指定測試圖像庫目錄
img_dir = 'img_test'
# 指定測試圖像文件擴展名
img_suffix = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
# 獲取當前執(zhí)行腳本所在目錄
script_dir = os.path.dirname(__file__)
# 獲取目標測試圖像的全路徑
img_org_path = os.path.join(script_dir, img_dir, 'apple-01.jpg')
# 獲取目標圖像可識別哈希值(圖像指紋)
org_img_hash = get_pHash(img_org_path)
print(f"目標圖像:{os.path.relpath(img_org_path)},圖片HASH:{org_img_hash}")
# 獲取測試圖像庫中所有文件
all_files = os.listdir(os.path.join(script_dir, img_dir))
# 篩選出指定后綴的圖像文件
img_files = [file for file in all_files if any(file.endswith(suffix) for suffix in img_suffix)]
img_hash_all = []
# 遍歷測試圖像庫中的每張圖像
for img_file in img_files:
# 獲取相似圖像文件路徑
img_path = os.path.join(script_dir, img_dir, img_file)
# 獲取相似圖像可識別哈希值(圖像指紋)
img_hash = get_pHash(img_path)
# 獲取相似圖像與目標圖像的漢明距離
distance = hamming_distance(org_img_hash, img_hash)
# 存儲相似圖像的相對路徑、哈希值、漢明距離
img_hash_all.append((os.path.relpath(img_path), img_hash, distance))
for img in img_hash_all:
print(f"圖像:{img[0]},圖像HASH:{img[1]},與圖像目標的相似值(漢明距離):{img[2]}")
time_end = time.time()
print(f"耗時:{time_end - time_start}")
輸出打?。?/p>
目標圖像:..\..\P1_Hash\02_pHash\img_test\apple-01.jpg,圖片HASH:b080000088000000
圖像:..\..\P1_Hash\02_pHash\img_test\apple-01.jpg,圖像HASH:b080000088000000,與目標圖像的相似值(漢明距離):0
圖像:..\..\P1_Hash\02_pHash\img_test\apple-02.jpg,圖像HASH:a080000018000000,與目標圖像的相似值(漢明距離):2
圖像:..\..\P1_Hash\02_pHash\img_test\apple-03.jpg,圖像HASH:b020000080000000,與目標圖像的相似值(漢明距離):2
圖像:..\..\P1_Hash\02_pHash\img_test\apple-04.jpg,圖像HASH:a480000020000000,與目標圖像的相似值(漢明距離):4
圖像:..\..\P1_Hash\02_pHash\img_test\apple-05.jpg,圖像HASH:a400000044000000,與目標圖像的相似值(漢明距離):5
圖像:..\..\P1_Hash\02_pHash\img_test\apple-06.jpg,圖像HASH:f881000084000000,與目標圖像的相似值(漢明距離):4
圖像:..\..\P1_Hash\02_pHash\img_test\apple-07.jpg,圖像HASH:e408000090000000,與目標圖像的相似值(漢明距離):6
圖像:..\..\P1_Hash\02_pHash\img_test\apple-08.jpg,圖像HASH:cad9522236480010,與目標圖像的相似值(漢明距離):13
圖像:..\..\P1_Hash\02_pHash\img_test\apple-09.jpg,圖像HASH:b000000098000000,與目標圖像的相似值(漢明距離):2
圖像:..\..\P1_Hash\02_pHash\img_test\pear-001.jpg,圖像HASH:e0000000c8000000,與目標圖像的相似值(漢明距離):3
耗時:0.09674215316772461
簡單的測試分析:
原圖 | 相似圖片 | 相似值(漢明距離) | 相似圖片特點 | 相似圖片與原圖Hash對比結(jié)果 |
---|---|---|---|---|
圖片01 | 圖片01 | 0 | 自己 | 自己與自己相似度100% |
圖片01 | 圖片02、03、09 | 2 | 主體形狀、位置、背景基本相似 | 最相似。相同背景、相同物體、同相位置下最相似。 |
圖片01 | 圖片pear-001 | 3 | 黃色的梨子 | 意外相似。相似搜索并不能識別物體/內(nèi)容。 |
圖片01 | 圖片04、圖片06 | 4 | 原圖像的180度旋轉(zhuǎn)圖;多主體 | 比較相似。對于多主體、原圖旋轉(zhuǎn)變換相似搜索友好,因為經(jīng)過DCT變換后,圖像的能量特征集中在圖像的左上角。 |
圖片01 | 圖片05 | 5 | 青蘋果(2D) | 比較相似。對于2D的扁平相似圖片搜索也相對友好。 |
圖片01 | 圖片07 | 6 | 背景差異、多色調(diào) | 勉強相似。對于背景差異、多色調(diào)的圖片開始查找吃力。 |
圖片01 | 圖片08 | 10以上 | 背景差異、多色調(diào) | 較難分辨。復(fù)雜背景差異、多色調(diào)的圖片較難與原圖相似。 |
10張測試圖片中,漢明距離在5以內(nèi)的有8張圖片;漢明距離在10以外只有1張圖片。
從抽樣簡單測試結(jié)果看,感知哈希算法表現(xiàn)更友好、更準確。
備注:如果漢明距離0,則表示這兩張圖片非常相似;如果漢明距離小于5,則表示有些不同,但比較相近;如果漢明距離大于10,則表明是完全不同的圖片。
?
5. 總結(jié)
經(jīng)過實驗和測試,感知哈希算法的擼棒性更好??傮w與均值哈希算法(aHash)差不多,區(qū)別在于二值化方式不一樣。
特點: 傳統(tǒng), 屬于一種外觀相似哈希算法。
優(yōu)點: 簡單、相對準確、計算效率高;感知哈??紤]了圖像的全局特征,對圖像的尺寸和旋轉(zhuǎn)變化具有一定的魯棒性;適用于快速圖像相似性搜索。
缺點: 對一些局部變化不夠敏感;對于復(fù)雜、多色調(diào)的圖像較難辨別,屬于一種外觀相似的相似度計算。
?
6. 實驗問題
為什么要縮放DCT?DCT縮放方式有哪些?不同DCT縮放方式有何不同?不進行DCT縮放效果會怎么樣?
對于這些問題,我們來通過下面三組對比分析,一探究竟。
6.1 過程對比
方式一: DCT變換后,無DCT特征頻率區(qū)域縮放
方式二: DCT變換后,將DCT系數(shù)顯式調(diào)整為8x8
方式三: DCT變換后,只提取DCT系數(shù)左上角8x8像素
從上圖的DCT變換過程來看,從原圖讀取,到縮小到指定大小的像素圖像,再到像素圖像灰度化,對于圖像的加工結(jié)果都是一樣的。區(qū)別僅在于對灰度圖像使用離散余弦變換(DCT)之后,對DCT系數(shù)的使用方式不一樣。
6.2 結(jié)果對比
1)縱向?qū)Ρ?/h4>
同一張圖片使用不同DCT變換方式獲得的哈希值結(jié)果:
方式一:離散余弦變換DCT變換后,無DCT特征頻率區(qū)域縮放,獲得圖像的二進制哈希值=b3c3c682c9306640
方式二:離散余弦變換DCT變換后,將DCT系數(shù)顯式調(diào)整為8x8,獲得圖像的二進制哈希值=b080000088000000
方式三:離散余弦變換DCT變換后,只提取DCT系數(shù)左上角8x8像素,獲得圖像的二進制哈希值=b088822008008000
從上述的DCT變換結(jié)果來看,同一張圖片獲得圖像的二進制哈希值各不一樣。
- 方式一與方式二、方式三的結(jié)果相差較大。
- 方式二與方式三的結(jié)果也不盡一致。
2)橫向?qū)Ρ?/h4>
不同圖片使用相同DCT變換方式獲得的哈希值結(jié)果:
從上圖的DCT變換結(jié)果來看,不同圖片使用不同方式的DCT變換,最終查找的相似圖片結(jié)果都不盡相同。
- 從DCT變換方式維度看,方式二,將DCT系數(shù)顯示調(diào)整為8x8的查找效果最好。方式三其次。方式一最次。
- 從DCT變換方式的計算效率來看,方式二與方式三耗時相當,計算效率較高;而方式一,由于無DCT特征頻率區(qū)域縮放,所以計算量最大,效率最次。
6.3 代碼對比
"""
以圖搜圖:感知哈希算法(Perceptual Hash Algorithm,簡稱pHash)的原理與實現(xiàn)
測試環(huán)境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1
實驗時間:2023-10-22
"""
# ---------------------------------------------------------------------------------------------------------------------
# 測試:為什么要縮放DCT?DCT縮放方式有哪些?不同DCT縮放方式有何不同?不進行DCT縮放效果會怎么樣?
# ---------------------------------------------------------------------------------------------------------------------
import cv2
import time
import numpy as np
import matplotlib.pyplot as plt
# DCT變換后:無特征頻率區(qū)域縮放,使用整個32x32圖像塊的頻率分布,計算整個DCT系數(shù)的均值,并根據(jù)這個均值生成哈希值。
def get_pHash1(img_path):
img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
# plt.imshow(img, cmap='gray')
# plt.show()
img = cv2.resize(img, (32, 32), cv2.INTER_CUBIC)
# plt.imshow(img, cmap='gray')
# plt.show()
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# plt.imshow(img_gray, cmap='gray')
# plt.show()
img_dct = cv2.dct(np.float32(img_gray))
# 顯示DCT系數(shù)的圖像
# dct_scaled = cv2.normalize(img_dct, None, 0, 255, cv2.NORM_MINMAX)
# img_dct_scaled = dct_scaled.astype(np.uint8)
# plt.imshow(img_dct_scaled, cmap='gray')
# plt.show()
img_avg = np.mean(img_dct)
# print(f"DCT變換后圖像塊的均值={img_avg}")
img_hash_str = get_img_hash_binary(img_dct, img_avg)
# print(f"圖像的二進制哈希值={img_hash_str}")
img_hash = get_img_hash(img_hash_str)
return img_hash
# DCT變換后:將DCT系數(shù)的大小顯式地調(diào)整為8x8,使用8x8的DCT系數(shù)塊的頻率分布,計算調(diào)整后的DCT系數(shù)的均值,并生成哈希值。
def get_pHash2(img_path):
img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
# plt.imshow(img, cmap='gray')
# plt.show()
img = cv2.resize(img, (32, 32), cv2.INTER_CUBIC)
# plt.imshow(img, cmap='gray')
# plt.show()
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# plt.imshow(img_gray, cmap='gray')
# plt.show()
img_dct = cv2.dct(np.float32(img_gray))
img_dct.resize(8, 8)
# 顯示DCT系數(shù)的圖像
# dct_scaled = cv2.normalize(img_dct, None, 0, 255, cv2.NORM_MINMAX)
# img_dct_scaled = dct_scaled.astype(np.uint8)
# plt.imshow(img_dct_scaled, cmap='gray')
# plt.show()
img_avg = np.mean(img_dct)
# print(f"DCT變換后圖像塊的均值={img_avg}")
img_hash_str = get_img_hash_binary(img_dct, img_avg)
# print(f"圖像的二進制哈希值={img_hash_str}")
img_hash = get_img_hash(img_hash_str)
return img_hash
# DCT變換后:只提取DCT系數(shù)的左上角8x8塊的信息,然后計算這個塊的均值。此法只考慮圖像一小部分的頻率分布,并生成哈希值。
def get_pHash3(img_path):
img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
# plt.imshow(img, cmap='gray')
# plt.show()
img = cv2.resize(img, (32, 32), cv2.INTER_CUBIC)
# plt.imshow(img, cmap='gray')
# plt.show()
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# plt.imshow(img_gray, cmap='gray')
# plt.show()
img_dct = cv2.dct(np.float32(img_gray))
dct_roi = img_dct[0:8, 0:8]
# 顯示DCT系數(shù)的圖像
# dct_scaled = cv2.normalize(dct_roi, None, 0, 255, cv2.NORM_MINMAX)
# img_dct_scaled = dct_scaled.astype(np.uint8)
# plt.imshow(img_dct_scaled, cmap='gray')
# plt.show()
img_avg = np.mean(dct_roi)
# print(f"DCT變換后圖像塊的均值={img_avg}")
img_hash_str = get_img_hash_binary(dct_roi, img_avg)
# print(f"圖像的二進制哈希值={img_hash_str}")
img_hash = get_img_hash(img_hash_str)
return img_hash
def get_img_hash_binary(img_dct, img_avg):
img_hash_str = ''
for i in range(8):
img_hash_str += ''.join(map(lambda i: '0' if i < img_avg else '1', img_dct[i]))
# print(f"圖像的二進制哈希值={img_hash_str}")
return img_hash_str
def get_img_hash(img_hash_str):
img_hash = ''.join(map(lambda x:'%x' % int(img_hash_str[x : x + 4], 2), range(0, 64, 4)))
# print(f"圖像可識別的哈希值={img_hash}")
return img_hash
# 漢明距離:計算兩個等長字符串(通常是二進制字符串或位字符串)之間的漢明距離。用于確定兩個等長字符串在相同位置上不同字符的數(shù)量。
def hamming_distance(s1, s2):
# 檢查這兩個字符串的長度是否相同。如果長度不同,它會引發(fā) ValueError 異常,因為漢明距離只適用于等長的字符串
if len(s1) != len(s2):
raise ValueError("Input strings must have the same length")
distance = 0
for i in range(len(s1)):
# 遍歷兩個字符串的每個字符,比較它們在相同位置上的值。如果發(fā)現(xiàn)不同的字符,將 distance 的值增加 1
if s1[i] != s2[i]:
distance += 1
return distance
# ======================================== 測試場景一 ========================================
# img = 'img_test/apple-01.jpg'
# img_hash1 = get_phash1(img)
# img_hash2 = get_phash2(img)
# img_hash3 = get_phash3(img)
# print(f"方式一:DCT變換后,無DCT特征頻率區(qū)域縮放,獲得圖像的二進制哈希值={img_hash1}")
# print(f"方式二:DCT變換后,將DCT系數(shù)顯式調(diào)整為8x8,獲得圖像的二進制哈希值={img_hash2}")
# print(f"方式三:DCT變換后,只提取DCT系數(shù)左上角8x8像素,獲得圖像的二進制哈希值={img_hash3}")
# ======================================== 測試場景二 ========================================
time_start = time.time()
img_1 = 'img_test/apple-01.jpg'
img_2 = 'img_test/apple-02.jpg'
img_3 = 'img_test/apple-03.jpg'
img_4 = 'img_test/apple-04.jpg'
img_5 = 'img_test/apple-05.jpg'
img_6 = 'img_test/apple-06.jpg'
img_7 = 'img_test/apple-07.jpg'
img_8 = 'img_test/apple-08.jpg'
img_9 = 'img_test/apple-09.jpg'
img_10 = 'img_test/pear-001.jpg'
# ------------------------------------- 測試場景二:方式一 --------------------------------------
# img_hash1 = get_pHash1(img_1)
# img_hash2 = get_pHash1(img_2)
# img_hash3 = get_pHash1(img_3)
# img_hash4 = get_pHash1(img_4)
# img_hash5 = get_pHash1(img_5)
# img_hash6 = get_pHash1(img_6)
# img_hash7 = get_pHash1(img_7)
# img_hash8 = get_pHash1(img_8)
# img_hash9 = get_pHash1(img_9)
# img_hash10 = get_pHash1(img_10)
# ------------------------------------- 測試場景二:方式二 --------------------------------------
img_hash1 = get_pHash2(img_1)
img_hash2 = get_pHash2(img_2)
img_hash3 = get_pHash2(img_3)
img_hash4 = get_pHash2(img_4)
img_hash5 = get_pHash2(img_5)
img_hash6 = get_pHash2(img_6)
img_hash7 = get_pHash2(img_7)
img_hash8 = get_pHash2(img_8)
img_hash9 = get_pHash2(img_9)
img_hash10 = get_pHash2(img_10)
# ------------------------------------- 測試場景二:方式三 --------------------------------------
# img_hash1 = get_pHash3(img_1)
# img_hash2 = get_pHash3(img_2)
# img_hash3 = get_pHash3(img_3)
# img_hash4 = get_pHash3(img_4)
# img_hash5 = get_pHash3(img_5)
# img_hash6 = get_pHash3(img_6)
# img_hash7 = get_pHash3(img_7)
# img_hash8 = get_pHash3(img_8)
# img_hash9 = get_pHash3(img_9)
# img_hash10 = get_pHash3(img_10)
distance1 = hamming_distance(img_hash1, img_hash1)
distance2 = hamming_distance(img_hash1, img_hash2)
distance3 = hamming_distance(img_hash1, img_hash3)
distance4 = hamming_distance(img_hash1, img_hash4)
distance5 = hamming_distance(img_hash1, img_hash5)
distance6 = hamming_distance(img_hash1, img_hash6)
distance7 = hamming_distance(img_hash1, img_hash7)
distance8 = hamming_distance(img_hash1, img_hash8)
distance9 = hamming_distance(img_hash1, img_hash9)
distance10 = hamming_distance(img_hash1, img_hash10)
time_end = time.time()
print(f"圖片名稱:{img_1},圖片HASH:{img_hash1},與圖片1的近似值(漢明距離):{distance1}")
print(f"圖片名稱:{img_2},圖片HASH:{img_hash2},與圖片1的近似值(漢明距離):{distance2}")
print(f"圖片名稱:{img_3},圖片HASH:{img_hash3},與圖片1的近似值(漢明距離):{distance3}")
print(f"圖片名稱:{img_4},圖片HASH:{img_hash4},與圖片1的近似值(漢明距離):{distance4}")
print(f"圖片名稱:{img_5},圖片HASH:{img_hash5},與圖片1的近似值(漢明距離):{distance5}")
print(f"圖片名稱:{img_6},圖片HASH:{img_hash6},與圖片1的近似值(漢明距離):{distance6}")
print(f"圖片名稱:{img_7},圖片HASH:{img_hash7},與圖片1的近似值(漢明距離):{distance7}")
print(f"圖片名稱:{img_8},圖片HASH:{img_hash8},與圖片1的近似值(漢明距離):{distance8}")
print(f"圖片名稱:{img_9},圖片HASH:{img_hash9},與圖片1的近似值(漢明距離):{distance9}")
print(f"圖片名稱:{img_10},圖片HASH:{img_hash10},與圖片1的近似值(漢明距離):{distance10}")
print(f"耗時:{time_end - time_start}")
如上代碼,這三種方法獲取到的圖像二進制哈希值之所以不同,是因為它們在DCT變換后的處理方式不同:
- get_pHash1 方法: 這種方法首先將圖像進行灰度化,然后執(zhí)行DCT變換。接著,它計算整個DCT系數(shù)的均值,并根據(jù)這個均值生成哈希值。這意味著它考慮了整個32x32圖像塊的頻率分布。
- get_pHash2 方法: 這種方法在執(zhí)行DCT后,將DCT系數(shù)的大小顯式地調(diào)整為8x8。然后它計算調(diào)整后的DCT系數(shù)的均值,并生成哈希值。這個方法只考慮了8x8的DCT系數(shù)塊的頻率分布。
- get_pHash3 方法: 這種方法與 get_pHash2 類似,但它只提取了DCT系數(shù)的左上角8x8塊的信息(即ROI,感興趣區(qū)域),然后計算這個塊的均值。這個方法只考慮了圖像的一個小部分頻率分布。
由于這些方法考慮的DCT系數(shù)區(qū)域不同,它們生成的哈希值會有差異。一般來說,get_pHash1 方法考慮了整個圖像塊的頻率分布,因此哈希值可能更穩(wěn)定,但它也可能受到圖像整體性的影響。而 get_pHash2 和 get_pHash3 方法只考慮了一個小塊的頻率信息,所以哈希值可能更容易受到圖像的局部特征影響。
選擇哪種方法取決于你的應(yīng)用需求。如果你希望更穩(wěn)定的哈希值,get_pHash1 可能是一個不錯的選擇。如果你希望更靈敏地檢測局部特征,get_pHash2 或 get_pHash3 可能更適合。
?
7. 系列書簽
OpenCV書簽 #均值哈希算法的原理與相似圖片搜索實驗
OpenCV書簽 #感知哈希算法的原理與相似圖片搜索實驗
OpenCV書簽 #差值哈希算法的原理與相似圖片搜索實驗
OpenCV書簽 #直方圖算法的原理與相似圖片搜索實驗文章來源地址http://www.zghlxwxcb.cn/news/detail-744967.html
到了這里,關(guān)于OpenCV #以圖搜圖:感知哈希算法(Perceptual hash algorithm)的原理與實驗的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!