我正在尝试编写一个函数来确定两个相等大小的位图是否相同。我现在的功能只是在每个位图中一次比较一个像素,在第一个不相等的像素处返回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
答案 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)
如果您的图形卡上已有这些位图,那么您可以使用CUDA或OpenCL等语言在显卡上进行此类检查并行化。
我会用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);