这篇文章是我解决方案的起点,这里有很多好主意,所以我想分享我的结果。主要见解是,我找到了一种通过利用 phash 的速度来解决基于关键点的图像匹配缓慢的方法。
对于一般解决方案,最好采用多种策略。每种算法都最适合某些类型的图像转换,您可以利用这一点。
在顶部,最快的算法;在底部最慢(虽然更准确)。如果在较快的级别上找到了好的匹配项,您可能会跳过较慢的级别。
- 基于文件哈希(md5、sha1 等)的精确重复项
- 重新缩放图像的感知散列 (phash)
- 基于特征的 (SIFT) 修改图像
我在使用 phash 时取得了很好的效果。对于重新缩放的图像,精度很好。它不适用于(感知)修改的图像(裁剪、旋转、镜像等)。为了处理散列速度,我们必须使用磁盘缓存/数据库来维护干草堆的散列。
phash 的真正好处在于,一旦您构建了哈希数据库(对我来说大约是 1000 张图像/秒),搜索速度会非常非常快,尤其是当您可以将整个哈希数据库保存在内存中时.这是相当实用的,因为散列只有 8 个字节。
例如,如果您有 100 万张图像,则需要一个包含 100 万个 64 位哈希值 (8 MB) 的数组。在某些 CPU 上,这适合 L2/L3 缓存!在实际使用中,我看到 corei7 的比较速度超过 1 Giga-hamm/sec,这只是 CPU 的内存带宽问题。 10 亿张图片数据库在 64 位 CPU(需要 8GB RAM)上是实用的,搜索不会超过 1 秒!
对于修改/裁剪的图像,看起来像 SIFT 这样的变换不变特征/关键点检测器是可行的方法。 SIFT 会产生很好的关键点来检测裁剪/旋转/镜像等。但是与 phash 使用的汉明距离相比,描述符比较非常慢。这是一个主要限制。有很多比较要做,因为有最大 IxJxK 描述符比较查找一张图像(I=num haystack 图像,J=每个 haystack 图像的目标关键点,K=每个针图像的目标关键点)。
为了解决速度问题,我尝试在每个找到的关键点周围使用 phash,使用特征大小/半径来确定子矩形。使这项工作顺利进行的技巧是增大/缩小半径以生成不同的子矩形级别(在针图像上)。通常第一级(未缩放)会匹配,但通常需要更多。我不是 100% 确定为什么会这样,但我可以想象它启用了太小而无法使用 phash 的功能(phash 将图像缩小到 32x32)。
另一个问题是 SIFT 不会最优地分布关键点。如果图像的某个部分有很多边缘,则关键点将聚集在那里,而您将不会在其他区域得到任何边缘。我在 OpenCV 中使用 GridAdaptedFeatureDetector 来改进分布。不确定哪种网格尺寸最好,我使用的是小网格(1x3 或 3x1,具体取决于图像方向)。
您可能希望在特征检测之前将所有干草堆图像(和针)缩放到更小的尺寸(我沿最大尺寸使用 210 像素)。这将减少图像中的噪声(对于计算机视觉算法来说一直是个问题),还将检测器聚焦在更突出的特征上。
对于人物图像,您可以尝试人脸检测并使用它来确定要缩放到的图像大小和网格大小(例如,最大的人脸缩放为 100 像素)。特征检测器考虑了多个尺度级别(使用金字塔),但它会使用多少个级别是有限制的(这当然是可调的)。
当关键点检测器返回的特征数量少于您想要的数量时,它可能工作得最好。例如,如果您要求 400 并获得 300 回来,那很好。如果您每次都返回 400,那么可能不得不忽略一些好的功能。
needle 图像的关键点可以比 haystack 图像少,但仍然可以获得良好的结果。添加更多并不一定会给您带来巨大的收益,例如在 J=400 和 K=40 的情况下,我的命中率约为 92%。在 J=400 和 K=400 的情况下,命中率仅上升到 96%。
我们可以利用汉明函数的极速来解决缩放、旋转、镜像等问题。可以使用多通道技术。在每次迭代中,变换子矩形,重新散列,并再次运行搜索函数。