如何量化两幅图像之间的差异?

225

以下是我想做的事情:

我想定期使用网络摄像头拍照片。有点像时间流逝的东西。但是,如果没有真正发生改变,也就是说,图片基本上看起来相同,我不想存储最新的快照。

我想象有一种量化差异的方法,我将不得不凭经验确定阈值。

我寻求简单而非完美。 我正在使用Python。


相关:https://dev59.com/EnVD5IYBdhLWcg3wTJvF - Anoyz
25个回答

308

思路概述

选项1:将两张图像加载为数组(scipy.misc.imread),然后计算元素间的差异(按像素计算)。计算差异的范数。

选项2:加载两张图像。为每个图像计算某种特征向量(例如直方图)。计算特征向量之间的距离而不是图像。

但是,首先需要做出一些决定。

问题

您应该首先回答以下问题:

  • 图像是否具有相同的形状和尺寸?

    如果没有,则可能需要调整它们的大小或裁剪它们。PIL库可以在Python中帮助您完成这项工作。

    如果它们使用相同的设置和相同的设备拍摄,则它们可能是相同的。

  • 图像是否对齐良好?

    如果没有,则您可能需要首先运行交叉关联,以首先找到最佳对齐方式。SciPy具有执行此操作的功能。

    如果相机和场景静止,则图像可能对齐良好。

  • 图像曝光是否始终相同?(亮度/对比度是否相同?)

    如果不是,则可能需要规范化图像。

    但要小心,在某些情况下,这可能会比有益更加错误。例如,暗背景上的单个亮像素将使规范化的图像非常不同。

  • 颜色信息是否重要?

    如果要注意颜色变化,则每个点将具有一个颜色值向量,而不是灰度图像中的标量值。编写此类代码时需要更多注意。

  • 图像中是否存在明显的边缘?它们可能会移动吗?

    如果是,则可以首先应用边缘检测算法(例如,使用Sobel或Prewitt变换计算梯度,应用一些阈值),然后将第一张图像上的边缘与第二张上的边缘进行比较。

  • 图像中是否存在噪声?

    所有传感器都会使图像受到某些噪声污染。低成本传感器具有更多噪声。在比较图像之前,您可能希望应用一些噪声减少。模糊是最简单(但不是最好)的方法。

  • 您希望注意到什么样的更改?

    这可能会影响用于图像之间差异的范数选择。

    考虑使用曼哈顿范数(绝对值之和)或零范数(不等于0的元素数)来测量图像的变化程度。前者将告诉您图像偏离多少,而后者仅告诉有多少像素不同。

示例

我假设您的图像对齐良好,具有相同的大小和形状,可能具有不同的曝光。为简单起见,即使它们是彩色(RGB)图像,我也将它们转换为灰度。

import sys

from scipy.misc import imread
from scipy.linalg import norm
from scipy import sum, average

主函数,读取两张图片,转换为灰度图,进行比较并打印结果:

def main():
    file1, file2 = sys.argv[1:1+2]
    # read images as 2D arrays (convert to grayscale for simplicity)
    img1 = to_grayscale(imread(file1).astype(float))
    img2 = to_grayscale(imread(file2).astype(float))
    # compare
    n_m, n_0 = compare_images(img1, img2)
    print "Manhattan norm:", n_m, "/ per pixel:", n_m/img1.size
    print "Zero norm:", n_0, "/ per pixel:", n_0*1.0/img1.size

如何比较。这里的img1img2是2D SciPy数组:

def compare_images(img1, img2):
    # normalize to compensate for exposure difference, this may be unnecessary
    # consider disabling it
    img1 = normalize(img1)
    img2 = normalize(img2)
    # calculate the difference and its norms
    diff = img1 - img2  # elementwise for scipy arrays
    m_norm = sum(abs(diff))  # Manhattan norm
    z_norm = norm(diff.ravel(), 0)  # Zero norm
    return (m_norm, z_norm)
如果文件是彩色图像,则imread返回一个三维数组,平均RGB通道(最后一个数组轴)以获取强度。对于灰度图像(例如.pgm),无需执行此操作。
def to_grayscale(arr):
    "If arr is a color image (3D array), convert it to grayscale (2D array)."
    if len(arr.shape) == 3:
        return average(arr, -1)  # average over the last axis (color channels)
    else:
        return arr

规范化很简单,您可以选择将其规范化为[0,1]而不是[0,255]。arr 是一个SciPy数组,所以所有操作都是逐元素的。

def normalize(arr):
    rng = arr.max()-arr.min()
    amin = arr.min()
    return (arr-amin)*255/rng

运行main函数:

if __name__ == "__main__":
    main()

现在你可以将所有这些放入一个脚本中,并针对两个图像运行。如果我们将图像与自身进行比较,则没有任何区别:

$ python compare.py one.jpg one.jpg
Manhattan norm: 0.0 / per pixel: 0.0
Zero norm: 0 / per pixel: 0.0

如果我们对图像进行模糊处理并与原图进行比较,会发现有一些差异:

$ python compare.py one.jpg one-blurred.jpg 
Manhattan norm: 92605183.67 / per pixel: 13.4210411116
Zero norm: 6900000 / per pixel: 1.0

P.S. 整个 compare.py 脚本。

更新:相关技术

因为问题涉及到视频序列,其中帧很可能几乎相同,你在寻找异常,我想提及一些可能相关的替代方法:

  • 背景减法和分割(检测前景对象)
  • 稀疏光流(检测运动)
  • 比较直方图或其他统计数据而不是图像

我强烈推荐看一下 “Learning OpenCV” 书,第9章(图像部分和分割)和第10章(跟踪和运动)。前者教你使用背景减法方法,后者给出了一些关于光流方法的信息。所有方法都在OpenCV库中实现。如果您使用Python,我建议使用OpenCV≥2.3及其cv2 Python模块。

背景减法的最简单版本:

  • 学习每个像素的背景平均值μ和标准差σ
  • 将当前像素值与(μ-2σ、μ+2σ)或(μ-σ,μ+σ)范围进行比较

更高级的版本可能会考虑每个像素的时间序列,处理非静态场景(如移动的树木或草地)。

光流的思想是取两个或多个帧,并为每个像素分配速度向量(密集光流)或其中一些像素(稀疏光流)。要估计稀疏光流,可以使用Lucas-Kanade方法(它也在OpenCV中实现)。显然,如果有很多流(速度场的最大值超过平均值),那么帧中有东西在移动,后续图像将更加不同。

比较直方图可以帮助检测连续帧之间的突变。这种方法在Courbon等人, 2010中使用:

连续帧的相似性。测量两帧之间的距离。如果太高,则意味着第二帧已损坏,因此该图像被消除。使用两个帧的直方图的Kullback-Leibler距离或互熵:

$$ d(p,q) = \sum_i p(i) \log (p(i)/q(i)) $$

其中pq是帧的直方图。阈值固定为0.2。


我在第44行收到了一个RuntimeWarning: invalid value encountered in double_scalars,并且在第30行收到了一个ValueError: array must not contain infs or NaNs。第44行代码为return (arr-amin)*255/rng,第30行代码为z_norm = norm(diff.ravel(), 0) - BioGeek
@BioGeek,如果rng等于零。只需添加一个检查并将rng设置为1。 - haisi

87

一个简单的解决方法:

将图片编码为 jpeg 格式,并查看文件大小是否有大幅度变化。

我曾经在视频缩略图上实现过类似的方法,获得了很大的成功和可扩展性。


5
这是一个非常简单易行的解决方案,比逐像素比较要好得多。如果您的网络摄像头图像存在一些噪点,或者图像移动了一个像素,那么直接比较将捕捉到所有这些无意义的变化。更加稳健的方法是计算离散余弦变换,然后在频域中比较图像。使用类似JPEG压缩的方法可以获得大部分的好处,而不需要深入研究傅里叶理论。 - AndrewF
2
喜欢它。虽然其他解决方案也可以,但这对于常见情况有很大优势:如果您不想保存“基本”图像怎么办?只需将文件大小保存为哈希值,然后通过减法比较数字即可。在我的情况下,我有4张图片,其中一张非常相似,而其他3张则完全不同。只需缩放到相同的尺寸,转换为jpg并进行减法运算。真的很好。 - Diego Andrés Díaz Espinoza
谢谢,对我很有帮助 :-D 我用它来提取通过视频会议呈现的快速滚动文档的片段。使用建议的差异检测应用于相邻帧,我选择了关键帧,其中有可见的内容跳跃。从超过2000帧视频中选择约50张图像后,我可以重构呈现的文档。 - Glushiator
如果有用的话,你可以使用OpenCV对图像进行编码,而无需保存它,方法是 cv2.imencode('jpg', img) - PangolinPaws

70
您可以使用来自PIL的函数比较两个图像。
import Image
import ImageChops

im1 = Image.open("splash.png")
im2 = Image.open("splash2.png")

diff = ImageChops.difference(im2, im1)
diff对象是一张图像,其中每个像素的值都是第一张图片对应像素颜色值减去第二张图片对应像素颜色值的结果。使用diff图像,你可以做很多事情。最简单的方法是使用diff.getbbox()函数,它将告诉你包含两张图片之间所有变化的最小矩形。你也可以使用PIL库的其他函数来实现这里提到的其他功能的近似版本。

2
我想保存差异图像。也就是保存包含图像差异的 diff 对象。我该如何保存它? - Sagar
2
@Anthony,你可以在diff对象上调用save()方法并指定图像名称,就像这样:diff.save("diff.png"),它会为你保存差异图像。 - Sagar
2
注意:这已经改变了导入方式,不能再像以前一样工作了,至少在 Python 3.10 中测试过,你必须使用 from PIL import Image, ImageChops - nck

23

两种流行且相对简单的方法是:(a) 已经提出的欧几里得距离,或者 (b) 归一化交叉相关。归一化交叉相关比简单交叉相关更具鲁棒性,能更好地适应光照变化。维基百科给出了归一化交叉相关的公式。还存在更复杂的方法,但需要进行更多的工作。

使用类似 numpy 的语法,

dist_euclidean = sqrt(sum((i1 - i2)^2)) / i1.size

dist_manhattan = sum(abs(i1 - i2)) / i1.size

dist_ncc = sum( (i1 - mean(i1)) * (i2 - mean(i2)) ) / (
  (i1.size - 1) * stdev(i1) * stdev(i2) )

假设 i1i2 是二维灰度图像数组。


3
图像互相关函数已经集成到了SciPy中(http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.correlate2d.html#scipy.signal.correlate2d),并且使用FFT的快速版本可在stsci python中获得(http://www.stsci.edu/resources/software_hardware/pyraf/stsci_python)。 - endolith

16

一个很简单的尝试:

将两张图片都重新采样为小缩略图(例如 64 x 64),并使用一定的阈值逐像素比较缩略图。如果原始图片几乎相同,那么重新采样的缩略图将非常相似甚至完全相同。这种方法可以处理低光场景中可能出现的噪点。如果你转为灰度,效果可能会更好。


但是你如何比较像素? - carrier
一旦你有了缩略图,你可以逐个像素地进行比较。如果你在处理彩色图像,你需要计算RGB值之间的“距离”,如果是灰度图像,只需计算灰度值之间的差异即可。 - Ates Goral
1
逐个比较像素。这是什么意思?如果64x64像素测试中的一个失败,测试是否应该失败? - Federico A. Ramponi
我所说的“用一个特定的阈值逐像素比较缩略图”是指提出一个模糊算法来比较像素。如果计算得出的差异(取决于您的模糊算法)超过了某个特定的阈值,那么这些图像就“不相同”。 - Ates Goral
1
非常简单的例子,不需要“模糊算法”:并行循环遍历每个像素(将图像1的像素#n与图像2的像素#n进行比较),并将值的差异加到变量中。 - mk12
@Mk12 这种方法可能无法处理像网络摄像头这样的嘈杂图像源。 - Ates Goral

11

另一种简单好用的测量两张图片相似度的方法:

import sys
from skimage.measure import compare_ssim
from skimage.transform import resize
from scipy.ndimage import imread

# get two images - resize both to 1024 x 1024
img_a = resize(imread(sys.argv[1]), (2**10, 2**10))
img_b = resize(imread(sys.argv[2]), (2**10, 2**10))

# score: {-1:1} measure of the structural similarity between the images
score, diff = compare_ssim(img_a, img_b, full=True)
print(score)

如果其他人对于一种更强大的比较图像相似度的方式感兴趣,我写了一个使用Tensorflow测量和可视化相似图像的教程和Web 应用程序


4
是的,skimage非常适合用于这个应用。我经常使用from skimage.measure import compare_ssim, compare_mseskimage.measure文档 - ximiki
此代码已被弃用。compare_ssim现在是structural_similarity。请使用from skimage.metrics import structural_similarityfrom imageio import imread - shredEngineer

9
我在工作中遇到了类似的问题,我正在重写我们的图像转换端点,我想检查新版本是否产生与旧版本相同或几乎相同的输出。因此,我编写了以下代码:https://github.com/nicolashahn/diffimg。它对于同样大小的图像进行操作,并在每个像素级别上测量各通道的值的差异:R、G、B(A),获取这些通道的平均差异,然后将所有像素的差异平均值返回一个比率。
例如,对于一个10x10的白色像素图像和另一个相同的图像,只有一个像素变成了红色,那么该像素的差异是1/3或0.33…(RGB 0,0,0 vs 255,0,0),而其他所有像素的差异都为0。有100个像素总共,0.33… / 100 = 图像中约0.33%的差异。
我认为这对于OP的项目完全可行(我意识到这是一个非常老的帖子,但发布给未来也想在python中比较图像的StackOverflowers)。

7
我特意解答如何计算它们是否“足够不同”。 我假设您可以逐一减去像素。首先,我会拍摄许多没有任何变化的图像,并找出由于捕获中的变化,成像系统中的噪声,JPEG压缩伪像和瞬态光照变化而导致的任何像素最大变化量。也许即使什么都没有移动,您会发现1或2个位差异是可以预期的。然后,对于“真正”的测试,您需要这样的标准:如果最多P个像素的差异不超过E,则相同。因此,如果E = 0.02,P = 1000,则(大约)意味着如果任何单个像素更改超过〜5个单位(假设为8位图像),或者如果超过1000个像素有任何错误,则将被视为“不同”。这主要是作为一种良好的“分流”技术,以快速识别足够接近而不需要进一步检查的图像。未通过的图像可能需要采用更复杂/昂贵的技术,如果相机抖动,例如,或对光照变化更加稳健,则不会产生误报。
我负责运营一个开源项目OpenImageIO,其中包含一个名为"idiff"的实用程序,该程序可以比较差异并设置像这样的阈值(实际上更加复杂)。即使您不想使用此软件,您可能也想查看源代码以了解我们是如何做到的。它被商业化广泛使用,并且这种阈值技术是为了让我们能够拥有渲染和图像处理软件的测试套件,其中包含“参考图像”,这些图像可能在平台之间具有小差异,或者当我们对算法进行微调时会发生变化,因此我们需要进行“在容限内匹配”的操作。

5

大部分给出的答案都没有涉及照明水平。

在进行比较之前,我首先会将图像归一化到标准光照水平。


如果您正在拍摄定期图像并对相邻的一对进行差异比较,那么在有人打开灯之后,您可能可以保留第一张图像。 - walkytalky

4

你是否看过寻找相似图像的算法问题?可以查看一下以获取建议。

我建议对帧进行小波变换(我使用哈尔变换编写了一个C扩展程序),然后比较两张图片中最大(按比例计算)小波因子的索引,这样应该能得到数值上的相似度近似值。


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