机器学习算法原理与实践(一)、基于感知哈希算法的图像搜索实现


【原创】Liu_LongPo 转载请注明出处
【CSDN】http://blog.csdn.net/llp1992

无意中看见一篇博客,是讲仿造google搜图的,链接如下:

Google 以图搜图 - 相似图片搜索原理 - Java实现

觉得挺好玩的,博主使用Java实现的,于是我用 OpenCv实现了下。

根据看到的博文,里面说到,Google图像搜索的关键技术是“感知压缩算法”(Perceptual hash algorithm),它的作用是对每张图片生成一个“指纹”(fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。看到这里我就突然来了兴趣想自己实现!

接下来,简单介绍以下感知哈希算法:实验室邹晓艺师兄已经总结得挺好了,所以,我摘取部分:

基于低频的均值哈希

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

而一张大的,详细的图片有很高的频率,而小图片缺乏图像细节,所以都是低频的。所以我们平时的下采样,也就是缩小图片的过程,实际上是损失高频信息的过程。如下图:

这里写图片描述

上述这些东西,我们在DSP课堂上都可以学到。

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

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

(2)简化色彩:将8*8的小图片转换成灰度图像。

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

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

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

这里写图片描述

计算一个图片的 hash 指纹就是这么简单,计算出来的hash指纹相对比原来的图片已经丢失了太多的信息了,以至于我们都怀疑这样的指纹是不是真的能够识别出相似的图片。不过,结果当然是不用怀疑的,数学之美与编程之美的结合!

如果图片放大或缩小,或改变纵横比,结果值也不会改变。增加或减少亮度或对比度,或改变颜色,对hash值都不会太大的影响。这种方法是被图片的最大的优点:计算速度快!

因为就像我们看到的,一幅图片被压缩,被转为灰度图,只采集hash指纹,这个过程计算量并不大,而这些指纹就相当于图片的特征。

比较两个图片的相似性,就是先计算这两张图片的hash指纹,也就是64位0或1值,然后计算不同位的个数(汉明距离)。如果这个值为0,则表示这两张图片非常相似,如果汉明距离小于5,则表示有些不同,但比较相近,如果汉明距离大于10则表明完全不同的图片。

实际情况中,我自己写的时候,发现汉明距离小于5的要求太苛刻了,汉明距离接近20的两张图片的相似度还是挺高的。

其实看到这里,你可以根据这个思路自己去实现以下,不用看下面的代码,一直觉得这样才是学习的好方法,看博客拓展思路,自己去实现。

接下来说代码实现:

首先介绍 getImageFinger 函数,也就是生成图像的指纹,代码如下:

// 寻找图像指纹
// 参数: 输入图像 img ,图像的指纹数组
void getImageFinger(IplImage * img,char *status)
{
    int avrpixel= 0;
    int i,j;
    CvScalar scalar ;
    for (i = 0;i<8;i++)
    {
        for (j = 0;j<8;j++)
        {
            scalar = cvGet2D(img,i,j);
            avrpixel += scalar.val[0];
        }
    }
    avrpixel = avrpixel / 64 ;
    int k = 0;
    for (i = 0;i<8;i++)
    {
        for (j = 0;j<8;j++)
        {
            if (cvGet2D(img,i,j).val[0] > avrpixel)
            {
                status[k++] = 1;
            }else
            {
                status[k++] = 0;
            }
        }
    }
}

根据上面的原理就可以理解代码。

接下来是计算汉明距离的函数:

// 计算汉明距离
// 参数: 两幅图的指纹数组
// 输出: 汉明距离
int calHammDist(char * src_img,char * dst_img)
{
    int dist = 0;
    for(int i=  0;i<MAX_PIXEL_NUMBER;i++)
    {
        if (src_img[i] != dst_img[i])
        {
            dist ++;
        }
    }
    return dist;
}

搜素图片匹配的过程代码:

    // 读取本地某目录下的全部图片进行搜索匹配
    int show_number = 0;
    for (int i = 0;i<7;i++)
    {
        sprintf(FilePath,"F://image//%d.JPG",i);
        dst_img = cvLoadImage(FilePath);

        resz_dst_img = cvCreateImage(cvSize(8,8),IPL_DEPTH_8U,3);
        cvResize(dst_img,resz_dst_img,1);

        com_dst_img = cvCreateImage(cvSize(8,8),IPL_DEPTH_8U,1);
        cvCvtColor(resz_dst_img,com_dst_img,CV_RGB2GRAY);

        getImageFinger(com_dst_img,status_dst);
        dis = calHammDist(status_src,status_dst);
        cout<<" dis :"<<dis<<endl;
        if (dis<20)
        {
            showimgindex[show_number++] = i;
        }
    }

for 循环中 i 小于7 是因为我只放了7张图片,具体可以自己修改。

效果如下:

这是我本地的要搜索的7张图片

这里写图片描述

左边第一张为输入图片,第二张和第三张是匹配出来的相似度比较高的图片,第二张其实就是原来的图片,只是改变了大小尺寸,根据下面的命令行打印出来的汉明距离,第一张跟第二张的汉明距离为0,第一张和第三张的汉明距离为10,相似度还是比较高的

这里写图片描述
这里写图片描述

这里就不贴出全部代码了,实现起来不是很难,大家可以自己去试试。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值