65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 GDI+ 比较图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (28投票s)

Jan 13, 2005

3分钟阅读

viewsIcon

359261

downloadIcon

6397

一篇关于通过计算和比较图像的哈希值来比较两张图像的文章。

引言

.NET 提供了一些很棒的方法,可以使用托管 GDI+ 方法来处理图像和位图。 但是,当我想要比较两张图像以查看它们是否相同时,即使使用 GDI+,我仍然感到有些困惑。 我正在尝试对我们的图表组件 SimpleChart 运行一些自动化测试,并且我需要知道生成的图表是否与测试规范中的图表相同。 为此,我需要将测试中 SimpleChart 生成的每个图像与已知良好的参考图像进行比较。 如果两者完全相同,则测试通过。

Comparing Images

首次尝试

比较两张图像以查看它们是否相同的第一步是检查每张图像的大小。 如果它们不匹配,那么我们几乎立即知道图像不相同。 一旦完成快速测试,我们需要查看实际图像内容以查看它是否匹配。 最初,我决定使用 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 秒即可完成。

哈希通常用作安全工具,以查看密码等凭据是否匹配。 使用相同的哈希方法,我们还可以快速比较两张图像以查看它们是否也相同。

© . All rights reserved.