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

C# 比较两个大小相等的位图以确定它们是否相同的最快方法是什么?,c#,comparison,bitmap,C#,Comparison,Bitmap,我正在尝试编写一个函数来确定两个大小相等的位图是否相同。我现在使用的函数只是在每个位图中一次比较一个像素,在第一个不相等的像素处返回false 虽然这是可行的,而且对小位图也很有效,但在生产中,我将在一个紧密的循环中以及在更大的图像上使用它,所以我需要一种更好的方法。有人有什么建议吗 顺便说一下,我使用的语言是C语言——是的,我已经在使用.LockBits方法了 编辑:我已经对给出的一些建议的实现进行了编码,下面是基准测试。设置:两个相同(最坏情况)的位图,大小为100x100,每个位图有100

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

虽然这是可行的,而且对小位图也很有效,但在生产中,我将在一个紧密的循环中以及在更大的图像上使用它,所以我需要一种更好的方法。有人有什么建议吗

顺便说一下,我使用的语言是C语言——是的,我已经在使用.LockBits方法了

编辑:我已经对给出的一些建议的实现进行了编码,下面是基准测试。设置:两个相同(最坏情况)的位图,大小为100x100,每个位图有10000次迭代。结果如下:

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

好吧,您使用的是
.LockBits
,所以可能您使用的是不安全的代码。而不是将每行的原点(<代码> SCA0+Y*Struts )作为<代码>字节*/COD>处理,考虑将其视为<代码> int */COD>;code>int算法非常快,你只需要做四分之一的工作。对于ARGB中的图像,您可能仍然以像素为单位,这使得数学计算变得简单。

您可以对每个图像进行散列并进行比较吗?这有点概率,但实际上并非如此


多亏了Ram,这里有一个关于这项技术的例子。

如果你想确定它们是否100%相等,你可以将一个倒过来,如果它们的零点相同,就把它加到另一个上。使用不安全的代码进行扩展,每次占用64位,这样做,任何差异都可能导致立即失败

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


祝你好运。

如果你能在你的语言中实现类似的东西,那可能会在一个简单的循环中给你带来显著的速度提升。通常它用于复制数据,但没有理由不能将其用于比较数据


或者,就这一点而言,您可能只想使用一些与memcmp()等效的工具。

您可以尝试将它们添加到数据库“blob”中,然后使用数据库引擎比较它们的二进制文件。对于二进制数据是否相同,这只能给出是或否的答案。这将是非常容易使两个图像产生相同的图形,但有不同的二进制虽然


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

如果最初的问题只是在两个位图之间找到精确的重复项,那么只需进行位级比较即可。我不知道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的图像进行两两比较。

如果这些位图已经在图形卡上,则可以使用类似或的语言在图形卡上进行并行检查

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

如果您的图形卡上还没有位图,这可能不是一个好办法,因为在图形卡上加载两个位图的成本将很容易抵消并行化带来的节省

下面是一些(非常糟糕的)示例代码(我编写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;
}
//要在GPU上运行的内核,每个线程一次
__全局无效比较位图(长常量*常量A、长常量*常量B、字符*常量retValue、大小常量len)
{
//在线程之间平均分配工作(每个线程在一个块中,每个块在一个网格中)
每个块的大小常数线程=blockDim.x*blockDim.y*blockDim.z;
大小常数len_to_compare=len/(gridDim.x*gridDim.y*gridDim.z*每个块的线程数);
#定义偏移量3(idx3,dim3)(idx3.x+dim3.x*(idx3.y+dim3.y*idx3.z))
大小常数开始偏移=长度到比较*(偏移量3(线程IDX,块DIM)+每个线程块*偏移量3(块IDX,网格DIM));
大小
[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);
    }
}
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);