看起来像它——图像搜索其实也不难 (图像相似,图像指纹,phash hash,图像搜索) 使用时候记得看这文章的评论

链接: http://pan.baidu.com/s/1o7ScyVo 密码: h8eb    这个文章的代码

另一个类似的代码  链接: http://pan.baidu.com/s/1hsFDCNy 密码: jxus

http://blog.csdn.net/luoweifu/article/details/8220992                 使用时候记得看这文章的评论

看起来像它——图像搜索其实也不难

标签: pHash图像搜索图像识别图片搜索算法

2012-11-24 23:14 13142人阅读 评论(13) 收藏 举报

 分类:

图像处理(26) 

目录(?)[+]

使用时候记得看这文章的评论

这是我第一次翻译外文文章,如果翻译的不好,还望大家多包含!以下黑色部分是作者原文的翻译,红色部分是我本人自己的理解和对其的补充。

原文:Looks Like It

在google里对的搜索结果是

下面是我用pHash算法(java)实现的结果:

十张比较的图如下:

source: f0a0000030400000

1-5    2-5    3-0    4-5    5-5    6-5    7-5    8-7    9-6    10-3    11-5

f0a0000030400000是原图片的指纹数

下面的一行“a-b”型的数据,a表示序号,b表示汉明距离,b越小就越相似;汉明距离<=5表示很相似。

实现源码下载:

http://download.csdn.net/download/luoweifu/4807319

----------------------------------------------------译文------------------------------------------------------------

在过去的几个月,我不停地寻求“TinEye 如何工作”的答案,或者说它是如何搜索图片的。

结果是我仍没法知道TinEye图片搜索引擎是如何工作的,他们并没有公开他们所用使用的算法细节。然而,根据它返回的结果,呈现给我的是感知哈希算法的一个变种。

这是有感知的

感知哈希(hash)算法描述了一个有可比较的哈希函数的类。图像特征被用于生成独特的(但不是唯一的)指纹,而这些指纹是可比较的。

感知哈希与像MD5和SHA1这样的加密哈希(散列)函数是不同的概念。加密哈希的hash值是随机的,数据用于生成像随机数种子的散列行为,所以相同的数据会产生相同的结果,不同的数据会产生不同的结果。比较两个SHA1的hash值,实际上只告诉我们两个东西,如果hash值是不同的,则数据也是不同的;如果hash值是相同的,则数据是相似的。(因为可能存在hash冲突,相同的hash值会产生不同的数据)。相比之下,感知哈希是可比较的——给你一种两个数据集之间相似的感觉。

我遇到的每一个感知哈希算法都有一个共同的特征:图片可以被放大或缩小,有不同的纵横比,甚至轻微的着色差异(对比度、亮度等),它们依然能够匹配相似的图片,TinEye也有同样的性能。(但TinEye似乎做了更多,我稍后会去了解)

美丽之道

如何创建感知哈希呢?有一些常见的算法,但没有一个是很复杂的。(我总是很惊讶,为什么如此间单却几乎所有的常见算法都能工作)。最间单的算法之一应该是基于低频的均值哈希。

一张高频率的图片可以提供详细的信息,而低频率的图片只显示一个框架;一张大的,详细的图片有很高的频率,而小图片缺乏图像细节,所以都是低频的。为了演示均值哈希算法如何工作,我将使用我妻子—— Alyson Hannigan的图片。

1.缩小尺寸

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

2.简化色彩

将8*8的小图片转换成灰度图像,将64个像素的颜色(red,green,blue)转换成一种颜色(黑白灰度)。

3.计算平均值

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

4.比较像素的灰度

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

5.计算hash值

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

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

如果你想比较两张图片,为每张图片构造hash值并且计算不同位的个数。(汉明距离)如果这个值为0,则表示这两张图片非常相似,如果汉明距离小于5,则表示有些不同,但比较相近,如果汉明距离大于10则表明完全不同的图片。

效果更佳的pHash

虽然均值哈希更简单且更快速,但是在比较上更死板、僵硬。它可能产生错误的漏洞,如果有一个伽马校正或颜色直方图被用于到图像。这是因为颜色沿着一个非线性标尺 - 改变其中“平均值”的位置,并因此改变哪些高于/低于平均值的比特数。

一个更健壮的算法叫pHash,(我使用的是自己改进后的算法,但概念是一样的) pHash的做法是将均值的方法发挥到极致。使用离散余弦变换(DCT)降低频率。

1.缩小尺寸

pHash以小图片开始,但图片大于8*8,32*32是最好的。这样做的目的是简化了DCT的计算,而不是减小频率。

2.简化色彩

将图片转化成灰度图像,进一步简化计算量。

3.计算DCT

DCT是把图片分解频率聚集和梯状形,虽然JPEG使用8*8的DCT变换,在这里使用32*32的DCT变换。

4.缩小DCT

虽然DCT的结果是32*32大小的矩阵,但我们只要保留左上角的8*8的矩阵,这部分呈现了图片中的最低频率。

5.计算平均值

如同均值哈希一样,计算DCT的均值,

6.进一步减小DCT

这是最主要的一步,根据8*8的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为”1”,小于DCT均值的设为“0”。结果并不能告诉我们真实性的低频率,只能粗略地告诉我们相对于平均值频率的相对比例。只要图片的整体结构保持不变,hash结果值就不变。能够避免伽马校正或颜色直方图被调整带来的影响。

7.构造hash值

将64bit设置成64位的长整型,组合的次序并不重要,只要保证所有图片都采用同样次序就行了。将32*32的DCT转换成32*32的图像。

与均值哈希一样,pHash同样可以用汉明距离来进行比较。(只需要比较每一位对应的位置并算计不同的位的个数)

同类中的最佳算法?

自从我做了大量关于数码照片取证和巨幅图片的收集工作之后,我需要一种方法来搜索图片,所以,我用了一些不同的感知哈希算法做一个图片搜索工具,根据我并不很科学但长期使用的经验来看,我发现均值哈希比pHash显著地要快。如果你找一些明确的东西,均值Hash是一个极好的算法,例如,我有一张图片的小缩略图,并且我知道它的大图存在于一个容器的某个地方,均值哈希能算法快速地找到它。然而,如果图片有些修改,如过都添加了一些内容或头部叠加在一起,均值哈希就无法处理,虽然pHash比较慢,但它能很好地容忍一些小的变型(变型度小于25%的图片)。

其次,如果,你运行的服务器像TinEye这样,你就可以不用每次都计算pHash值,我确信它们肯定之前就把pHash值保存在数据库中,核心的比较系统非常快,所以只需花费一次计算的时间,并且几秒之内能进行成千上百次的比较,非常有实用价值。

改进

有许多感知哈希算法的变形能改进它的识别率,例如,在减小尺寸之前可以被剪裁,通过这种方法,主体部分周围额外的空白区域不会产生不同。也可以对图片进行分割,例如,你有一个人脸识别算法,然后你需要计算每张脸的hash值,

可以跟踪一般性的着色(例如,她的头发比蓝色或绿色更红,而背景比黑色更接近白色)或线的相对位置。

如果你能比较图片,那么你就可以做一些很酷的事情。例如, 你可以在GazoPa搜索引擎拖动图片,和TinEye一样,我并不知道GazoPa工作的细节,然而它似乎用的是感知哈希算法的变形,由于哈希把所有东西降低到最低频率,我三个人物线条画的素描可以和其它的图片进行比较——如匹配含有三个人的照片。

算法实现

关于均值哈希算法的实现,请参考:Google 以图搜图 - 相似图片搜索原理 - Java实现http://blog.csdn.net/luoweifu/article/details/7733030

下面详细讲一下pHash算法的实现

基体的步骤已经在“效果更佳的pHash”中讲了,下面对应地给出java代码的实现:

1.缩小尺寸

[java] view plain copy

  1. /**
  2. * 局部均值的图像缩小
  3. * @param pix 图像的像素矩阵
  4. * @param w 原图像的宽
  5. * @param h 原图像的高
  6. * @param m 缩小后图像的宽
  7. * @param n 缩小后图像的高
  8. * @return
  9. */
  10. public static int[] shrink(int[] pix, int w, int h, int m, int n) {
  11. float k1 = (float) m / w;
  12. float k2 = (float) n / h;
  13. int ii = (int)(1 / k1); // 采样的行间距
  14. int jj = (int)(1 / k2); // 采样的列间距
  15. int dd = ii * jj;
  16. // int m=0 , n=0;
  17. // int imgType = img.getType();
  18. int[] newpix = new int[m * n];
  19. for (int j = 0; j < n; j++) {
  20. for (int i = 0; i < m; i++) {
  21. int r = 0, g = 0, b = 0;
  22. ColorModel cm = ColorModel.getRGBdefault();
  23. for (int k = 0; k <  jj; k++) {
  24. for (int l = 0; l <  ii; l++) {
  25. r = r
  26. + cm.getRed(pix[(jj * j + k) * w
  27. +  (ii * i + l)]);
  28. g = g
  29. + cm.getGreen(pix[(jj * j + k) * w
  30. +  (ii * i + l)]);
  31. b = b
  32. + cm.getBlue(pix[ (jj * j + k) * w
  33. +  (ii * i + l)]);
  34. }
  35. }
  36. r = r / dd;
  37. g = g / dd;
  38. b = b / dd;
  39. newpix[j * m + i] = 255 << 24 | r << 16 | g << 8 | b;
  40. // 255<<24 | r<<16 | g<<8 | b 这个公式解释一下,颜色的RGB在内存中是
  41. // 以二进制的形式保存的,从右到左1-8位表示blue,9-16表示green,17-24表示red
  42. // 所以"<<24" "<<16" "<<8"分别表示左移24,16,8位
  43. // newpix[j*m + i] = new Color(r,g,b).getRGB();
  44. }
  45. }
  46. return newpix;
  47. }

2.简化色彩

[java] view plain copy

  1. /**
  2. *  将图片转化成黑白灰度图片
  3. * @param pix 保存图片像素
  4. * @param iw 二维像素矩阵的宽
  5. * @param ih 二维像素矩阵的高
  6. * @return 灰度图像矩阵
  7. */
  8. public static int[] grayImage(int pix[], int w, int h) {
  9. //int[] newPix = new int[w*h];
  10. ColorModel cm = ColorModel.getRGBdefault();
  11. for(int i=0; i<h; i++) {
  12. for(int j=0; j<w; j++) {
  13. //0.3 * c.getRed() + 0.58 * c.getGreen() + 0.12 * c.getBlue()
  14. pix[i*w + j] = (int) (0.3*cm.getRed(pix[i*w + j]) + 0.58*cm.getGreen(pix[i*w + j]) + 0.12*cm.getBlue(pix[i*w + j]) );
  15. }
  16. }
  17. return pix;
  18. }

3.计算DCT

这一部分请参考我的上一篇博客:离散余弦变换(含源码)

4.缩小DCT

DCT的结果是32*32大小的矩阵,但我们只要保留左上角的8*8的矩阵,所以只需要设置两层的for循环是从0到7就可以了。

5.计算平均值

[java] view plain copy

  1. /**
  2. * 求灰度图像的均值
  3. * @param pix 图像的像素矩阵
  4. * @param w 图像的宽
  5. * @param h 图像的高
  6. * @return 灰度均值
  7. */
  8. private static int averageGray(int[] pix, int w, int h) {
  9. int sum = 0;
  10. for(int i=0; i<h; i++) {
  11. for(int j=0; j<w; j++) {
  12. sum = sum+pix[i*w + j];
  13. }
  14. }
  15. return (int)(sum/(w*h));
  16. }

6.构造hashf值

[java] view plain copy

  1. StringBuilder sb = new StringBuilder();
  2. for(int i=0; i<FHEIGHT; i++) {
  3. for(int j=0; j<FWIDTH; j++) {
  4. if(dctPix[i*FWIDTH + j] >= avrPix) {
  5. sb.append("1");
  6. } else {
  7. sb.append("0");
  8. }
  9. }
  10. }
  11. //System.out.println(sb.toString());
  12. long result = 0;
  13. if(sb.charAt(0) == ‘0‘) {
  14. result = Long.parseLong(sb.toString(), 2);
  15. } else {
  16. //如果第一个字符是1,则表示负数,不能直接转换成long,
  17. result = 0x8000000000000000l ^ Long.parseLong(sb.substring(1), 2);
  18. }
  19. sb = new StringBuilder(Long.toHexString(result));
  20. if(sb.length() < 16) {
  21. int n = 16-sb.length();
  22. for(int i=0; i<n; i++) {
  23. sb.insert(0, "0");
  24. }
  25. }
时间: 2024-10-10 17:01:51

看起来像它——图像搜索其实也不难 (图像相似,图像指纹,phash hash,图像搜索) 使用时候记得看这文章的评论的相关文章

搜索分析(DFS、BFS、递归、记忆化搜索)

搜索分析(DFS.BFS.递归.记忆化搜索) 1.线性查找 在数组a[]={0,1,2,3,4,5,6,7,8,9,10}中查找1这个元素. (1)普通搜索方法,一个循环从0到10搜索,这里略. (2)递归(从中间向两边) 1 //递归一定要写成记忆化递归 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool vis[11]; 5 int count1=0; 6 7 void search(int n){ 8 count1++; 9

HTML &lt;area&gt; 标签 带有可点击区域的图像映射(图像映射指的是带有可点击区域的图像)

例子: <img src="planets.gif" width="145" height="126" alt="Planets" usemap="#planetmap"> <map name="planetmap"> <area shape="rect" coords="0,0,82,126" href="

本图片处理类功能非常之强大可以实现几乎所有WEB开发中对图像的处理功能都集成了,包括有缩放图像、切割图像、图像类型转换、彩色转黑白、文字水印、图片水印等功能

import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Toolkit; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform;

从手淘搜索到优酷短视频,阿里巴巴是如何在搜索推荐领域下应用深度学习的?

摘要:深度学习是一个既可以处理特征.学习特征又可以实现最后的排序打分的一套整体解决方案,借助深度学习的解决方案,搜索推荐的工作方式将发生巨大的变化.想知道阿里巴巴如何将在搜索推荐领域下应用深度学习技术的吗?想知道手淘和优酷搜索结果的个性化又是如何实现的吗?本文不容错过! 本节视频地址:http://click.aliyun.com/m/48161/ PDF下载:http://click.aliyun.com/m/49207/ 演讲嘉宾简介: 孙修宇(花名:翎翀),阿里巴巴机器智能技术实验室算法专

hash算法搜索获得api函数地址的实现

我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较.这样弊端很多,例如如果我们定义一个杀毒软件比较敏感的api函数字符串,那么可能就会增加杀毒软件对我们的程序的判定值,而且定义这些字符串还有一个弊端是占用的字节数较大.我们想想如何我们的api函数字符串通过算法将它定义成一个4字节的值,然后在GetProcAddress中把AddressOfNames表中的每个地址指向的

GD库的基本信息,图像的旋转、水印、缩略图、验证码,以及图像类的封装

GD库检测 <?php phpinfo(); ?> GD库安装• Windows 使用phpstudy • Linux 编译安装 –with-gd• Linux 编译安装扩展 GD库支持的图像格式 使用 gd_info() 函数 检测服务器支持的图像格式 图像信息处理 <?php //获取图像详细信息 $image = '../image/b.png'; $info = getimagesize($image); var_dump($info); $string = file_get_c

优秀管理者必看--你的下属跳槽的八大征兆,你注意到了吗?(超级准,不看后悔的。。。)

眼下随着经济形式的变化,从互联网.移动互联网到传统企业,人才竞争愈演愈烈,留住人才特别是核心人才是每个管理者和人力资源工作者的最重要职责之一. 如果当你的下属特别是对公司或部门很关键的人把辞职报告递给你的时候,你才意识到他的重要性,那就为时太晚喽. 通常,一个人做出辞职的决定是要经过长时间的深思熟虑.对比和考察的,一旦提交辞呈,意味着他已经与另一家公司签了Offer,这时一般人不会因为公司的挽留而动心,因为他与另一家公司已经有了契约关系. 其实一个人离开公司前的1-3个月(对于经理人可能会长达6

阶段一-02.分类,推荐,搜索,评价,购物车开发-第2章 商品推荐+搜索功能实现-2-1 商品推荐 - 需求分析与sql查询

下面关于新商品一些商家的推荐. 下面这些没一个都是一个分类,滚动条向上滚动,下面的一个个分类数据都展示出来.判断页面的滚动,实现懒加载. 滚动条触底后,会把下一个要展示的商品分类懒加载. 前端的代码 scroll的滚动的监听, index就是我们的vue的对象.在页面的最上方定义的 首先获取当前的分类的list catIndex html内容的渲染.rootCat就是一个json对象. 右侧包含最新的6个商品 后端 多表关联查询,一个是分类表,一个是商品表. 商品表的表结构. cat_id:子分

[看书][CSS精粹(第2版)]第三章 CSS和图像 & HTML网页布局

本章主要讲述作为背景图像方面的技巧. 1.边框(添加边框样式.去除边框): 2.为页面设置背景图像,位置设定,固定背景图像: 3.为任意元素设置背景图像: 4.文字放在图像的上面(作为背景图像): 5.为文档添加多个背景图像(本例利用html和body元素产生多重背景图像的效果): 6.在页面中使用透明效果(使用PNG格式图像). 利用网络搜索补充学习了padding的4个方向顺序(上右下左 顺时针),流式布局(看网上的解释结合书里的描述,大概是指页内布局元素采用百分数指定位置和大小). [个人