感知哈希算法之基于低频的均值哈希

1.1前言

在Google的首页上有相似图片搜索功能。你可以用一张图片,搜索互联网上所有与它相似的图片。点击搜索框中照相机的图标。

《感知哈希算法之基于低频的均值哈希》

一个对话框会出现。

《感知哈希算法之基于低频的均值哈希》

你输入网片的网址,或者直接上传图片,Google就会找出与其相似的图片。下面这张图片是美国女演员Alyson Hannigan。

《感知哈希算法之基于低频的均值哈希》

上传后,Google返回如下结果:

《感知哈希算法之基于低频的均值哈希》

类似的”相似图片搜索引擎”还有不少,TinEye甚至可以找出照片的拍摄背景。

《感知哈希算法之基于低频的均值哈希》

==========================================================

这种技术的原理是什么?计算机怎么知道两张图片相似呢?

根据Neal Krawetz博士的解释,原理非常简单易懂。我们可以用一个快速算法,就达到基本的效果。

这里的关键技术叫做”感知哈希算法”(Perceptual hashalgorithm),它的作用是对每张图片生成一个”指纹”(fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。。达到图片比较目的且利用信息指纹比较有三种算法,这些算法都很易懂,下面先介绍基于低频的均值哈希,pHash和差异哈希算法(dHash)后续介绍:

1.2基于低频的均值哈希

下面是一个最简单的一张图片就是一个二维信号,它包含了不同频率的成分。如下图所示,亮度变化小的区域是低频成分,它描述大范围的信息。而亮度变化剧烈的区域(比如物体的边缘)就是高频的成分,它描述具体的细节。或者说高频可以提供图片详细的信息,而低频可以提供一个框架。

     而一张大的,详细的图片有很高的频率,而小图片缺乏图像细节,所以都是低频的。所以我们平时的下采样,也就是缩小图片的过程,实际上是损失高频信息的过程。下面5张图依次是原图,放缩至64*6432*3216*168*8的图。

《感知哈希算法之基于低频的均值哈希》《感知哈希算法之基于低频的均值哈希》《感知哈希算法之基于低频的均值哈希》《感知哈希算法之基于低频的均值哈希》《感知哈希算法之基于低频的均值哈希》          

     均值哈希算法主要是利用图片的低频信息,其工作过程如下:

(1)   缩小尺寸:去除高频和细节的最快方法是缩小图片,将图片缩小到8×8的尺寸,总共64个像素。不要保持纵横比,只需将其变成8*8的正方形。这样就可以比较任意大小的图片,这一步的作用是去除图片的细节,只保留结构、明暗等基本信息摒弃不同尺寸、比例带来的图片差异。

 《感知哈希算法之基于低频的均值哈希》《感知哈希算法之基于低频的均值哈希》

2)简化色彩:将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。

3)计算平均值:计算所有64个像素的灰度平均值。

4)比较像素的灰度:将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0

5)计算hash值:将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。(我设置的是从左到右,从上到下用二进制保存)。

《感知哈希算法之基于低频的均值哈希》 = 《感知哈希算法之基于低频的均值哈希》 = 8f373714acfcf4d0

得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。在理论上,这等同于计算汉明距离(Hamming distance)。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。

具体的代码实现,可以参见Wote用python语言写的imgHash.py。代码很短,只有53行。使用的时候,第一个参数是基准图片,第二个参数是用来比较的其他图片所在的目录,返回结果是两张图片之间不相同的数据位数量(汉明距离)。

这种算法的优点是简单快速,不受图片大小缩放或改变纵横比的影响,增加或减少亮度或对比度,或改变颜色,对hash值都不会太大的影响。缺点是图片的内容不能变更。如果在图片上加几个文字,它就认不出来了。所以,它的最佳用途是根据缩略图,找出原图。

实际应用中,往往采用更强大的pHash算法和SIFT算法,它们能够识别图片的变形。只要变形程度不超过25%,它们就能匹配原图。这些算法虽然更复杂,但是原理与上面的简便算法是一样的,就是先将图片转化成Hash字符串,然后再进行比较。

1.2.1范例

#include "stdafx.h"
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
 
#pragmacomment(lib,"opencv_core2410d.lib")         
#pragmacomment(lib,"opencv_highgui2410d.lib")         
#pragmacomment(lib,"opencv_imgproc2410d.lib")   
 
 
using namespace std;
 
 
int _tmain(int argc, _TCHAR* argv[])
{
 
   string strSrcImageName = "swan.jpg";
 
   cv::Mat matSrc, matSrc1, matSrc2;
 
   matSrc = cv::imread(strSrcImageName, CV_LOAD_IMAGE_COLOR);
   CV_Assert(matSrc.channels() == 3);
 
   cv::resize(matSrc, matSrc1, cv::Size(357, 419), 0, 0,cv::INTER_NEAREST);
   //cv::flip(matSrc1, matSrc1, 1);
   cv::resize(matSrc, matSrc2, cv::Size(2177, 3233), 0, 0,cv::INTER_LANCZOS4);
 
   cv::Mat matDst1, matDst2;
 
   cv::resize(matSrc1, matDst1, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC);
   cv::resize(matSrc2, matDst2, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC);
 
   cv::cvtColor(matDst1, matDst1, CV_BGR2GRAY);
   cv::cvtColor(matDst2, matDst2, CV_BGR2GRAY);
 
   int iAvg1 = 0, iAvg2 = 0;
   int arr1[64], arr2[64];
 
   for (int i = 0; i < 8; i++)
    {
       uchar* data1 = matDst1.ptr<uchar>(i);
       uchar* data2 = matDst2.ptr<uchar>(i);
 
       int tmp = i * 8;
 
       for (int j = 0; j < 8; j++)
       {
           int tmp1 = tmp + j;
 
           arr1[tmp1] = data1[j] / 4 * 4;
           arr2[tmp1] = data2[j] / 4 * 4;
 
           iAvg1 += arr1[tmp1];
           iAvg2 += arr2[tmp1];
       }
    }
 
   iAvg1 /= 64;
   iAvg2 /= 64;
 
   for (int i = 0; i < 64; i++)
    {
       arr1[i] = (arr1[i] >= iAvg1) ? 1 : 0;
       arr2[i] = (arr2[i] >= iAvg2) ? 1 : 0;
    }
 
   int iDiffNum = 0;
 
   for (int i = 0; i < 64; i++)
       if (arr1[i] != arr2[i])
           ++iDiffNum;
 
   cout<<"iDiffNum = "<<iDiffNum<<endl;
 
   if (iDiffNum <= 5)
       cout<<"two images are very similar!"<<endl;
   else if (iDiffNum > 10)
       cout<<"they are two different images!"<<endl;
   else
       cout<<"two image are somewhat similar!"<<endl;
 
   getchar();
   return 0;
}

1.2.2范例2

//均值Hash算法 
 string HashValue(Mat &src) 
 { 
    string rst(64,'\0'); 
    Mat img; 
    if(src.channels()==3) 
        cvtColor(src,img,CV_BGR2GRAY); 
   else 
       img=src.clone(); 
      /*第一步,缩小尺寸。
        将图片缩小到8x8的尺寸,总共64个像素,去除图片的细节*/ 
 
       resize(img,img,Size(8,8)); 
   /* 第二步,简化色彩(ColorReduce)。
      将缩小后的图片,转为64级灰度。*/ 
 
   uchar *pData; 
   for(int i=0;i<img.rows;i++) 
   { 
       pData = img.ptr<uchar>(i); 
       for(int j=0;j<img.cols;j++) 
       { 
           pData[j]=pData[j]/4;           } 
   } 
 
       /* 第三步,计算平均值。
      计算所有64个像素的灰度平均值。*/ 
   int average = mean(img).val[0]; 
 
       /* 第四步,比较像素的灰度。
     将每个像素的灰度,与平均值进行比较。大于或等于平均值记为1,小于平均值记为0*/ 
    Mat mask= (img>=(uchar)average); 
 
       /* 第五步,计算哈希值。*/ 
   int index = 0; 
   for(int i=0;i<mask.rows;i++) 
   { 
       pData = mask.ptr<uchar>(i); 
       for(int j=0;j<mask.cols;j++) 
       { 
           if(pData[j]==0) 
               rst[index++]='0'; 
           else 
                rst[index++]='1'; 
       } 
   } 
   return rst; 
 } 

1.2.3汉明距离计算

//汉明距离计算 
 intHanmingDistance(string &str1,string &str2) 
 { 
   if((str1.size()!=64)||(str2.size()!=64)) 
       return -1; 
   int difference = 0; 
   for(int i=0;i<64;i++) 
   { 
       if(str1[i]!=str2[i]) 
           difference++; 
   } 
   return difference; 
 }  
    原文作者:哈希算法
    原文地址: https://blog.csdn.net/jacky_ponder/article/details/70308517
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞