在C#中计算图像差异

6
我该如何生成一个包含两个其他图像像素差异的 System.Drawing.Image?类似于 GitHub 的效果,但用 C# 编写。 GitHub 使用的算法 是用 JavaScript 实现的。有一个名为 context-blender 的项目可以复制 Photoshop 混合模式。
你知道是否有被翻译成 C# 或具有相同质量水平的类似算法吗?我还需要处理带有 alpha 通道的 透明图像
2个回答

15

这是一个快速且简单的实现:

void Main()
{
    var a = (Bitmap)Image.FromFile("image1.png");
    var b = (Bitmap)Image.FromFile("image2.png");
    var diff = PixelDiff(a, b);
}

unsafe Bitmap PixelDiff(Bitmap a, Bitmap b)
{
    Bitmap output = new Bitmap(a.Width, a.Height, PixelFormat.Format32bppArgb);
    Rectangle rect = new Rectangle(Point.Empty, a.Size);
    using (var aData = a.LockBitsDisposable(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
    using (var bData = b.LockBitsDisposable(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
    using (var outputData = output.LockBitsDisposable(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb))
    {
        byte* aPtr = (byte*)aData.Scan0;
        byte* bPtr = (byte*)bData.Scan0;
        byte* outputPtr = (byte*)outputData.Scan0;
        int len = aData.Stride * aData.Height;
        for (int i = 0; i < len; i++)
        {
            // For alpha use the average of both images (otherwise pixels with the same alpha won't be visible)
            if ((i + 1) % 4 == 0)
                *outputPtr = (byte)((*aPtr  + *bPtr) / 2);
            else
                *outputPtr = (byte)~(*aPtr ^ *bPtr);

            outputPtr++;
            aPtr++;
            bPtr++;
        }
    }
    return output;
}

static class Extensions
{
    public static DisposableImageData LockBitsDisposable(this Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
    {
        return new DisposableImageData(bitmap, rect, flags, format);
    }

    public class DisposableImageData : IDisposable
    {
        private readonly Bitmap _bitmap;
        private readonly BitmapData _data;

        internal DisposableImageData(Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
        {
            bitmap.CheckArgumentNull("bitmap");
            _bitmap = bitmap;
            _data = bitmap.LockBits(rect, flags, format);
        }

        public void Dispose()
        {
            _bitmap.UnlockBits(_data);
        }

        public IntPtr Scan0
        {
            get { return _data.Scan0; }
        }

        public int Stride
        {
            get { return _data.Stride;}
        }

        public int Width
        {
            get { return _data.Width;}
        }

        public int Height
        {
            get { return _data.Height;}
        }

        public PixelFormat PixelFormat
        {
            get { return _data.PixelFormat;}
        }

        public int Reserved
        {
            get { return _data.Reserved;}
        }
    }   
}

注:

  • 这个实现假定两个图像具有相同的大小,但实际情况可能不是这样的... 当然,考虑不同的大小是可能的,只是会更加困难。
  • LockBitsDisposable 方法仅仅是一种便捷方式,如果你喜欢可以使用标准的 LockBits 方法(但别忘了在完成后解锁位)。

@ThomasLevesque 嘿,我知道我很晚了,但我使用了你的解决方案,现在出现了一个错误:*outputPtr = (byte)~(*aPtr ^ *bPtr);Attempt to read or write Protected Memory This is often an indicating that other memory is corrupt。你能帮我看看吗? - user3548681
@ThomasLevesque,他们使用 GDI 捕获了 2 张屏幕截图,并希望获取它们之间的差异。 - user3548681
@itapi,不确定发生了什么......这段代码不应该能访问分配给图像之外的内存。当异常发生时,“i”的值是多少,图像的尺寸是多少? - Thomas Levesque
@itapi,糟糕,我的代码没有处理那种情况。但是len也应该是负数,所以它甚至不应该进入循环,因此您不应遇到此错误...无论如何,请尝试使用Math.Abs(aData.Stride)而不是aData.Stride - Thomas Levesque
@ThomasLevesque 谢谢兄弟,但现在我又遇到了另一个问题:*outputPtr = *outputPtr = (byte)*bPtr; 它抛出了一个错误 Attempted to read or write protected memory. This is often an indication that other memory is corrupt.... 我正在这里苦思冥想,哈哈。 - user3548681
显示剩余8条评论

2

我已经搜索了上下近100个,bryncook是唯一一个可以与锁定位直接工作的答案。+1 - bh_earth0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接