詳解類似圖片匹配算法:差別值哈希算法 + 顏色直方圖

因爲最近涉及到匹配類似圖片的問題,因此在此記錄下解決辦法:差別值哈希算法 + 顏色直方圖

環境要求:Python cv2庫 math庫html

差別值哈希算法

檢索類似圖片,第一個想到的就是差別值哈希算法。這個算法的步驟是:算法

  1. 縮小尺寸
    通常將圖片縮放爲 8 * 8 的尺寸大小,共64個像素的圖片。可是因爲64個像素對於我來講,損失的細節太多因此我選擇了縮放到 33 * 32 的尺寸大小
  2. 彩色圖像灰度化
    因爲咱們現有的圖片是由 RGB 三原色構成,每一個像素點是一個由這三個顏色組成的一個 list 。而 RGB 三個顏色中每一個顏色值都是用 8 個比特來表示,大小範圍是 0 ~ 255(2^8 - 1),就一共有 256 * 256 * 256 種顏色。而且做爲一個像素相似於這樣的數值:[253 255 255] 是不利於簡單比較的,肉眼看着相似的顏色,可是它的三個顏色分佈可能相差不少。因此將它灰度化,用 256 個不一樣的灰色表示現有的圖片。因爲如今用一種灰色表示三種顏色,原來每一個像素是一個 list 如今就降維成一個數值,數值的大小仍是比較容易比較的。
  3. 比較像素的灰度值
    比較圖片灰度化的每行相鄰像素之間的大小,每行後面像素值大於前面一個像素值那麼記爲1,若是不大於則記爲0
  4. 計算哈希值
    根據上一步獲得了由0和1構成的數組合在一塊兒就構成了1024位的整數
  5. 對比不一樣圖片的漢明距離
    對比兩個圖片生成的整數有多少位不同。通常漢明距離小於 5 ,兩張圖片的類似度就很高了。

差別值哈希算法的 Python 代碼

import cv2

# 差別值哈希算法
def dhash(image):
    resize_height, resized_width = 32, 33
    # 縮放到(resized_width, resize_height)尺寸的大小
    resized_img = cv2.resize(image, (resized_width, resize_height))
    # 圖片灰度化
    grey_resized_img = cv2.cvtColor(resized_img, cv2.COLOR_RGB2GRAY)
    # 差別值計算
    hash_list = []
    for row in range(resize_height):
        for col in range(resized_width - 1):
            # 每行前一個顏色強度大於後一個,值爲1,不然值爲0
            if grey_resized_img[row, col] > grey_resized_img[row, col + 1]:
                hash_list.append('1')
            else:
                hash_list.append('0')

    return '' . join(hash_list)

# 比較漢明距離
def hamming_distance(dhash1, dhash2):
    return bin(int(dhash1, base = 2) ^ int(dhash2, base = 2)).count('1')

# 讀取圖片內容
img1 = cv2.imread(img1_path)
# 讀取圖片內容
img2 = cv2.imread(img2_path)
if hamming_distance(dhash(img1), dhash(img2)) <= 5:
    print('類似圖片')

顏色直方圖

因爲差別值哈希失去了太多的細節,適合比較原圖或者縮略圖。因此我再加上顏色直方圖的比較計算圖片間的接近程度,用以排除部分像素的微小差別。數組

  1. 縮小尺寸
    通常將圖片縮放爲 8 * 8 的尺寸大小,共64個像素的圖片。可是因爲64個像素對於我來講,損失的細節太多因此我選擇了縮放到 32 * 32 的尺寸大小
  2. 下降位深
    原來 RGB 每一個顏色都有 256 種變化,如今作一個映射,將原來的 256 分爲 8(3個比特表示) 個顏色區間。相似舊的 0 - 31 對應新的顏色 0,以達到下降計算的效果
  3. 計算像素值
    因爲下降了位深,圖片顏色值變小。每一個顏色值不大於8(0 - 7),而後咱們給三元素不一樣的權重,分別爲 8 * 881 做爲數組的 key,用以統計每一個顏色的像素出現次數,而且不會出現不一樣顏色統計到了同一個 key 值下的目的。
  4. 計算類似度
    計算出像素值後獲得,咱們獲得了以不一樣顏色的數值爲 key,出現次數爲 value 的數組。這時候咱們可使用用餘弦類似度去計算相同顏色出現次數的類似度,越是類似的像素最後值越接近於1。截圖來自於WiKi

    圖片描述

顏色直方圖的 Python 代碼

import cv2
from math import sqrt

# 顏色映射
def bgr_mapping(img_val):
    # 將bgr顏色分紅8個區間作映射
    if img_val >= 0 and img_val <= 31: return 0
    if img_val >= 32 and img_val <= 63: return 1
    if img_val >= 64 and img_val <= 95: return 2
    if img_val >= 96 and img_val <= 127: return 3
    if img_val >= 128 and img_val <= 159: return 4
    if img_val >= 160 and img_val <= 191: return 5
    if img_val >= 192 and img_val <= 223: return 6
    if img_val >= 224: return 7

# 顏色直方圖的數值計算
def calc_bgr_hist(image):
    if not image.size: return False
    hist = {}
    # 縮放尺寸減少計算量
    image = cv2.resize(image, (32, 32))
    for bgr_list in image:
        for bgr in bgr_list:
            # 顏色按照順序映射
            maped_b = bgr_mapping(bgr[0])
            maped_g = bgr_mapping(bgr[1])
            maped_r = bgr_mapping(bgr[2])
            # 計算像素值
            index   = maped_b * 8 * 8 + maped_g * 8 + maped_r
            hist[index] = hist.get(index, 0) + 1
    
    return hist

# 計算兩張圖片的類似度
def compare_similar_hist(h1, h2):
    if not h1 or not h2: return False
    sum1, sum2, sum_mixd = 0, 0, 0
    # 像素值key的最大數不超過512,直接循環到512,遍歷取出每一個像素值
    for i in range(512):
        # 計算出現相同像素值次數的平方和
        sum1 = sum1 + (h1.get(i, 0) * h1.get(i, 0))
        sum2 = sum2 + (h2.get(i, 0) * h2.get(i, 0))
        # 計算兩個圖片次數乘積的和
        sum_mixd = sum_mixd + (h1.get(i, 0) * h2.get(i, 0))
    # 按照餘弦類似性定理計算類似度
    return sum_mixd / (sqrt(sum1) * sqrt(sum2))

# 讀取圖片內容
img1 = cv2.imread(img1_path)
# 讀取圖片內容
img2 = cv2.imread(img2_path)
if compare_similar_hist(calc_bgr_hist(img1), calc_bgr_hist(img2)) < 0.9999:
    print('類似圖片')

總結

總的來講:差別值哈希算法 + 顏色直方圖 解決了個人類似圖片匹配問題。app

參考資料

類似圖片搜索的原理一
類似圖片搜索的原理二spa

相關文章
相關標籤/搜索