4

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

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只是一个新图像。这需要相当长的时间。

有什么更快的方法来做到这一点?
也许没有必要为了比较它而实际获取像素?

4

3 回答 3

5

要比较像素,您需要读取它们。但是,GetPixel()这是一种非常缓慢的方法,不推荐使用,除非您只是检查非常少量的数据。

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

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

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

于 2012-07-26T04:39:06.850 回答
3

这段代码与我使用的类似。绝对,在这种情况下,不安全的代码是唯一的出路。没有必要在内存周围编组一个充满像素的位图,您必须这样做两次才能进行比较!

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

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

听起来我真的很挑剔,但我猜性能对你来说很重要——而且来自游戏背景,对我也很重要。

(摘自上述第 1 部分的链接,感谢 Karl-Johan Sjö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*,因此您不是每条指令读取一个字节,而是一次性读取 4 个字节,作为 UInt32。

我将把这个内存作为一个数组来访问——我通过识别步幅等于一个 Y 像素来计算一维数组的偏移量,因此将步幅乘以 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。

希望这可以帮助某人。

于 2014-06-28T08:38:46.790 回答
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;
    }

}

于 2012-07-26T09:24:55.313 回答