PHash從0到1

ERKE發表於2020-12-09

背景

在重複圖識別領域,對於識別肉眼相同圖片,PHash是很有用的,而且演算法複雜度很低。它抓住了 ” 人眼對於細節資訊不是很敏感 “ 的特性,利用DCT變換把高頻資訊去掉,再加上合適&簡單的二值化方式,使得演算法效果比較魯棒。

PHash演算法

  • 附上python程式碼:
 def phash(image, hash_size=8, highfreq_factor=4):
     import scipy.fftpack
     img_size = hash_size * highfreq_factor
     image = image.convert("L").resize((img_size, img_size), Image.ANTIALIAS)// 1、【預處理】轉灰度圖,resize
     pixels = numpy.asarray(image)
     dct = scipy.fftpack.dct(scipy.fftpack.dct(pixels, axis=0), axis=1) //DCT變換
     dctlowfreq = dct[:hash_size, :hash_size] //2、只留下直流&&低頻變數
     med = numpy.median(dctlowfreq) //取中值
     diff = dctlowfreq > med //3、【二值化】大於中值為1,小於等於中值為0
     return diff

PHash演算法其實很簡單,主要就3步:

  • 圖片預處理
  • DCT變換
  • 二值化

其中圖片預處理很簡單,這裡就不詳細講解了。下面主要給大家直觀介紹下DCT變換究竟是什麼,還有這裡是怎麼二值化的。

DCT變換

DCT變換,全稱是Discrete Cosine Transform,也就是離散餘弦變換,具體的公式跟原理這裡就不詳述了,具體可以看DCT變換公式&原理

  • DCT變換能把影像轉成頻譜圖,DCT逆變換能把頻譜圖轉回原圖,如下圖所示。

其中頻譜圖中,左上角屬於低頻變數,右下角屬於高頻變數,然後比較特殊的一點是左上角a[0][0]這點屬於直流變數。由於人眼對於細節資訊不是很敏感,所以我們在識別肉眼相同級別重複圖的時候,只用頻譜圖中的低頻資訊就足夠了,所以這就是phash中只取DCT低頻資訊的原因。

怎麼理解圖片中的低頻跟高頻

在頻譜圖中,我們知道左上角那些是低頻資訊,右下角是高頻資訊。那麼在一張圖片中,哪些資訊是低頻,哪些資訊是高頻呢?

由於DCT是可逆變換,那麼我們可以只用頻譜圖中某一塊進行DCT逆變換,那麼就可以直觀看到頻譜圖中這一塊代表什麼資訊?

接下來,我們利用DCT逆變換生成兩列圖片(如下所示):

  • 【左下角】第一列直接用頻譜圖左上角N*N的矩陣,進行DCT逆變換生成的圖片。
  • 【除左下角】第二列把頻譜圖中左上角N*N矩陣置0,進行DCT逆變換生成的圖片。

從上可以得出結論:

  • 圖片中低頻資訊是那些畫素點色塊連續的部分
  • 圖片中高頻資訊是那些色塊邊界點
  • 左上角那一點,屬於直流變數,直接置0,影響不大
  • 當N(600)很大的時候,DCT變換可以用坐降噪、壓縮

附上程式碼,方便大家理解

import cv2
import copy
import numpy as np
import matplotlib.pyplot as plt

#展示圖片
def show_img(img):
    plt.imshow(img, cmap='Greys_r')
    plt.show()
    
#左上角低頻矩陣,進行DCT逆變換
def low_frequency_idct(dct,dct_size):
	#非左上角N*N區域置0
    dct[dct_size+1:,:] = 0
    dct[:,dct_size+1:] = 0
    #逆DCT變換
    img = cv2.idct(dct)
    #展示圖片
    show_img(img)
    
#把左上角資訊清除後,進行DCT逆變換
def hight_frequency_idct(dct,dct_size):
	#左上角N*N區域置0
    dct[0:dct_size,0:dct_size]=0
    #逆DCT變換
    img = cv2.idct(dct)
    #展示圖片
    show_img(img)

#主函式
def work(image_name, img_size, dct_size):
    #圖片預處理
    img = cv2.imread(image_name,0)
    show_img(img)
    img = cv2.resize(img,(img_size,img_size),interpolation=cv2.INTER_CUBIC)
    show_img(img)
    img = np.float32(img)
    #DCT變換
    dct = cv2.dct(img)
    #用左上角,進行逆dct變換
    low_frequency_idct(copy.deepcopy(dct),dct_size)
    #左上角置0,進行逆dct變換
    hight_frequency_idct(copy.deepcopy(dct),dct_size)

image_name = '11.png'
img_size = 1000
dct_size = 30
work(image_name,img_size,dct_size)

二值化

目前我們獲取到了肉眼最敏感的資訊,這裡應該怎麼二值化呢?
首先我們需要選取一個基準值,然後大於基準值的置1,小於等於基準值的置0。
那麼問題來了,怎麼選擇這個基準值呢?這裡有兩種方式:

1、均值

由於頻譜圖左上角那一點(直流變數),就是用原圖所有畫素點加起來得到的,所以這個點會很大,完全偏離總體的值。
然後這裡基準值如果用均值的話,會導致phash值中1的個數會偏少,而且左上角那邊大概率是1,右下角那邊大概為0。這就會導致phash中0,1的分佈不均勻。那麼其實對於phash值的特徵空間就有一定的縮小很多了。(如上圖所示,1個數很少)

PS: 改進策略:去除頻譜圖中第一行&&第一列的元素。

這樣能把一些很離譜的偏離點刪除,但是未必偏離點就在第一行&第一列,只是大概率在這裡。其實這樣還不如直接用中值更加直接。
改進之後效果好很多,但是並沒有中值魯棒。

2、中值

利用中值來當基準值,效果會好很多。phash值中,0,1分佈概率一樣,並且特徵空間比均值大很多。

相關文章