如何找到两个图像之间的差异?

时间:2014-10-06 20:11:21

标签: c#

我正在开发一个屏幕共享应用程序。在这个项目中,我需要通过互联网传输图像。显然,我不能每隔几秒就通过互联网发送一张新照片,这将非常慢。 我想将服务器屏幕的一个图像发送到客户端,然后,而不是发送新图片,而只发送自上一个图像(客户端已经拥有)以来已经更改的像素。

我写了这段代码:

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;
}

然而,它的工作方式太慢了。

我正在寻找一种速度更快的算法。

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

4 个答案:

答案 0 :(得分:3)

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

我缩小了800MB 1o 12k的屏幕截图 - 但是时钟指针上只有一个非常小的变化;-)如果你的图像在很多像素上有所不同,那么压缩就不会那么壮观......但我相信它会b足以传输,我怀疑像素的基础上的任何东西都会与png或jpg文件格式的压缩程序进行比较..(你希望不传输bmps!)

例程使用LockBits并且非常快。

bool参数决定是创建差异位图还是恢复更改的位图。

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

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

答案 1 :(得分:0)

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

  1. (Bitmap)_new).GetPixel(i,j)被调用两次,使用临时值来存储它可能会好一些。

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

  3. 像:

    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;
    }
    

答案 2 :(得分:0)

我不确定您要对此代码执行什么操作,因为您没有保存已更改的索引...

您可能希望为List使用其他构造函数,因为List()haves a default capacity of 0

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

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

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

答案 3 :(得分:0)

这可能会或可能不会完美,但是这里也是如此。如果您认为合适,可以通过添加AsParallel来对此进行并行化。

我也在这里复制并粘贴了几行,如果我有任何拼写错误或不匹配的变量,请随时告诉我或编辑。但这是它的要点。这应该是非常快的,在合理范围内。

基本上,由于这可能有点难以理解,因此想法是锁定&#34;比特,然后使用该指针将它们复制到byte[]。这有效地复制了所有RGB(A)值,然后您可以轻松访问这些值。当你阅读时,这比GetPixel要快一倍,超过一两个像素,因为需要一点时间来抓取像素,但它只是对内存的简单读取。

一旦我将它们放入byte[] s,就可以轻松地比较每个像素坐标的那些。{p>我选择使用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设置为零,可能)任何尚未改变的东西。您也可以从像素解锁中受益。同样,有可能教程告诉你如何做到这一点。但其中大部分可能保持不变。