仰天一笑(Ansonxuyu),专业从事软件定制开发、Web软件开发,网站建设,网络推广,APP开发,微博应用开发,微信应用开发,电子商务开发,物联网开发等技术。
互联网8年风雨,愿在此交朋识友,交流心得,分享技术知识(策划/研发/运营/推广/合作)!QQ:943530498


仰天一笑

昨日不悔,今日勿失,明日莫忧! —徐羽

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

  .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 

转自:http://www.cnblogs.com/xrwang/articles/1276826.html

posted on 2012-05-22 13:22  仰天一笑  阅读(525)  评论(0编辑  收藏  举报