背景
在重複圖識別領域,對於識別肉眼相同圖片,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分佈概率一樣,並且特徵空間比均值大很多。