Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# faster way to compare pixels between two images and only write out the differences

Tags:

c#

image

Just doing a pixel-by-pixel comparison of two images that are very similar (one is an edited version of another) and writing out the differences to a new file.

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 and comp are the two images to compare and dest is just a new image. It takes a fairly long time.

What's a faster way to do this?
Maybe it's not necessary to actually get the pixel in order to compare it?

like image 655
MxLDevs Avatar asked Dec 26 '22 20:12

MxLDevs


2 Answers

To compare the pixels you need to read them. However, GetPixel() is a very slow method of doing this and not recommended unless you are just checking a very small amount of data.

To get better performance the best way is to go with unsafe-code and use pointers instead. There are lots of samples of this on the internet, below is one that I found that explains the problems a bit and offers two different solutions to this.

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

Be sure to check part two as well where he has some benchmarks and links to the complete source.

like image 111
Karl-Johan Sjögren Avatar answered Jan 24 '23 05:01

Karl-Johan Sjögren


This code is similar to what I use. Absolutely, in this case, unsafe code is the only way to go. Marshalling a bitmap full of pixels around memory isn't necessary, and you'd have to do that twice for the comparison!

However, comparing pixels should be seen as comparing two numbers. As a pixel is 3 bytes for each of the Red, Green and Blue components of the colour, and of course the Alpha - which is often ignored - you might want to compare values instead as UInt32's. This would have the benefit of comparing one number (one pixel) with another, and if they're the same, then you move on.

For this to work, you don't want a byte* but a UInt32*. Secondly, the code above doesn't consider Stride that I can see, which is where each horizontal line of an image may actually use a different number of bytes than the sum of the pixels themselves. As you're likely dealing with 24bits (rgb) and an alpha (a) that means the pixels should already line up, and hence, the above code should work. Yet there's no guarantee it would 100% of the time.

It might sound like I'm being really picky, but I'm guessing performance means something to you - and coming from a gaming background, it does to me too.

(taken from above link to part 1, thanks to Karl-Johan Sjögren)

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

When you hit this line, you really need to be careful - the PixelFormat you're locking with should match one of your source image's PixelFormat. Otherwise things might not behave quite as you expect.

from the LockBits, you get the BitmapData, which you need to dig into.

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

this is the line I'm referring to - I'd make this a UInt32* so you're not reading one byte per instruction, but 4 in one go, as a UInt32.

I'd access this memory as an array - and I calculate the offset into the one-dimensional array by recogising the stride is the equivalent of one Y pixel, and so multiplying the Stride by the Y. However, you would fall into the same trap I have so many times at this point!

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.

So reading pixels could be done in this way :

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

Lastly then, don't forget to unlock your bitmaps when you're done. An easy one to forget. Also, if you're looking to save the changed pixels to another bitmap, you'll want to write the pixels in the same way. I think my example using a pointer as an array to read a pixel should be obvious how to do the same to write pixels then.

One other thought - I hope you're not trying to compare Jpeg or other lossy compressed images - as the changes could be very unpredictable, and not quite what you think. Instead, you need to use loss-less image formats such as BMP and PNG.

Hope this helps someone.

like image 41
Simon Miller Avatar answered Jan 24 '23 03:01

Simon Miller