C#更快的方式来比较两个图像之间的像素,只写出差异

只是对两个非常相似的图像(一个是另一个的编辑版本)进行逐像素比较,并将差异写出到新文件中。

for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { pix1 = src.GetPixel(x, y); pix2 = comp.GetPixel(x, y); if (pix1 != pix2) { dest.SetPixel(x, y, pix1); } } } 

srccomp是要比较的两个图像, dest只是一个新图像。 这需要相当长的时间。

有什么更快的方法呢?
也许没有必要实际获取像素以进行比较?

要比较您需要读取它们的像素。 但是, GetPixel()是一种非常慢的方法,除非您只是检查非常少量的数据,否则不建议这样做。

为了获得更好的性能,最好的方法是使用不安全代码并使用指针代替。 互联网上有很多这样的样本,下面是我发现的一个解释问题并提供两种不同的解决方案。

http://davidthomasbernal.com/blog/2008/03/13/c-image-processing-performance-unsafe-vs-safe-code-part-i

一定要检查第二部分,他有一些基准和链接到完整的来源。

你可以看看这个类:它是一个开源代码,提供基于指针来比较像素的快速方法,我找不到链接,所以我发布你的代码:

 using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Imaging; namespace SampleGrabberNET { public unsafe class UnsafeBitmap { Bitmap bitmap; // three elements used for MakeGreyUnsafe int width; BitmapData bitmapData = null; Byte* pBase = null; public UnsafeBitmap(Bitmap bitmap) { this.bitmap = new Bitmap(bitmap); } public UnsafeBitmap(int width, int height) { this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb); } public void Dispose() { bitmap.Dispose(); } public Bitmap Bitmap { get { return(bitmap); } } private Point PixelSize { get { GraphicsUnit unit = GraphicsUnit.Pixel; RectangleF bounds = bitmap.GetBounds(ref unit); return new Point((int) bounds.Width, (int) bounds.Height); } } public void LockBitmap() { GraphicsUnit unit = GraphicsUnit.Pixel; RectangleF boundsF = bitmap.GetBounds(ref unit); Rectangle bounds = new Rectangle((int) boundsF.X, (int) boundsF.Y, (int) boundsF.Width, (int) boundsF.Height); // Figure out the number of bytes in a row // This is rounded up to be a multiple of 4 // bytes, since a scan line in an image must always be a multiple of 4 bytes // in length. width = (int) boundsF.Width * sizeof(PixelData); if (width % 4 != 0) { width = 4 * (width / 4 + 1); } bitmapData = bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); pBase = (Byte*) bitmapData.Scan0.ToPointer(); } public PixelData GetPixel(int x, int y) { PixelData returnValue = *PixelAt(x, y); return returnValue; } public void SetPixel(int x, int y, PixelData colour) { PixelData* pixel = PixelAt(x, y); *pixel = colour; } public void UnlockBitmap() { bitmap.UnlockBits(bitmapData); bitmapData = null; pBase = null; } public PixelData* PixelAt(int x, int y) { return (PixelData*)(pBase + y * width + x * sizeof(PixelData)); } } public struct PixelData { public byte blue; public byte green; public byte red; } 

}

这段代码与我使用的类似。 当然,在这种情况下,不安全的代码是唯一的方法。 编组一个位于内存周围的像素位图是没有必要的,你必须做两次比较!

但是,比较像素应视为比较两个数字。 由于像素对于颜色的每个红色,绿色和蓝色组件是3个字节,当然还有Alpha(通常被忽略) – 您可能希望将值比较为UInt32。 这将有利于将一个数字(一个像素)与另一个数字进行比较,如果它们相同,那么您继续前进。

为此,您不需要字节*而是UInt32 *。 其次,上面的代码没有考虑我能看到的Stride,这是图像的每个水平线实际上可能使用与像素本身的总和不同的字节数。 因为您可能处理24位(rgb)和alpha(a)意味着像素应该已经排成一行,因此上述代码应该可以工作。 然而,并不能保证它会100%的时间。

这可能听起来像我真的很挑剔,但我猜你的表现对你来说意味着什么 – 来自游戏背景,它也对我有用。

(取自上面链接到第1部分,感谢Karl-JohanSjögren)

 BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat); 

当你点击这一行时,你真的需要小心 – 你锁定的PixelFormat应该匹配你的一个源图像的PixelFormat。 否则事情可能不像你期望的那样。

从LockBits中,你得到了BitmapData,你需要深入研究。

 byte* scan0 = (byte*)bData.Scan0.ToPointer(); 

这就是我所指的那一行 – 我将它变成了一个UInt32 *所以你不是每条指令读一个字节,而是四合一,作为一个UInt32。

我将这个存储器作为一个数组访问 – 我通过识别步幅来计算一维数组中的偏移量相当于一个Y像素,因此将Stride乘以Y.但是,你会陷入同样的陷阱我在这一点上有这么多次!

 int stride = bData.Stride / 4; // the width is expressed in bytes, yet we need it as UInt32's. As there's 4 bytes per UInt32, this makes sense of the divide by 4. 

因此,读取像素可以通过这种方式完成:

 int x = 123; int y = 321; UInt32 pixelColour = scan0[(y * stride) + x]; 

最后,当你完成时不要忘记解锁你的位图。 一个容易忘记的人。 此外,如果您要将更改后的像素保存到另一个位图,则需要以相同的方式写入像素。 我认为我的例子使用指针作为数组来读取像素应该是显而易见的,如何做同样的事情来写像素。

另一个想法 – 我希望你不要试图比较Jpeg或其他有损压缩图像 – 因为这些变化可能非常难以预测,并且不是你想的那么多。 相反,您需要使用无损图像格式,如BMP和PNG。

希望这有助于某人。