zoukankan      html  css  js  c++  java
  • 【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分布概率一样,并且特征空间比均值大很多。

  • 相关阅读:
    LINQ标准查询操作符及例子(转载)
    Winform Combox 下拉模糊匹配
    在RowDataBound事件中取值的几种方法 转载
    shell脚本作业练习题8.6
    7.31.... 简单的python代码
    shell 脚本——第一节课 bash的基本特性
    shell 脚本——第二节课 重定向和管道符
    730
    应用范例:解析 Yahoo 奇摩股市的各档股票资讯HtmlAgilityPack
    微软一个罕为人知的无敌命令
  • 原文地址:https://www.cnblogs.com/ERKE/p/14110372.html
Copyright © 2011-2022 走看看