基于AS3.0的相似图像搜索算法实现

Posted on Aug 14, 2012

Google的图像搜索,可以根据用户上传或者网络上的图片找到与此最相似的图片(百度也有类似功能)。

google image search

阮一峰的博客曾经提到过 “感知哈希算法(Perceptual hash algorithm)”,主要介绍了 Dr. Neal Krawetz基于感知哈希算法的”平均哈希算法(Average Hash algorithm)”。相比其他算法,这种算法简单快速,很容易实现。

对于图像来说,高频信息体现图像的细节,低频信息体现图像的边缘和轮廓。一幅大而清晰的图像不仅包含低频,而且会包含很多高频分量。而小图片因为缺乏细节部分,往往只有低频信息。因此用只包含低频分量的图像来做图像相似度匹配是非常合适的。

感知哈希算法的基本原理是根据图像特征生成一个特定(但非唯一)的指纹,根据指纹来比较图像的相似度。其特点是,即使图像放大缩小,调整高宽,或者有少许色彩变化(对比度,亮度等等),仍然可以匹配到相似的图像。

平均哈希算法(Average Hash algorithm)

实现步骤如下:

  1. 缩小尺寸:移除高频部分,将图像缩小至8×8像素,不考虑高宽比; {% img https://pjkm4w.bay.livefilestore.com/y1pEQIINXhwtkYhvSu94Q9L0iRya9-bx-0UDrNZDL8yb3_2ODB88DXQAhb3f4Oe8J9Kj8eElRgB1nZy95uSdf8Uye0dNGwDPpWW/reduce_size.jpg 64 64 %}

  2. 减少颜色:转为灰度图,将64像素的RGB 64×3个颜色值简化为 64个色值; {% img https://pjkm4w.bay.livefilestore.com/y1pEQIINXhwtkZyypWiFbXh51KEaUsH1RT_1Qli_UpOupdO-_1PbPXCbINY0UQRbfgfHPmXyoTqz0kcq963cYITTxHLInBtZQGl/reduce_color.jpg 64 64 %}

  3. 计算平均值:计算64个颜色的均值;

  4. 比较灰度值:将每个像素的颜色值与平均值比较,小于均值记0,否则记1;

  5. 计算哈希值:将上述的比较结果拼起来构成一个64位的整数。

pHash算法

平均哈希简单快速,但是缺点也显而易见,如果图像的gamma校正或者直方图变化,识别就差多啦。这是因为图像颜色经过了非线性的变换,而上述算法是基于平均值的。更加健壮的算法是pHash,pHash使用DCT(Discrete Cosine Transform 离散余弦变换)来降频。(高数、信号处理完璧归赵了吗?)。Neal自己对pHash作了稍稍改动,但概念相同,其实现步骤如下:

  1. 缩小尺寸:目的是减少DCT计算量,而不是移除高频分量,32*32像素为宜;
  2. 减少颜色:进一步减少计算量;
  3. 计算DCT:DCT将图像分割处理成频率和标量的集合。JPEG有损压缩使用8×8 DCT,这里使用32×32 DCT;
  4. 减少DCT:采用32×32 DCT,但只保留左上角的8×8 部分,取该图像的最低频部分。
  5. 计算均值:和平均哈希一样,计算DCT均值;
  6. 再次减少DCT:和DCT均值比较,大于等于记1,小于记0。得到一组64位数。这组数据没有明确最低频的确切值,但是描述了和平均频率的相对关系。因此只要图像整体结构不变,结果也将保持不变。即便经过gamma校正和直方图改变也可以正确匹配。
  7. 构建哈希:将上述64位的结果转换为64位整数。即得到该图像的Hash指纹。想知道指纹图是什么样?只要设置值(根据各个位是1还是0使用+255和-255)并将32×32 DCT转换回32×32像素的图像。 phash-dct = 8a0303f6df3ec8cd

乍看上去像是随机的点点。但是仔细看就会发现深色点围成的圈是人物的头发,右边的黑点是背景墙上的那道水平线。

AS3.0 的算法实现

平均哈希的算法比较简单,所以简单地用ActionScript写了一下。

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	
	import mx.controls.Alert;
	
	public class PHAImage
	{
		public function PHAImage()
		{
		}
		
		public static function process(bmpData:BitmapData):String
		{
			//processing the image
			trace("Start processing...n");
			//scaling and converting
			var resizedData:BitmapData = reduceSize(bmpData,8,8);
			//转换为灰度
			var greyBmp:BitmapData = reduceColor(resizedData);
			//计算灰度平均值
			var avgGrey:uint = calcAvgGrey(greyBmp);
			trace("Average GreyScale:0x"+avgGrey.toString(16)+"n");
			//比较灰度值与平均值,建立哈希指纹
			var hashArr:Array = calcAvgHash(greyBmp, avgGrey);
			trace("hashArr: ",hashArr.join(""));
			return hashArr.join("");
		}
		
		private static function reduceSize(source:BitmapData,width:Number = 8, height:Number=8):BitmapData
		{
			var newData:BitmapData = new BitmapData(width,height);
			var matrix:Matrix = new Matrix();
			//缩小至 8x8
			matrix.scale(newData.width/source.width, newData.height/source.height);
			newData.draw(source,matrix);
			
			return newData;
		}
		
		private static function reduceColor(source:BitmapData):BitmapData
		{
			var result:BitmapData = new BitmapData(source.width,source.height);
			for(var i:int = 0; i < source.height; i++)
			{
				for(var j:uint = 0; j < source.width; j++)
				{
					var color:uint = source.getPixel(i, j);
					var red:uint = (color & 0xFF0000) >> 16;
					var green:uint = (color & 0x00FF00) >> 8;
					var blue:uint = (color & 0x0000FF) >> 0;
					//var bwColor:uint = (red + green + blue) / 3;
					var bwColor:uint = (red * 30 + green * 59 + blue * 11) / 100;
					// puts the average in each channel
					bwColor = (bwColor << 16) + (bwColor << 8) + bwColor; 
					result.setPixel(i, j, bwColor);
				}
			}
			return result;
		}
		
		private static function calcAvgGrey(bmpData:BitmapData):uint
		{
			var vecGrey:Vector.<uint> = bmpData.getVector(bmpData.rect);
			var total:uint = 0;
			var length:uint = vecGrey.length;
			for(var i:int = 0; i< length;i++)
			{
				total += (vecGrey[i] & 0x00FFFFFF);
			}
			
			return uint(total/vecGrey.length);
		}
		
		//计算哈希
		private static function calcAvgHash(bmpData:BitmapData, avgValue:uint):Array
		{
			var vecGrey:Vector.<uint> = bmpData.getVector(bmpData.rect);
			var length:uint = vecGrey.length;
			var hashArr:Array = [];
			for(var i:int = 0; i< length;i++)
			{
				//ARGB 32位数据,只取RGB
				var pxColor:uint = vecGrey[i] & 0x00FFFFFF;	
				//是否小于灰度均值,小于记0,否则记0
				var value:uint =  pxColor < avgValue ? 0:1;
				hashArr.push(value);
			}
			return hashArr;
		}
	}
}

值得一提的几点:

  1. 灰度的转换,可以采用(R+G+B)/3,也可以(R * 30 + G * 59 + B * 11) / 100;而且还有用移位取代除法以获得更高性能的方法;
  2. ActionScript 没有64位整型,在获得最后hash,我输出成了字符串。如果想用64Int可以自己定义个数据结构,也可以使用 protoc-gen-as3 的Int64/UInt64。
  3. 图像相似度的比较:只需计算2个哈希值的汉明距离,距离越小,相似度越高。

实例演示

一般搜索要用到服务器,为了省事,直接写了个简单的AIR应用来测试算法。浏览文件目录,遍历并计算目录下所有图像文件(jpg,png,gif)的哈希值。然后上传一个图片,便会找出目录中与之汉明距离最短的图片。 demo

应用比较粗糙,包含了测试需要的基本功能,即使图像被缩小并且去除了色彩,依然可以匹配到。

总结

简要介绍了用于图像相似度匹配的感知哈希算法,以及其简化版——平均哈希算法—— 的功能,特点以及实现步骤。使用ActionScript 3.0 实现了平均哈希算法并编写应用测试,验证了该算法的性能和可行性。

图像处理的算法也非常多,目前相似图片匹配的算法除了上述的还有pHash,SIFT等等。但是图像处理也是数字信号的处理,因此高等数学,信号处理的知识非常重要,也是我需要提高和学习的重点之一。

吐槽:看了Neal的几篇文章,里面都提到了 “his next wife, actress Alyson Hannigan”。 alyson 以及这段:

A couple of people at Reddit complained about my used of Alyson Hannigan as the example image. (She’s so cute that she is distracting.) However, it is actually part of my master plan. (Don’t tell Alyson!) I’m hoping that she’ll notice it one day and call me. Maybe she and I can double-date at Defcon this year… sigh

有几个家伙在Reddit上抱怨我用Alyson Hannigan作为示例图片。(她太可爱太让我着迷啦)不管怎样,这确实是我宏伟计划的一个部分。(别告诉Alyson!)我盼着哪天她能注意到这个还打电话给我。那样或许我和她可以在今年的Defcon(DEF CON ,世界上规模最大的黑客大会之一)来个Double-date(双双约会,两对情侣约会)。。。唉

略带Nerd气质的Geek风范尽显无遗啊~ 哈哈 😎


参考资料

  1. http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
  2. http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html
  3. http://www.cnblogs.com/technology/archive/2012/07/12/Perceptual-Hash-Algorithm.html
  4. http://www.hackerfactor.com/blog/index.php?/archives/355-How-I-Met-Your-Mother-Through-Photoshop.html
  5. http://phash.org/
  6. http://zh.wikipedia.org/wiki/%E7%A6%BB%E6%95%A3%E4%BD%99%E5%BC%A6%E5%8F%98%E6%8D%A2
  7. http://blog.csdn.net/lin_zyang/article/details/2603028
  8. http://blog.csdn.net/v_JULY_v/article/details/6186942
  9. http://www.isnowfy.com/similar-image-search/
  10. http://blog.csdn.net/v_JULY_v/article/details/6186942
  11. http://code.google.com/p/protoc-gen-as3/