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

新手上路,请多包涵

这是我想做的:

我定期用网络摄像头拍照。有点像时间流逝的东西。但是,如果真的什么都没有改变,也就是说,图片 看起来 几乎一样,我不想存储最新的快照。

我想有某种方法可以量化差异,我将不得不凭经验确定一个阈值。

我正在寻找简单而不是完美。我正在使用蟒蛇。

原文由 carrier 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

大概的概念

选项 1:将两个图像加载为数组 ( scipy.misc.imread ) 并计算逐元素(逐像素)差异。计算差值的范数。

选项 2:加载两个图像。为它们中的每一个计算一些特征向量(如直方图)。计算特征向量而不是图像之间的距离。

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

问题

你应该首先回答这些问题:

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

如果不是,您可能需要调整它们的大小或裁剪它们。 PIL 库将有助于在 Python 中完成它。

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

  • 图像是否对齐?

如果不是,您可能需要先运行互相关,首先找到最佳对齐方式。 SciPy 具有执行此操作的功能。

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

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

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

但要小心,在某些情况下,这样做可能弊大于利。例如,黑暗背景上的单个明亮像素会使归一化图像有很大差异。

  • 颜色信息重要吗?

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

  • 图像中是否有明显的边缘?他们有可能搬家吗?

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

  • 图像中有噪点吗?

所有传感器都会用一定量的噪声污染图像。低成本传感器的噪声更大。在比较图像之前,您可能希望应用一些降噪。模糊是这里最简单(但不是最好)的方法。

  • 你想注意到什么样的变化?

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

考虑使用曼哈顿范数(绝对值之和)或零范数(不等于零的元素数)来衡量图像发生了多少变化。前者会告诉您图像偏离了多少,后者只会告诉您有多少像素不同。

例子

我假设您的图像对齐良好,大小和形状相同,可能曝光不同。为简单起见,我将它们转换为灰度,即使它们是彩色 (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 是二维 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 返回一个3D数组,平均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

PS 整个 compare.py 脚本。

更新:相关技巧

由于问题是关于视频序列的,其中帧可能几乎相同,并且您正在寻找不寻常的东西,因此我想提及一些可能相关的替代方法:

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

我强烈建议看一看“学习 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。

原文由 sastanin 发布,翻译遵循 CC BY-SA 3.0 许可协议

一个简单的解决方案:

将图像编码为 jpeg 并寻找 文件大小 的实质性变化。

我已经实现了与视频缩略图类似的东西,并且取得了很大的成功和可扩展性。

原文由 keparo 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题