图像比较算法

47 投票
9 回答
63319 浏览
提问于 2025-04-15 16:31

我正在尝试比较图片,看看它们是否不同。首先,我试着用RGB值计算皮尔逊相关系数,这个方法效果还不错,但如果图片稍微偏移一下,就会出现问题。比如说,如果两张图片完全相同,但其中一张稍微移动了,我就会得到一个不好的相关值。

有没有更好的算法建议呢?

顺便说一下,我是要比较成千上万张图片……

编辑:这里有我图片的例子(显微镜下的):

im1:

在这里输入图片描述

im2:

在这里输入图片描述

im3:

在这里输入图片描述

im1和im2是相同的,但稍微偏移/裁剪了,而im3应该被识别为完全不同的……

编辑: 问题在Peter Hansen的建议下解决了!效果很好!感谢所有的回答!一些结果可以在这里找到 http://labtools.ipk-gatersleben.de/image%20comparison/image%20comparision.pdf

9 个回答

8

如果你遇到的问题是关于像素偏移的,或许你可以考虑使用频率变换来比较一下。

快速傅里叶变换(FFT)应该是可以的(numpy有针对二维矩阵的实现),不过我总是听说小波变换在这类任务中表现更好^_^

关于性能,如果所有的图像大小都一样,我记得FFTW这个库为每种FFT输入大小创建了专门的函数,这样你就可以通过重复使用相同的代码来获得不错的性能提升……我不确定numpy是否基于FFTW,但如果不是,你可以考虑去了解一下这个库。

这里有一个原型……你可以稍微玩一下,看看哪个阈值适合你的图像。

import Image
import numpy
import sys

def main():
    img1 = Image.open(sys.argv[1])
    img2 = Image.open(sys.argv[2])

    if img1.size != img2.size or img1.getbands() != img2.getbands():
        return -1

    s = 0
    for band_index, band in enumerate(img1.getbands()):
        m1 = numpy.fft.fft2(numpy.array([p[band_index] for p in img1.getdata()]).reshape(*img1.size))
        m2 = numpy.fft.fft2(numpy.array([p[band_index] for p in img2.getdata()]).reshape(*img2.size))
        s += numpy.sum(numpy.abs(m1-m2))
    print s

if __name__ == "__main__":
    sys.exit(main())

另一种方法是先对图像进行模糊处理,然后将两幅图像的像素值相减。如果差异不为零,那么你可以将其中一幅图像在每个方向上移动1个像素,再进行比较。如果这次的差异比上一步小,你可以继续沿着梯度方向移动并相减,直到差异小于某个阈值或者再次增大。这样做应该有效,前提是模糊核的半径大于图像的偏移量。

此外,你还可以尝试一些在摄影工作流程中常用的工具,比如用于合成多张曝光或制作全景的工具,如Pano Tools

14

我之前用图像直方图比较做过这个。我的基本算法是这样的:

  1. 把图像分成红色、绿色和蓝色三个部分
  2. 为红色、绿色和蓝色通道创建标准化的直方图,然后把它们合并成一个向量 (r0...rn, g0...gn, b0...bn),其中 n 是“桶”的数量,256个应该就够了
  3. 把这个直方图从另一张图像的直方图中减去,然后计算它们之间的距离

这里有一些用 numpypil 写的代码

r = numpy.asarray(im.convert( "RGB", (1,0,0,0, 1,0,0,0, 1,0,0,0) ))
g = numpy.asarray(im.convert( "RGB", (0,1,0,0, 0,1,0,0, 0,1,0,0) ))
b = numpy.asarray(im.convert( "RGB", (0,0,1,0, 0,0,1,0, 0,0,1,0) ))
hr, h_bins = numpy.histogram(r, bins=256, new=True, normed=True)
hg, h_bins = numpy.histogram(g, bins=256, new=True, normed=True)
hb, h_bins = numpy.histogram(b, bins=256, new=True, normed=True)
hist = numpy.array([hr, hg, hb]).ravel()

如果你有两个直方图,可以这样计算它们之间的距离:

diff = hist1 - hist2
distance = numpy.sqrt(numpy.dot(diff, diff))

如果两张图像完全相同,距离就是0;它们越不一样,距离就越大。

对我来说,这个方法在照片上效果很好,但在文本和标志这样的图形上就不太行。

39

一年前有个类似的问题被提出来,得到了很多回复,其中有一个提到可以把图片像素化,这个方法我本来也想推荐,作为一个初步筛选步骤(这样可以很快排除那些差别很大的图片)。

在那个问题下还有一些更早的问题链接,里面有更多的参考资料和不错的答案。

下面是一个使用Scipy实现的例子,使用了你上面提到的三张图片(分别保存为im1.jpg、im2.jpg和im3.jpg)。最后的输出显示了im1与自身的比较,作为基准,然后是每张图片与其他图片的比较。

>>> import scipy as sp
>>> from scipy.misc import imread
>>> from scipy.signal.signaltools import correlate2d as c2d
>>>
>>> def get(i):
...     # get JPG image as Scipy array, RGB (3 layer)
...     data = imread('im%s.jpg' % i)
...     # convert to grey-scale using W3C luminance calc
...     data = sp.inner(data, [299, 587, 114]) / 1000.0
...     # normalize per http://en.wikipedia.org/wiki/Cross-correlation
...     return (data - data.mean()) / data.std()
...
>>> im1 = get(1)
>>> im2 = get(2)
>>> im3 = get(3)
>>> im1.shape
(105, 401)
>>> im2.shape
(109, 373)
>>> im3.shape
(121, 457)
>>> c11 = c2d(im1, im1, mode='same')  # baseline
>>> c12 = c2d(im1, im2, mode='same')
>>> c13 = c2d(im1, im3, mode='same')
>>> c23 = c2d(im2, im3, mode='same')
>>> c11.max(), c12.max(), c13.max(), c23.max()
(42105.00000000259, 39898.103896795357, 16482.883608327804, 15873.465425120798)

注意,im1与自身比较得分是42105,im2与im1的得分也差不多,但im3与其他两张的得分则远低于这个值的一半。你需要尝试其他图片,看看这个方法的效果如何,以及如何改进它。

运行时间比较长……在我的机器上要几分钟。我建议你先做一些预处理,避免浪费时间去比较那些差别很大的图片,可能可以用之前提到的“比较jpg文件大小”的方法,或者用像素化的方法。由于你有不同大小的图片,这会让事情变得复杂,但你没有提供足够的信息来说明可能会有多大的差异,所以很难给出一个具体的答案来考虑这个因素。

撰写回答