.net中的GDI+能非常方便的进行图象处理,但是并未直接提供进行图片比较的类或者方法,本文的目的是探讨如何进行图片比较。
首先要说明的是,进行比较的两幅图片必须具有相同的格式,并且最好是未经压缩的图片格式。否则,不能进行完全的相同性对比,只能做近似判断,比如各种颜色分量相差5%即认为一致。
在自己动手之前先GOOGLE一番,找到了下面两篇比较典型的文章:
(1)http://groups.google.com/group/microsoft.public.dotnet.languages.csharp/msg/4c8beaa229c2cbd6
这篇文章使用Image.GetPixel方法获取每个像素点的Color,然后将各点组合成一个Hash值。关键代码段:
[CLSCompliant(false)]
public static uint GetBitmapHashValue(
Bitmap image)
{
uint result = 0xFFFFFFFF;
for (int x = 0; x < image.Width; x++)
for (int y = 0; y < image.Height; y++)
result =
BitmapHasher.CRCTable[
(result ^ image.GetPixel(x, y).ToArgb()) & 0xFF] ^
(result >> 8);
return result ^ 0xFFFFFFFF;
}
(2)http://topic.csdn.net/u/20080808/09/c92a057e-ebd0-438c-bc1d-64d0a78127f7.html
CSDN论坛的这个帖子有很多实现图片比较的思路,比如:
(a)比较直接用 memcmp , 10M以内的数据不会超过 1毫秒
当然你不能用 GetPix, GetPix非常慢, 应该直接取出内存块来比较,
图像压缩可以把前后两幅图像相减, 再找个压缩算法压缩相减的结果
另外一边只要加上这个结果就是新的图像
(b)将屏幕分成固定的若干小块,对其分别编号,分块比较,每次只传送发生改变的块
(c)请参考请他VNC开源项目。
它们通常是安装钩子监视屏幕区域的变化,然后压缩传送变化的图片区。
用判断图像变化的办法效率太低,很少被使用。
(d)保存上一张图片,抓到一张新图后,在内存中按块用memcmp比较,如果发现有不同,就发送此块,然后到客户端组装起来
由于本文只讨论图片比较,所有(c)中的安装系统钩子不考虑,其他几点无非就是用GetPixel按像素比较,或者得到图片的内存块然后比较内存块是否一致。
1.使用GetPixel得到各像素,然后逐个像素进行对比:
/// <summary>
/// 比较两幅图片是否一致
/// </summary>
/// <param name="bitmap1">图片1</param>
/// <param name="bitmap2">图片2</param>
/// <returns>如果两幅图片相同,返回0;如果图片1小于图片2,返回小于0的值;如果图片1大于图片2,返回大于0的值。</returns>
public static int BitmapCompare(Bitmap bitmap1, Bitmap bitmap2)
{
int result = 0; //假设两幅图片相同
if (bitmap1 == null || bitmap2 == null)
return -1;
if (bitmap1.Width == bitmap2.Width && bitmap1.Height == bitmap2.Height)
{
for (int i = 0; i < bitmap1.Width; i++)
{
for (int j = 0; j < bitmap1.Height; j++)
{
Color color1 = bitmap1.GetPixel(i, j);
Color color2 = bitmap2.GetPixel(i, j);
if (color1 != color2)
{
result = color1.ToArgb() - color2.ToArgb();
break;
}
}
if (result != 0)
break;
}
}
else if (bitmap1.Width != bitmap2.Width)
{
result = bitmap1.Width - bitmap2.Width;
}
else if (bitmap1.Height != bitmap2.Height)
{
result = bitmap1.Height - bitmap2.Height;
}
return result;
}
2.得到图片数据内存块,然后比较内存块是否一致:
用Bitmap.LockBits方法可以得到BitmapData(位图数据),BitmapData.Scan0指向了位图数据部分的基地址。BitmapData.Stride给出了图象中每行所占的字节数目,注意BitmapData.Stride并不等于BitmapData.Width,原因如下:(1)BitmapData.Width是每行的像素数目,每个像素所占的字节数跟PixelFormat(像素格式)有关,可以是1、4、8、16、24、32、48、64等位数;(2)系统将图片数据行在内存中进行了对齐,每行所占字节数是4的倍数,且总是大于等于Width*BitsPerPixel/8。Marshal.Copy方法复制内存块到字节数组。
得到图片数据内存块的代码如下:
BitmapData bmd1 = bitmap1.LockBits(new Rectangle(0, 0, bitmap1.Width, bitmap1.Height), ImageLockMode.ReadOnly, bitmap1.PixelFormat); //得到图片数据对象
int bytes = bmd1.Stride * bitmap1.Height; //图片数据大小
byte[] buff1 = new byte[bytes]; //保存图片数据的字节数组
Marshal.Copy(bmd1.Scan0, buff1, 0, Marshal.SizeOf(typeof(byte)) * bytes); //复制图片数据块
//在这里对图片数据块执行操作
bitmap1.UnlockBits(bmd1); //解锁图片数据块
对内存块进行比较的方法有以下几种:
(1)C API函数memcmp,该函数的原型为:
int memcmp(const void *buf1,const void *buf2,size_t count);
PINVOKE引用方式为:
[DllImport("msvcrt.dll")]
private static extern IntPtr memcmp(byte[] b1, byte[] b2, IntPtr count);
(2)用Marshal.ReadByte方法读取内存中的字节,然后逐字节进行比较。
BitmapData bmd1 = bitmap1.LockBits(new Rectangle(0, 0, bitmap1.Width, bitmap1.Height), ImageLockMode.ReadOnly, bitmap1.PixelFormat);
IntPtr start1 = bmd1.Scan0;
int sizeOfByte = Marshal.SizeOf(typeof(byte));
for (int i = 0; i < sizeOfByte * bmd1.Stride * bitmap1.Height; i++)
{
byte b1 = Marshal.ReadByte(start1, i);
//在这里对字节进行操作,也可以用Marshal.WriteByte修改字节内容
}
bitmap1.UnlockBits(bmd1);
(3)自定义函数进行字节数组比较:
public static int MemoryCompare2(byte[] b1, byte[] b2)
{
int result = 0;
if (b1.Length != b2.Length)
result = b1.Length - b2.Length;
else
{
for (int i = 0; i < b1.Length; i++)
{
if (b1[i] != b2[i])
{
result = (int)(b1[i] - b2[i]);
break;
}
}
}
return result;
}
3.各种图片比较方式的效率对比
我写了一小段测试代码对上述四种图片比较方式的效率进行了对比,图片为一个24位色的1024X768像素位图,结果如下表所示:
单位:毫秒
图片比较方式 | 第一次 | 第二次 | 第三次 | 第一次 | 第二次 | 第三次 | 平均用时 | 备注 |
Bitmap.GetPixel | 4466 | 4296 | 4878 | 4416 | 4530 | 4584 | 4528.3 | 每次比较一个像素,内存占用低,耗时最长 |
memcmp | 31 | 30 | 40 | 70 | 41 | 31 | 40.5 | 一次比较所有图片内存数据,内存占用高,耗时最短; 如果分块对比图片内存数据,可以减少内存占用,但是会增加处理时间 |
Marshal.ReadByte | 2103 | 1943 | 2103 | 2043 | 2068 | 2083 | 2057.2 | 每次比较一个字节,内存占用最低,耗时较长 |
自定义字节数组比较 | 60 | 60 | 70 | 110 | 95 | 67 | 77 | 一次比较所有图片内存数据,内存占用高,耗时较短; 如果分块对比图片内存数据,可以减少内存占用,但是会增加处理时间 |
4.源代码下载
https://files.cnblogs.com/xrwang/ImageCompare/ImageCompare.rar