背景
在重復圖識別領域,對于識別肉眼相同圖片,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分布概率一樣,并且特征空間比均值大很多,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/232357.html
標籤:其他
