1.1前言
在Google的首页上有"相似图片搜索"功能。你可以用一张图片,搜索互联网上所有与它相似的图片。点击搜索框中照相机的图标。
一个对话框会出现。
你输入网片的网址,或者直接上传图片,Google就会找出与其相似的图片。下面这张图片是美国女演员Alyson Hannigan。
上传后,Google返回如下结果:
类似的"相似图片搜索引擎"还有不少,TinEye甚至可以找出照片的拍摄背景。
==========================================================
这种技术的原理是什么?计算机怎么知道两张图片相似呢?
根据Neal Krawetz博士的解释,原理非常简单易懂。我们可以用一个快速算法,就达到基本的效果。
这里的关键技术叫做"感知哈希算法"(Perceptual hashalgorithm),它的作用是对每张图片生成一个"指纹"(fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。。达到图片比较目的且利用信息指纹比较有三种算法,这些算法都很易懂,下面先介绍基于低频的均值哈希,pHash和差异哈希算法(dHash)后续介绍:
1.2基于低频的均值哈希
下面是一个最简单的一张图片就是一个二维信号,它包含了不同频率的成分。如下图所示,亮度变化小的区域是低频成分,它描述大范围的信息。而亮度变化剧烈的区域(比如物体的边缘)就是高频的成分,它描述具体的细节。或者说高频可以提供图片详细的信息,而低频可以提供一个框架。
而一张大的,详细的图片有很高的频率,而小图片缺乏图像细节,所以都是低频的。所以我们平时的下采样,也就是缩小图片的过程,实际上是损失高频信息的过程。下面5张图依次是原图,放缩至64*64、32*32、16*16、8*8的图。
均值哈希算法主要是利用图片的低频信息,其工作过程如下:
(1) 缩小尺寸:去除高频和细节的最快方法是缩小图片,将图片缩小到8x8的尺寸,总共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;
}