使用 GDI+ 比较图像






4.73/5 (28投票s)
Jan 13, 2005
3分钟阅读

359261

6397
一篇关于通过计算和比较图像的哈希值来比较两张图像的文章。
引言
.NET 提供了一些很棒的方法,可以使用托管 GDI+ 方法来处理图像和位图。 但是,当我想要比较两张图像以查看它们是否相同时,即使使用 GDI+,我仍然感到有些困惑。 我正在尝试对我们的图表组件 SimpleChart 运行一些自动化测试,并且我需要知道生成的图表是否与测试规范中的图表相同。 为此,我需要将测试中 SimpleChart 生成的每个图像与已知良好的参考图像进行比较。 如果两者完全相同,则测试通过。
首次尝试
比较两张图像以查看它们是否相同的第一步是检查每张图像的大小。 如果它们不匹配,那么我们几乎立即知道图像不相同。 一旦完成快速测试,我们需要查看实际图像内容以查看它是否匹配。 最初,我决定使用 GDI+ Bitmap
类的 GetPixel
方法将第一张图像中的每个像素与第二张图像中的相应像素进行比较。 如果在任何时候,两个像素不匹配,那么我们可以安全地说图像是不同的。 但是,如果我们完成了比较测试而没有任何不匹配,那么我们可以得出结论,这两张图像确实相同。
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;
}
这种方法效果很好,但有一个主要的缺点,即速度,或者更确切地说,是缺乏速度。 使用此方法比较两张 2000 x 1500 像素的图像花费了超过 17 秒! 有超过 200 张图像需要比较,这意味着我的测试将需要将近一个小时才能完成,而且我不准备等待那么长时间。
闪电般的哈希
我需要的是一种更快的比较图像的方法,以便测试能够及时完成。 我决定不使用 GetPixel
比较每个图像中的单个像素,而是更快地比较每个图像的“哈希”以查看它们是否相同。 众所周知,哈希是代表大量数据的固定大小的唯一值,在本例中是我们的图像数据。 只有当相应的图像也匹配时,两个图像的哈希值才应该匹配。 图像的微小变化会导致哈希值产生大的不可预测的变化。
.NET 在 System.Security.Cryptography
命名空间中提供了许多不同的哈希算法,例如 SHA1 和 MD5,但我决定使用 SHA256Managed
类。 此类的 ComputeHash
方法将数据字节数组作为输入参数,并生成该数据的 256 位哈希值。 通过计算然后比较每个图像的哈希值,我可以快速判断图像是否相同。
现在剩下的唯一问题是如何将存储在 GDI+ Bitmap
对象中的图像数据转换为适合传递给 ComputeHash
方法的形式,即字节数组。 最初,我查看了 Bitmap
类的 LockBits
方法,该方法允许我访问单个像素字节,但这将意味着进入非托管代码的领域,而这正是我真正不想访问的地方。 相反,GDI+ 慷慨地提供了一个 ImageConvertor
类,允许我们将 Image
(或 Bitmap
)对象从一种数据类型转换为另一种数据类型,例如字节数组。
检查图像是否相同的最后一步是比较两个哈希值(也存储在字节数组中)以查看它们是否匹配。 这是最终代码
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Security.Cryptography;
namespace Imagio
{
public class ComparingImages
{
public enum CompareResult
{
ciCompareOk,
ciPixelMismatch,
ciSizeMismatch
};
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
{
//Convert each image to a byte array
System.Drawing.ImageConverter ic =
new System.Drawing.ImageConverter();
byte[] btImage1 = new byte[1];
btImage1 = (byte[])ic.ConvertTo(bmp1, btImage1.GetType());
byte[] btImage2 = new byte[1];
btImage2 = (byte[])ic.ConvertTo(bmp2, btImage2.GetType());
//Compute a hash for each image
SHA256Managed shaM = new SHA256Managed();
byte[] hash1 = shaM.ComputeHash(btImage1);
byte[] hash2 = shaM.ComputeHash(btImage2);
//Compare the hash values
for (int i = 0; i < hash1.Length && i < hash2.Length
&& cr == CompareResult.ciCompareOk; i++)
{
if (hash1[i] != hash2[i])
cr = CompareResult.ciPixelMismatch;
}
}
return cr;
}
}
}
结论
在 2000 x 1500 像素的位图上运行这种新的比较方法,结果比较时间为 0.28 秒,这意味着我对 200 张 SimpleChart 图像的自动化测试现在只需 56 秒即可完成。
哈希通常用作安全工具,以查看密码等凭据是否匹配。 使用相同的哈希方法,我们还可以快速比较两张图像以查看它们是否也相同。