Reputation: 2062
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
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.
ciPixelMismatch
, but without throwing an exception.If we consider only the first case, then what you really want is a function to compare two Color
s 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 Color
s 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