2014-10-06 83 views
0

我正在开发一个屏幕共享应用程序。在这个项目中,我需要通过互联网传输图像。显然,我无法每隔几秒在互联网上发送一张新照片,这会非常缓慢。 我想发送服务器屏幕的一个图像到客户端,然后,而不是发送一个新的图片,只发送自最后一个图像(客户端已有的图像)以来已更改的像素。如何找到两个图像之间的区别?

我写了这个代码:

private List<Color> CompareBitmaps(Image old, Image _new) 
{ 
    List<Color> returnList = new List<Color>(); 

    for(int i = 0; i < old.Width; i++) 
     for (int j = 0; j < old.Height; j++) 
     { 
      if (((Bitmap)old).GetPixel(i, j) != ((Bitmap)_new).GetPixel(i, j)) 
      { 
       returnList.Add(((Bitmap)_new).GetPixel(i, j)); 
      } 
     } 

return returnList; 
} 

但是,它的工作方式过于缓慢。

我正在寻找更快的算法,其中一个具有更好的复杂性。

注:我不想要一个建立的库来做到这一点。我需要一个算法。

+0

如何获得可接受尺寸的中心矩形并比较该区域内的随机像素? – PeteGO 2014-10-06 20:15:00

+1

其中一个想法是先检查每个[10th,5th,2nd]等像素,如果确定了一个更改,则会优化您检查的区域。 – user1274820 2014-10-06 20:15:08

+2

'GetPixel'也是一个超级慢的方法。尝试一个'UnlockBits'方法。如果你看起来有很多的教程,它会快得多。 – 2014-10-06 20:17:39

回答

0

您需要返回所有更改的像素,因此复杂度必须为m * n。

  1. (位图)_新).GetPixel(i,j)被调用两次,使用临时值来存储它可能会好一点。

  2. 像素应该有几个值对吗?你可以尝试创建一个名为comprareTwoPixel(颜色A,颜色B)的函数吗?并逐一比较所有值,如果其中一个为假,则不需要比较其余的值,只返回false。 (不知道这是否会让它更快与否虽然)。

像:

bool comprareTwoPixel(color A, color B) 
{ 
    if(A.a!=B.b) 
     return false; 
    if(A.b!=B.b) 
     return false; 
    if(A.c!=B.c) 
     return false; 

    return true; 
} 
0

我不知道你正在尝试使用此代码做什么,因为你AREN “T保存已更改索引...

您可能需要使用其他的构造函数为您的列表,因为列表()haves a default capacity of 0

这意味着你将重新分配L的内部缓冲器如果很多像素发生了变化,可能会很多次

也许记录已更改的像素的平均数量并将列表的初始容量设置为该数字可以加速您的代码。至少,你可以说,例如, 10%的像素每帧改变:

List<Color> returnList = new List<Color>((int)(0.10 * numberOfPixel)); 
0

这可能会或可能不会完美工作,但在这里。如果您看到如此合适,可以通过添加AsParallel来将其并行化。

我也在这里复制并粘贴了几行,所以请随时让我知道或编辑,如果我有任何错别字或不匹配的变量。但这是它的要点。这应该是相当快的,在合理的范围内。基本上,由于这可能有点难以理解,所以想法是“锁定”这些位,然后使用该指针将它们复制到byte[]。这有效地复制出所有RGB(A)值,然后您可以很容易地访问它们。当你阅读的时候,这个速度比GetPixel快了很多,因为你需要一点点时间才能获取像素,但这只是简单的读取内存。

一旦我将它们放入byte[] s中,只需比较每个像素坐标就足够简单了。我选择使用LINQ,以便在需要时很容易并行化,但是您可能会或可能不会选择实际实现它。我不确定你是否需要。

我在这里做了一些假设,我认为这是公平的,因为听起来像您的实现具有来自单一来源的所有图像。也就是说,我认为这些图像的大小和格式是相同的。因此,如果事实并非如此,那么您需要在此处添加一些额外的代码,但这仍然非常简单。

private byte[] UnlockBits(Bitmap bmp, out int stride) 
{ 
    BitmapData bmpData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat); 

    IntPtr ptr = bmpData.Scan0; 

    stride = bmpData.Stride; 

    int bytes = Math.Abs(bmpData.Stride) * bmp.Height; 

    byte[] ret = new byte[bytes]; 

    System.Runtime.InteropServices.Marshal.Copy(ptr, ret, 0, bytes); 

    bmp.UnlockBits(bmpData); 

    return ret; 
} 

private bool AreArraysEqual(byte[] a, byte[] b, int offset, int length) 
{ 
    for (int v = 0; v < length; v++) 
    { 
     int c = v + offset; 

     if (a[c] != b[c]) 
     { 
      return false; 
     } 
    } 
    return true; 
} 

private IEnumerable<KeyValuePair<Point, Tuple<Color, Color>>> GetDifferences(Bitmap a, Bitmap b) 
{ 
    if (a.PixelFormat != b.PixelFormat) 
     throw new ArgumentException("Unmatched formats!"); 

    if (a.Size != b.Size) 
     throw new ArgumentException("Unmatched length!"); 

    int stride; 
    byte[] rgbValuesA = UnlockBits(a, out stride); 
    byte[] rgbValuesB = UnlockBits(b, out stride); 

    if (rgbValuesA.Length != rgbValuesB.Length) 
     throw new ArgumentException("Unmatched array lengths (unexpected error)!"); 

    int bytesPerPixel = Image.GetPixelFormatSize(a.PixelFormat)/8; 

    return Enumerable.Range(0, a.Height).SelectMany(y => 
      Enumerable.Range(0, a.Width) 
        .Where(x => !AreArraysEqual(rgbValuesA, 
               rgbValuesB, 
               (y * stride) + (x * bytesPerPixel), 
               bytesPerPixel)) 
        .Select(x => 
          { 
           Point pt = new Point(x, y); 

           int pixelIndex = (y * stride) + (x * bytesPerPixel); 

           Color colorA = ReadPixel(rgbValuesA, pixelIndex, bytesPerPixel); 
           Color colorB = ReadPixel(rgbValuesB, pixelIndex, bytesPerPixel); 

           return new KeyValuePair<Point, Tuple<Color, Color>>(pt, colorA, colorB); 
          } 
} 

private Color ReadPixel(byte[] bytes, int offset, int bytesPerPixel) 
{ 
    int argb = BitConverter.ToInt32(pixelBytes, offset); 

    if (bytesPerPixel == 3) // no alpha 
     argb |= (255 << 24); 

    return Color.FromArgb(argb); 
} 

public IEnumerable<KeyValuePair<Point, Color>> GetNewColors(Bitmap _new, Bitmap old) 
{ 
    return GetDifferences(_new, old).Select(c => new KeyValuePair<Point, Color>(c.Key, c.Value.Item1)); 
} 

在真正的实现,你可能要多一点彻底,比我想想字节顺序和像素格式,但这应该工作或多或少作为概念验证,我相信应该处理的多数的实际案例。

而@TaW在评论中说,你也可以尝试消隐(可能将alpha设置为零)任何没有改变的东西。您也可以从像素解锁中受益。再次,有可能教程告诉你如何去做。但其中大部分可能保持不变。

1

此例程找出两个位图之间的差异,并通过将其他位设置为几乎黑色并且非常透明,将它们返回到第一位图中。它还可以通过将结果添加回先前的图像来恢复原始的第二个文件。

我缩小了800MB 1o 12k的屏幕截图 - 但在Clocks手中确实只有很小的变化;-)如果你的图像在很多像素上有所不同,压缩将不会如此壮观......但我相信它对于传输会足够好,并且我怀疑逐个像素的任何内容都会与png或jpg文件格式的压缩例程进行比较。(你不会传输bmp,我希望!)

例程使用LockBits,速度非常快。

布尔参数决定是否创建差异位图或还原已更改的位图。

public static Bitmap Difference(Bitmap bmp0, Bitmap bmp1, bool restore) 
{ 
    int Bpp = 4; // assuming an effective pixelformat of 32bpp 
    var bmpData0 = bmp0.LockBits(
        new Rectangle(0, 0, bmp0.Width, bmp0.Height), 
        ImageLockMode.ReadWrite, bmp0.PixelFormat); 
    var bmpData1 = bmp1.LockBits(
        new Rectangle(0, 0, bmp1.Width, bmp1.Height), 
        ImageLockMode.ReadOnly, bmp1.PixelFormat); 

    int len = bmpData0.Height * bmpData0.Stride; 
    byte[] data0 = new byte[len]; 
    byte[] data1 = new byte[len]; 
    Marshal.Copy(bmpData0.Scan0, data0, 0, len); 
    Marshal.Copy(bmpData1.Scan0, data1, 0, len); 

    for (int i = 0; i < len; i += Bpp) 
    { 
     if (restore) 
     { 
      bool toberestored = (data1[i ] != 2 && data1[i+1] != 3 && 
           data1[i+2] != 7 && data1[i+2] != 42); 
      if (toberestored) 
      { 
       data0[i ] = data1[i]; // Blue 
       data0[i+1] = data1[i+1]; // Green 
       data0[i+2] = data1[i+2]; // Red 
       data0[i+3] = data1[i+3]; // Alpha 
      } 
     } 
     else 
     { 
      bool changed = ((data0[i ] != data1[i ]) || 
          (data0[i+1] != data1[i+1]) || (data0[i+2] != data1[i+2])); 
      data0[i ] = changed ? data1[i ] : (byte)2; // special markers 
      data0[i+1] = changed ? data1[i+1] : (byte)3; // special markers 
      data0[i+2] = changed ? data1[i+2] : (byte)7; // special markers 
      data0[i+3] = changed ? (byte)255 : (byte)42; // special markers 
     } 
    } 

    Marshal.Copy(data0, 0, bmpData0.Scan0, len); 
    bmp0.UnlockBits(bmpData0); 
    bmp1.UnlockBits(bmpData1); 
    return bmp0; 
} 

注: - 我已经选择了一个特殊的颜色来标记那些需要在收件人要恢复的像素。在这里,我选择了alpha=42R=7; G=3; B=2; ..不是100%安全,但差不多;不会有很多像素会被遗漏;也许你没有透明度..?

我追加两个较小的图像,两者的PNG,围绕400KB:

enter image description here enter image description here

这是差分图像(3KB):

enter image description here

恢复的图像是与第二张图像相同。

+0

我已经改进了一些代码并纠正了一个错字。 – TaW 2014-10-08 10:22:20