我希望创建一个图像基础表,然后将任何新图像与此进行比较,以确定新图像是否是基础图像的完全副本或近似副本。
例如:如果您想减少存储同一图像数百次的情况,您可以只存储一份,并提供其引用链接。当输入新图像时,您希望将其与现有图像进行比较,以确保它不是重复的...有什么建议吗?
我的一个想法是缩小成小缩略图,然后随机选择100个像素位置进行比较。
我希望创建一个图像基础表,然后将任何新图像与此进行比较,以确定新图像是否是基础图像的完全副本或近似副本。
例如:如果您想减少存储同一图像数百次的情况,您可以只存储一份,并提供其引用链接。当输入新图像时,您希望将其与现有图像进行比较,以确保它不是重复的...有什么建议吗?
我的一个想法是缩小成小缩略图,然后随机选择100个像素位置进行比较。
关键点匹配的一个缺点是朴素实现的运行时间复杂度为O(n^2m),其中n是每个图像中关键点的数量,m是数据库中图像的数量。一些聪明的算法可以更快地找到最接近的匹配,例如四叉树或二进制空间分割。
替代方案:直方图方法
另一个不太健壮但潜在更快的解决方案是为每个图像构建特征直方图,并选择与输入图像直方图最接近的图像。我在本科时实现了这个方法,我们使用了3种颜色直方图(红色、绿色和蓝色)和两种纹理直方图,方向和比例尺寸。我将在下面详细介绍,但我应该指出,这仅适用于非常类似于数据库图像的匹配图像。重新缩放、旋转或变色的图像可能无法使用此方法,但像裁剪这样的小变化不会破坏算法
计算颜色直方图很简单——只需选择直方图桶的范围,并对每个范围中具有该颜色的像素数量进行统计。例如,考虑“绿色”直方图,并假设我们为直方图选择了4个桶:0-63、64-127、128-191和192-255。然后对于每个像素,我们查看其绿色值,并将计数添加到相应的桶中。当我们完成计数时,我们将每个桶总数除以整个图像中的像素数,以获得绿色通道的归一化直方图。
对于纹理方向直方图,我们首先对图像执行边缘检测。每个边缘点都有一个法向量,指向垂直于边缘的方向。我们将法向量的角度量化为0到PI之间的6个桶之一(由于边缘具有180度的对称性,因此我们将-PI到0之间的角度转换为0到PI之间)。在统计每个方向的边缘点数后,我们得到了一个表示纹理方向的非归一化直方图,通过将每个桶除以图像中边缘点的总数进行归一化。
为了计算纹理比例直方图,对于每个边缘点,我们测量到与相同方向的下一个最近的边缘点的距离。例如,如果边缘点A的方向为45度,算法将在该方向上行走,直到找到另一个方向为45度(或在合理偏差范围内)的边缘点。计算每个边缘点的这个距离后,我们将这些值倾倒到一个直方图中,并通过除以边缘点的总数来进行归一化。|A.green_histogram.bucket_1 - B.green_histogram.bucket_1|
第三选择 - 关键点 + 决策树
第三种方法可能比其他两种方法快得多,它使用语义文本森林(PDF)来提取简单的关键点,并使用一组决策树来对图像进行分类。这比简单的SIFT关键点匹配更快,因为它避免了昂贵的匹配过程,并且关键点比SIFT简单得多,因此关键点提取速度更快。然而,它保留了SIFT方法对旋转、缩放和照明的不变性,这是直方图方法所缺乏的一个重要特征。
更新:
我犯了错误 - 《语义文本森林》论文并非专门讨论图像匹配,而是区域标记。最初进行匹配的论文是:使用随机化树进行关键点识别。此外,以下论文继续发展这些想法并代表了当时的最新技术(约为2010年):
我所知道的最好方法是使用感知哈希(Perceptual Hash)。这里有一个良好的开源实现可供使用:
主要思想是通过识别原始图像文件中的显著特征,并对这些特征的紧凑表示进行哈希(而不是直接哈希图像数据),将每个图像缩小到一个小的哈希码或“指纹”。这意味着与将图像缩小为微小的缩略图并比较缩略图等简单方法相比,误报率大大降低。
phash提供了几种类型的哈希,并可用于图像、音频或视频。
这篇文章是我解决问题的起点,其中有很多好的想法,所以我想分享一下我的结果。重要的一点是,我找到了一种利用phash速度来避免基于关键点图像匹配缓慢性的方法。
为了得到通用解决方案,最好采用多个策略。每个算法最适合某些类型的图像变换,你可以利用这一点。
在顶部是最快的算法;在底部是最慢的(但更准确)。如果在更快的级别上找到了好的匹配,那么可能会跳过较慢的选项。
对于phash,我得到了非常好的结果。对于大小调整后的图像,准确率很高。但对于(感知上)修改后的图像(裁剪,旋转,镜像等),效果不好。为了处理哈希速度,我们必须使用磁盘缓存/数据库来维护干草堆的哈希。
phash真正好的地方在于,一旦构建了哈希数据库(对我来说,每秒约1000张图像),搜索速度就可以非常快,特别是当你可以将整个哈希数据库保留在内存中时。这相当实用,因为哈希只有8字节。
例如,如果你有100万张图像,它将需要一个包含100万个64位哈希值的数组(8 MB)。对于某些CPU而言,这适合于L2/L3缓存!在实际使用中,我看到的一个Corei7比较速度超过1千兆哈姆/秒,这只是与CPU的内存带宽有关。在64位CPU上,10亿图像数据库是实用的(需要8GB RAM),并且搜索不会超过1秒!
对于修改/裁剪的图像,似乎使用SIFT这样的转换不变特征点检测器是正确的选择。SIFT将产生良好的关键点,可以检测到裁剪/旋转/镜像等操作。然而,与phash使用的海明距离相比,描述符比较速度非常慢。这是一个重大限制。由于最多需要IxJxK个描述符比较来查找一个图像(I=数字干草堆图像,J=每个干草堆图像的目标关键点,K=每个针图像的目标关键点),所以需要进行大量的比较。当关键点检测器返回的特征数量少于您需要的数量时,它可能会表现最佳。例如,如果您要求400个并得到300个,那就很好。如果每次都得到400个,那么可能有一些好的特征被忽略了。
针图像的关键点可以比堆栈图像少,仍然能获得良好的结果。添加更多的关键点并不一定会带来巨大的收益,例如J=400和K=40时,我的命中率约为92%。使用J=400和K=400时,命中率只增加到96%。
我们可以利用汉明函数的极速来解决缩放、旋转、镜像等问题。可以使用多通道技术。在每次迭代中,变换子矩形,重新哈希,再次运行搜索函数。
我的公司每个月从制造商那里接收大约2400万张图片。我正在寻找一种快速解决方案,以确保我们上传到目录中的图片是新的图片。
我想说我已经在互联网上广泛搜索,试图找到一个理想的解决方案。我甚至开发了自己的边缘检测算法。
我评估了多个模型的速度和准确性。
我的图片有白色背景,使用phashing非常有效。就像redcalx所说,我建议使用phash或ahash。不要使用MD5哈希或任何其他加密哈希。除非你只想获取完全相同的图片匹配。在图像之间进行任何调整或操作都会产生不同的哈希值。
关于phash/ahash,请查看:imagehash
我想通过发布我的代码和准确性来扩展*redcalx*的帖子。
我的做法:
from PIL import Image
from PIL import ImageFilter
import imagehash
img1=Image.open(r"C:\yourlocation")
img2=Image.open(r"C:\yourlocation")
if img1.width<img2.width:
img2=img2.resize((img1.width,img1.height))
else:
img1=img1.resize((img2.width,img2.height))
img1=img1.filter(ImageFilter.BoxBlur(radius=3))
img2=img2.filter(ImageFilter.BoxBlur(radius=3))
phashvalue=imagehash.phash(img1)-imagehash.phash(img2)
ahashvalue=imagehash.average_hash(img1)-imagehash.average_hash(img2)
totalaccuracy=phashvalue+ahashvalue
这是我的一些结果:
item1 item2 totalsimilarity
desk1 desk1 3
desk1 phone1 22
chair1 desk1 17
phone1 chair1 34
希望这能帮到您!from PIL import Image
import imagehash
# image_fns : List of training image files
img_hashes = {}
for img_fn in sorted(image_fns):
hash = imagehash.average_hash(Image.open(image_fn))
if hash in img_hashes:
print( '{} duplicate of {}'.format(image_fn, img_hashes[hash]) )
else:
img_hashes[hash] = image_fn
from PIL import Image
import imagehash
# image_fns : List of training image files
img_hashes = {}
epsilon = 50
for img_fn1, img_fn2 in zip(image_fns, image_fns[::-1]):
if image_fn1 == image_fn2:
continue
hash1 = imagehash.average_hash(Image.open(image_fn1))
hash2 = imagehash.average_hash(Image.open(image_fn2))
if hash1 - hash2 < epsilon:
print( '{} is near duplicate of {}'.format(image_fn1, image_fn2) )