我可以比较两个相等大小的位图以确定它们是否相同的最快方法是什么?

时间:2010-01-08 22:28:03

标签: c# comparison bitmap

我正在尝试编写一个函数来确定两个相等大小的位图是否相同。我现在的功能只是在每个位图中一次比较一个像素,在第一个不相等的像素处返回false。

虽然这很有效,并且适用于小位图,但在生产中我会在紧凑的循环和更大的图像中使用它,所以我需要更好的方法。有没有人有任何建议?

我正在使用的语言是C# - 是的,我已经在使用.LockBits方法了。 =)

编辑:我已经编写了一些建议的实现,这里是基准测试。设置:两个相同(最坏情况)的位图,大小为100x100,每个迭代次数为10,000次。结果如下:

CompareByInts (Marc Gravell) :   1107ms
CompareByMD5  (Skilldrick)   :   4222ms
CompareByMask (GrayWizardX)  :    949ms

在CompareByInts和CompareByMask中,我使用指针直接访问内存;在MD5方法中我使用Marshal.Copy来检索字节数组并将其作为参数传递给MD5.ComputeHash。 CompareByMask只是稍微快一点,但考虑到上下文,我认为任何改进都是有用的。

谢谢大家。 =)

编辑2 :忘记启用优化功能 - 这样做可以让GrayWizardX的答案更加强大:

CompareByInts   (Marc Gravell) :    944ms
CompareByMD5    (Skilldrick)   :   4275ms
CompareByMask   (GrayWizardX)  :    630ms
CompareByMemCmp (Erik)         :    105ms

有趣的是,MD5方法根本没有改善。

编辑3 :发布我的答案(MemCmp),将其他方法从水中吹走。 o.O

9 个答案:

答案 0 :(得分:32)

编辑8-31-12:下面的Joey's评论,请注意您比较的位图格式。它们可能在步幅上包含填充,使得位图不相等,尽管它们在像素方面是等效的。有关详细信息,请参阅this question


阅读this answer有关比较字节数组的问题已经产生了一个更快的方法:使用P / Invoke和msvcrt中的memcmp API调用。这是代码:

[DllImport("msvcrt.dll")]
private static extern int memcmp(IntPtr b1, IntPtr b2, long count);

public static bool CompareMemCmp(Bitmap b1, Bitmap b2)
{
    if ((b1 == null) != (b2 == null)) return false;
    if (b1.Size != b2.Size) return false;

    var bd1 = b1.LockBits(new Rectangle(new Point(0, 0), b1.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    var bd2 = b2.LockBits(new Rectangle(new Point(0, 0), b2.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    try
    {
        IntPtr bd1scan0 = bd1.Scan0;
        IntPtr bd2scan0 = bd2.Scan0;

        int stride = bd1.Stride;
        int len = stride * b1.Height;

        return memcmp(bd1scan0, bd2scan0, len) == 0;
    }
    finally
    {
        b1.UnlockBits(bd1);
        b2.UnlockBits(bd2);
    }
}

答案 1 :(得分:8)

好吧,你正在使用.LockBits,所以大概你使用的是不安全的代码。不要将每个行原点(Scan0 + y * Stride)视为byte*,而应将其视为int*; int算术很快,你只需做1/4的工作。而对于ARGB中的图像,您可能仍在以像素为单位进行讨论,使数学变得简单。

答案 2 :(得分:8)

如果您正在尝试确定它们是否100%相等,则可以反转一个并将其添加到另一个,如果它们相同则为零。使用不安全的代码扩展它,一次取64位作为一个长的数字,并以这种方式进行数学计算,任何差异都可能导致立即失败。

如果图像不是100%相同(比较png到jpeg),或者如果你不是在寻找100%的匹配,那么你还有更多工作要做。

祝你好运。

答案 3 :(得分:6)

你能拿出每个哈希并进行比较吗?这有点概率,但实际上并非如此。

感谢Ram,这是这项技术的sample implementation

答案 4 :(得分:3)

如果最初的问题只是在两个位图中找到确切的重复项,那么只需进行一次比特级别的比较即可。我不知道C#但是在C中我会使用以下函数:

int areEqual (long size, long *a, long *b)
{
    long start = size / 2;
    long i;
    for (i = start; i != size; i++) { if (a[i] != b[i]) return 0 }
    for (i = 0; i != start; i++) { if (a[i] != b[i]) return 0 }
    return 1;
}

我会从中间开始看,因为我怀疑在图像中间附近找到不等比特的可能性要大于开头;当然,这实际上取决于你正在推导的图像,选择一个随机的地方开始可能是最好的。

如果您试图在数百张图像中找到完全相同的副本,则无需比较所有图像。首先计算每个图像的MD5哈希并将其放在一对列表中(md5Hash,imageId);然后按m5Hash对列表进行排序。接下来,只对具有相同md5Hash的图像进行成对比较。

答案 5 :(得分:3)

如果您的图形卡上已有这些位图,那么您可以使用CUDAOpenCL等语言在显卡上进行此类检查并行化。

我会用CUDA解释,因为那是我所知道的。基本上,CUDA允许您编写通用代码,以在您的图形卡的每个节点上并行运行。您可以访问共享内存中的位图。每次调用函数时,还会在并行运行集中给出一个索引。因此,对于这样的问题,您只需为位图的某个子集运行上述比较函数之一 - 使用并行化来覆盖整个位图。然后,如果比较失败,只需将1写入某个内存位置(如果成功则不写入任何内容)。

如果您的显卡上还没有位图,这可能不是一种可行的方法,因为在卡上加载两个位图的成本很容易超过并行化带来的节省。< / p>

这是一些(非常糟糕的)示例代码(自编写CUDA以来已经有一段时间了)。有更好的方法来访问已经作为纹理加载的位图,但我没有在这里打扰。

// kernel to run on GPU, once per thread
__global__ void compare_bitmaps(long const * const A, long const * const B, char * const retValue, size_t const len)
{
 // divide the work equally among the threads (each thread is in a block, each block is in a grid)
 size_t const threads_per_block = blockDim.x * blockDim.y * blockDim.z;
 size_t const len_to_compare = len / (gridDim.x * gridDim.y * gridDim.z * threads_per_block);
# define offset3(idx3,dim3)  (idx3.x + dim3.x * (idx3.y + dim3.y * idx3.z))
 size_t const start_offset = len_to_compare * (offset3(threadIdx,blockDim) + threads_per_block * offset3(blockIdx,gridDim));
 size_t const stop_offset = start_offset + len_to_compare;
# undef offset3

 size_t i;
 for (i = start_offset; i < stop_offset; i++)
 {
  if (A[i] != B[i]) 
  {
   *retValue = 1;
   break;
  }
 }
 return;
}

答案 6 :(得分:0)

如果您可以使用您的语言实现Duff's Device之类的内容,那么这可能会让您在简单的循环中获得显着的速度提升。通常它用于复制数据,但没有理由不能用于比较数据。

或者,就此而言,您可能只想使用一些等效的memcmp()。

答案 7 :(得分:0)

您可以尝试将它们添加到数据库“blob”,然后使用数据库引擎来比较它们的二进制文件。这只会给你一个是或否答案二进制数据是否相同。制作相同图形但具有不同二进制的2幅图像非常容易。

您还可以选择一些随机像素并进行比较,然后如果它们相同则继续更多,直到您检查完所有像素为止。这只会返回一个更快的负面匹配,但仍然需要花费很长时间才能找到100%的正面匹配

答案 8 :(得分:-1)

基于比较哈希的方法而不是比较每个像素,这就是我使用的方法:

public static class Utils
{
    public static byte[] ShaHash(this Image image)
    {
        var bytes = new byte[1];
        bytes = (byte[])(new ImageConverter()).ConvertTo(image, bytes.GetType());

        return (new SHA256Managed()).ComputeHash(bytes);
    }

    public static bool AreEqual(Image imageA, Image imageB)
    {
        if (imageA.Width != imageB.Width) return false;
        if (imageA.Height != imageB.Height) return false;

        var hashA = imageA.ShaHash();
        var hashB = imageB.ShaHash();

        return !hashA
            .Where((nextByte, index) => nextByte != hashB[index])
            .Any();
    }
]

用法很简单:

bool isMatch = Utils.AreEqual(bitmapOne, bitmapTwo);