只是对两张非常相似的图像进行逐像素比较(一张是另一张图像的编辑版本),然后将差异写出到新文件中。
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);
}
}
}
src
和comp
是要比较的两张图片,dest
只是一张新图片。
这需要相当长的时间。
更快的方法是什么?
也许没有必要实际获取像素以进行比较?
答案 0 :(得分:4)
比较您需要读取它们的像素。但是,GetPixel()
是一种非常慢的方法,除非您只是检查非常少量的数据,否则不建议这样做。
为了获得更好的性能,最好的方法是使用不安全代码并使用指针代替。在互联网上有很多这样的样本,下面是我发现的一个解释问题并提供两种不同的解决方案。
请务必检查第二部分,其中包含一些基准和指向完整来源的链接。
答案 1 :(得分:3)
此代码与我使用的代码类似。 当然,在这种情况下,不安全的代码是唯一的方法。 编组一个位于内存周围的像素位图是没有必要的,你必须做两次比较!
但是,比较像素应视为比较两个数字。 对于颜色的每个红色,绿色和蓝色组件,像素是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。
希望这有助于某人。
答案 2 :(得分:2)
你可以看看这个类:它是一个开源代码,提供基于指针来比较像素的快速方法,我找不到链接,所以我给你发了代码:
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;
}
}