图像比较算法

47

我正在尝试比较图像以查找它们是否不同。首先,我尝试对RGB值进行Pearson相关性分析,这个方法也很好用,除非图片有些许移动。如果有一张图像是100%相同的,但是稍微移动了一下,我就会得到一个糟糕的相关性值。

有更好的算法建议吗?

顺便说一句,我想比较成千上万张图像...

编辑:这是我的图片示例(显微镜):

im1:

enter image description here

im2:

enter image description here

im3:

enter image description here

im1和im2是相同的,但是有一点点移动/裁剪,而im3应该被识别为完全不同的图像...

编辑: 根据Peter Hansen的建议,问题已得到解决!效果非常好!感谢所有答案!某些结果可以在这里找到 http://labtools.ipk-gatersleben.de/image%20comparison/image%20comparision.pdf


1
如果你能更具体地说明你拥有的图片类型以及它们在哪些方面可以不同(比如缩放、旋转、光照等),那么我们就能更容易地给出一个好的答案和解决方案。 - Hannes Ovrén
已经有很多类似的问题了。https://dev59.com/CHRC5IYBdhLWcg3wUvUS检测两个图像是否视觉上相同 https://dev59.com/Y3VC5IYBdhLWcg3wxEJ1如何量化两个图像之间的差异 https://dev59.com/CHRC5IYBdhLWcg3wUvUS检测两个图像是否视觉上相同 这个问题也跟显微镜有关:http://stackoverflow.com/questions/967436/检查图像特征对齐 - endolith
除了这些出色的答案之外,通常最好在HSV空间而不是RGB空间中比较真实世界的图像。 - Martin Beckett
@mgb,没错。请注意,我的答案确实使用了亮度,使用了W3C建议的算法。虽然这个算法不一定是最好的通用选择,但对于这种情况应该还可以。 - Peter Hansen
9个回答

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文件大小"技巧或像素化。您有不同大小的图像使事情变得复杂,但您没有提供足够的信息来说明可能期望的切割程度,因此很难给出特定的答案以考虑这一点。


3
看起来这个方法非常有效!我的阳性对照得出了明确的结果!通过将图片缩小到50%,我获得了很大的速度提升。非常感谢! - honeymoon
1
哦,我必须试一下这个!这种方法的灵敏度真是惊人!我会发布一个示例,展示它的工作效果有多好!它真的非常强大!到目前为止,所有的实现都无法处理移位图片;-) - honeymoon
1
对于那些感兴趣的人,这里是一些结果: http://www.file-upload.net/download-2054026/image_comparision.pdf.html 致以亲切的问候和感谢! - honeymoon
1
我已经更改了链接,现在它已经上线了: http://labtools.ipk-gatersleben.de/image%20comparison/image%20comparision.pdf - honeymoon
1
我在我的全景算法中使用了这个解决方案,它非常好用。虽然速度可能有点慢,但是根据snowflake所说的,似乎可以更快地运行。非常感谢你们两个——非常感激! - Brandon
显示剩余6条评论

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,它们越不同,距离就越大。

对于我的照片效果还不错,但对于文本和标志等图形失败了。


4
强调一下:即使两个直方图相等,也不一定意味着生成它们的两幅图像在结构上有任何相似之处。它们只是恰好具有相同的颜色分布。以美国和英国国旗为例,它们很可能会生成类似的直方图。 - Hannes Ovrén

8
您需要更明确地说明问题,但是从这5幅图像来看,所有生物体似乎都朝着同一个方向。如果这总是这种情况,您可以尝试在两个图像之间进行归一化交叉相关,并将峰值作为相似度的度量。我不知道Python中是否有归一化交叉相关函数,但是有一个类似的fftconvolve()函数,您可以自己进行循环交叉相关:
a = asarray(Image.open('c603225337.jpg').convert('L'))
b = asarray(Image.open('9b78f22f42.jpg').convert('L'))
f1 = rfftn(a)
f2 = rfftn(b)
g =  f1 * f2
c = irfftn(g)

这段代码无法正常工作,因为图像的大小不同,并且输出结果没有加权或规范化。
输出峰值的位置表示两个图像之间的偏差,峰值幅度表示相似性。应该有一种方法来加权/规范化它,以便您可以区分好匹配和差匹配之间的差异。
这不是我想要的答案,因为我还没有弄清楚如何将其规范化,但如果我弄清楚了,我会更新它,这会给您一个研究的思路。

8
如果您的问题与像素偏移有关,也许您应该与频率变换进行比较。
FFT 应该可以用 (numpy 有一个2D矩阵的实现), 不过我总是听说小波在这种任务中更好 ^_^
关于性能,如果所有的图像都是相同的尺寸,如果我没记错,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


这种方法的问题在于(自然)照片通常具有非常相似的频率内容。因此,仅使用FFT很可能效果不佳。我认为对于许多其他主题领域也是如此。 - Hannes Ovrén
1
@kigurai 我做了一些测试,使用具有相同“自然”内容的图像(两个不同的核爆炸,它们很容易找到,在谷歌图片中搜索“test”),以及伦敦眼的另一张图片,在我的快速FFT测试中得分为41583454和45014233...其中一个爆炸向右移动3个像素(填充白色)仅得分8749886(少4倍),15个像素的移动仍然是17325409(少2倍)。当然,完全相同的图像得分为0。因此,尽管您反对,平凡的FFT似乎是一种非常好的比较方法。 - fortran
不客气...我很高兴你最终从我的信号处理课程中获益 xD - fortran
首批结果看起来不是很好。原则上它可以工作,但不够敏感。在出现真正的重复之前会有很多误报。至少我离目标更近了...这些图片真的很讨厌,比如这两张最好的命中率是0: http://www9.picfront.org/picture/1UwuRQAw9ol/img/14B_033_01.jpg http://www7.picfront.org/picture/aPbgLAFg9NY/img/14B_038_01.jpg 愚蠢的是实际上我必须在显微镜下工作而没有时间编程,但这确实是一个有趣的任务...停不下来;-) 继续阅读、编码、尝试... - honeymoon
@fortran 我自己没有进行过全面的测试,但我从未见过任何涉及如此简单的计算机视觉方法来解决图像相似性的问题。同样地,我的教授们也提出了这个观点。而且,根据原帖的验证,似乎该观点是正确的 ;) - Hannes Ovrén
显示剩余3条评论

3

我曾经学过一些图像处理课程,记得当进行匹配时,通常首先将图像变为灰度,并锐化图像的边缘,以便只看到边缘。然后你(软件)可以移动和相减图像,直到差异最小。

如果差异大于您设置的阈值,则图像不相等,您可以继续下一个。具有较小阈值的图像可以接着分析。

我认为最多只能从可能的匹配中剔除大部分,但需要亲自比较可能的匹配以确定它们是否真的相等。

我无法展示代码,因为这是很久以前的事了,而且我当时使用的是Khoros/Cantata。


2
为了在我 Ubuntu 16.04 上(截至 2017 年 4 月)正确工作导入,我安装了 Python 2.7 和以下内容:
sudo apt-get install python-dev
sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk
sudo apt-get install python-scipy
sudo pip install pillow

然后我将Snowflake的导入更改为以下内容:
import scipy as sp
from scipy.ndimage import imread
from scipy.signal.signaltools import correlate2d as c2d

太棒了,Snowflake的脚本8年后仍然对我有效!


2
首先,相关性是一种非常CPU密集且不太准确的相似度测量方法。为什么不直接计算各个像素之间差值的平方和呢?
如果最大偏移量有限制,那么一个简单的解决方案是:生成所有可能的偏移图像,并找到最佳匹配的图像。确保你只在所有偏移图像中都能匹配的像素子集上计算你的匹配变量(即相关性)。此外,你的最大偏移量应该显著小于你的图像大小。
如果你想使用更高级的图像处理技术,我建议你看看SIFT这是一种非常强大的方法,可以理论上独立于平移、旋转和缩放正确地匹配图像中的物品。

1
我提出了一种基于图像直方图的Jaccard相似度指数的解决方案。参见:https://en.wikipedia.org/wiki/Jaccard_index#Weighted_Jaccard_similarity_and_distance 您可以计算像素颜色分布的差异。这确实对平移非常不变。
from PIL.Image import Image
from typing import List

def jaccard_similarity(im1: Image, im2: Image) -> float:
    """Compute the similarity between two images.
    First, for each image an histogram of the pixels distribution is extracted.
    Then, the similarity between the histograms is compared using the weighted Jaccard index of similarity, defined as:
    Jsimilarity = sum(min(b1_i, b2_i)) / sum(max(b1_i, b2_i)
    where b1_i, and b2_i are the ith histogram bin of images 1 and 2, respectively.

    The two images must have same resolution and number of channels (depth).

    See: https://en.wikipedia.org/wiki/Jaccard_index
    Where it is also called Ruzicka similarity."""

    if im1.size != im2.size:
        raise Exception("Images must have the same size. Found {} and {}".format(im1.size, im2.size))

    n_channels_1 = len(im1.getbands())
    n_channels_2 = len(im2.getbands())
    if n_channels_1 != n_channels_2:
        raise Exception("Images must have the same number of channels. Found {} and {}".format(n_channels_1, n_channels_2))

    assert n_channels_1 == n_channels_2

    sum_mins = 0
    sum_maxs = 0

    hi1 = im1.histogram()  # type: List[int]
    hi2 = im2.histogram()  # type: List[int]

    # Since the two images have the same amount of channels, they must have the same amount of bins in the histogram.
    assert len(hi1) == len(hi2)

    for b1, b2 in zip(hi1, hi2):
        min_b = min(b1, b2)
        sum_mins += min_b
        max_b = max(b1, b2)
        sum_maxs += max_b

    jaccard_index = sum_mins / sum_maxs

    return jaccard_index

关于均方误差,Jaccard指数始终在[0,1]范围内,因此可以比较不同图像尺寸。然后,您可以比较两个图像,但必须将它们重新调整为相同的大小!或者像素计数必须以某种方式标准化。我使用了这个:
import sys

from skincare.common.utils import jaccard_similarity

import PIL.Image
from PIL.Image import Image

file1 = sys.argv[1]
file2 = sys.argv[2]

im1 = PIL.Image.open(file1)  # type: Image
im2 = PIL.Image.open(file2)  # type: Image

print("Image 1: mode={}, size={}".format(im1.mode, im1.size))
print("Image 2: mode={}, size={}".format(im2.mode, im2.size))

if im1.size != im2.size:
    print("Resizing image 2 to {}".format(im1.size))
    im2 = im2.resize(im1.size, resample=PIL.Image.BILINEAR)

j = jaccard_similarity(im1, im2)
print("Jaccard similarity index = {}".format(j))

在您的图片上进行测试:

$ python CompareTwoImages.py im1.jpg im2.jpg
Image 1: mode=RGB, size=(401, 105)
Image 2: mode=RGB, size=(373, 109)
Resizing image 2 to (401, 105)
Jaccard similarity index = 0.7238955686269157
$ python CompareTwoImages.py im1.jpg im3.jpg 
Image 1: mode=RGB, size=(401, 105)
Image 2: mode=RGB, size=(457, 121)
Resizing image 2 to (401, 105)
Jaccard similarity index = 0.22785529941822316
$ python CompareTwoImages.py im2.jpg im3.jpg 
Image 1: mode=RGB, size=(373, 109)
Image 2: mode=RGB, size=(457, 121)
Resizing image 2 to (373, 109)
Jaccard similarity index = 0.29066426814105445

你也可以考虑尝试不同的重采样滤波器(例如NEAREST或LANCZOS),因为它们在调整大小时会改变颜色分布。
此外,请注意交换图像会改变结果,因为第二个图像可能会被降采样而不是上采样(毕竟,裁剪可能更适合你的情况而不是重新缩放)。

1

我猜你可以像这样做:

  • 估计参考图像与比较图像的垂直/水平位移。简单的SAD(绝对差之和)与运动向量一起使用即可。

  • 相应地移动比较图像

  • 计算你要做的Pearson相关系数

测量位移并不困难。

  • 在比较图像中取出一个区域(大约32x32)。
  • 将它在水平方向上移动x个像素,在垂直方向上移动y个像素。
  • 计算与原始图像的SAD(绝对差之和)。
  • 在一个小范围内(-10,+10),对几个x和y的值进行此操作。
  • 找到差异最小的地方。
  • 将该值选为移动向量。

注意:

如果所有x和y的值的SAD都很高,则可以认为图像非常不同,并且不需要进行移位测量。


1
有许多更复杂的运动估计(“移位测量”)算法可用。如果您选择检查的区域选择不当(本质上不是2D),则您的方法将彻底失败。 - Hannes Ovrén
1
参考:其中一个“标准”的运动估计算法是Lucas-Kanade跟踪器。 - Hannes Ovrén

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接