傳統加密雜湊(MD5、SHA)對微小像素變化極敏感,無法識別視覺相似圖片。本文介紹差異哈希(dHash)演算法,透過比較鄰近像素亮度梯度,產生具容錯力的圖片指紋。
from PIL import Image
def dhash(image, hash_size=8):
# 灰階 + 縮放
image = image.convert('L').resize((hash_size + 1, hash_size), Image.ANTIALIAS)
pixels = list(image.getdata())
# 比較鄰近像素
diff = []
for row in range(hash_size):
for col in range(hash_size):
left = image.getpixel((col, row))
right = image.getpixel((col + 1, row))
diff.append(left > right)
# 轉 hex
hex_str = []
dec_val = 0
for idx, val in enumerate(diff):
if val:
dec_val += 2 ** (idx % 8)
if (idx % 8) == 7:
hex_str.append(hex(dec_val)[2:].rjust(2, '0'))
dec_val = 0
return ''.join(hex_str)
>>> orig = Image.open('cat_grumpy_orig.png')
>>> modif = Image.open('cat_grumpy_modif.png') # 修改鼻子顏色
>>> dhash(orig)
'4c8e3366c275650f'
>>> dhash(modif)
'4c8e3366c275650f'
>>> dhash(orig) == dhash(modif)
True
輕微修改產生相同雜湊值,達到感知去重目的。
當雜湊值不完全相同,計算漢明距離(不同 bit 數)判斷相似度:
def hamming_distance(h1, h2):
return bin(int(h1, 16) ^ int(h2, 16)).count('1')
MySQL 可直接查詢:
SELECT pk, hash,
BIT_COUNT(CONV(hash, 16, 10) ^ CONV('4c8e3366c275650f', 16, 10)) AS hamming_distance
FROM image_hashes
HAVING hamming_distance < 4
ORDER BY hamming_distance ASC;
dHash 演算法簡單高效,適合中小型圖片庫即時去重與相似度檢測。