【PHash】更懂人眼的感知哈希

转载请标名出处

背景

在重复图识别领域,对于识别肉眼相同图片,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.cnblogs.com/ERKE/p/14110372.html