xDrago
xDrago

Reputation: 2062

C# Comparing 2 Bitmaps with tolerance?

I want to compare two Bitmaps, but with tolerance. My current code gives says "ciCompareOk" only if both images are exact the same.

Both have the same size and the same shape in it, sometimes its darker or lighter.
How could this look with tolerance?

public static CompareResult Compare(Bitmap bmp1, Bitmap bmp2)
{
    CompareResult cr = CompareResult.ciCompareOk;

    //Test to see if we have the same size of image
    if (bmp1.Size != bmp2.Size)
    {
        cr = CompareResult.ciSizeMismatch;
    }
    else
    {
        //Sizes are the same so start comparing pixels
        for (int x = 0; x < bmp1.Width
             && cr == CompareResult.ciCompareOk; x++)
        {
            for (int y = 0; y < bmp1.Height
                         && cr == CompareResult.ciCompareOk; y++)
            {
                if (bmp1.GetPixel(x, y) != bmp2.GetPixel(x, y))
                    cr = CompareResult.ciPixelMismatch;
            }
        }
    }

    return cr;
}

Upvotes: 1

Views: 5935

Answers (1)

Trevor
Trevor

Reputation: 1271

There are a few possible test cases for how "different" images can be, and the approach that you need to take to "match" them become progressively harder.

  • The simple case is where you expect the images to be the same size and contain the same features, except for pixels being slightly lighter or darker as you state. For example, if you were to save a bitmap image once as 24-bit, then again as 8-bit (without dithering). The two images would have the same features in the same place, but slightly different colours for each pixel.
  • Another possibility is where you take a bitmap image, and crop 1 line of pixels of the left to create the first image, and crop a line off the right for the second image.
  • A third possibility is where you save a bitmap image, then double the image size in both directions to create the second (so one pixel in the source becomes four pixels in the output). I accept that you have size checking code to detect this case already.
  • A fourth possibility (more of a test case) is to have a large image of just white, and a large image of just black. We expect this to return ciPixelMismatch, but without throwing an exception.

If we consider only the first case, then what you really want is a function to compare two Colors and return a value for how different they are. A simple way to compare two colours is to calculate the Pythagorean distance between the Red, Green and Blue components, such as

static int CompareColours(Color x, Color y)
{
    return (int)(Math.Pow((int)x.R - y.R, 2) + Math.Pow((int)x.B - y.B, 2) + Math.Pow((int)x.G - y.G, 2));
}

this will return an number between 0 (when the Colors are identical) and 198608 (between Black and White, which is Math.Pow(256, 2) * 3).

With this, you can apply the function to each pair of pixels (one from each image) and accumulate the error. Average this error across the number of pixels to determine the average pixel error across the whole image, then compare this to a threshold to determine if they are "the same":

const decimal errorThreshold = 0.0001D
decimal totalError = 0;
for (int x = 0; x < bmp1.Width; x++)
{
    for (int y = 0; y < bmp1.Height; y++)
    {
        totalError += CompareColours(bmp1.GetPixel(x, y), bmp2.GetPixel(x, y)) / 198608D;
    }
}
decimal averageError = totalError / (bmp1.Width * bmp1.Height);
if ( averageError > errorThreshold ) cr = CompareResult.ciPixelMismatch;

(I divide by 198608D to avoid the possibility of an Integer Overflow while adding. averageError is then a value between 0D for identical and 1D for completely different.)

I'd also recommend you look at some of the other questions here on StackOverflow. While this pixel-colour-matching works for the simplest case, it won't work for the others. The approaches given in answers to other questions will be useful if you need something more complex:

Hope this helps

Upvotes: 3

Related Questions