209

这是我想做的事情:

我定期用网络摄像头拍照。有点像时间流逝的东西。但是,如果没有真正改变,即图片看起来几乎相同,我不想存储最新的快照。

我想有一些方法可以量化差异,我必须凭经验确定一个阈值。

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

4

24 回答 24

291

大概的概念

选项 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

如何比较。img1这里img2是 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返回一个 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脚本。

更新:相关技术

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

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

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

背景减法最简单的版本:

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

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

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

比较直方图可能有助于检测连续帧之间的突然变化。Courbon et al, 2010中使用了这种方法:

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

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

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

于 2010-10-14T15:43:55.083 回答
81

一个简单的解决方案:

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

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

于 2008-10-10T02:46:02.733 回答
67

您可以使用PIL中的函数比较两个图像。

import Image
import ImageChops

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

diff = ImageChops.difference(im2, im1)

diff 对象是一幅图像,其中每个像素都是从第一幅图像中减去第二幅图像中该像素的颜色值的结果。使用差异图像,您可以做几件事。最简单的是diff.getbbox()函数。它会告诉你包含两个图像之间所有变化的最小矩形。

您也可以使用 PIL 中的函数来实现此处提到的其他内容的近似值。

于 2008-10-13T06:50:17.437 回答
22

两种流行且相对简单的方法是:(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是二维灰度图像数组。

于 2008-10-10T03:47:44.010 回答
14

一个微不足道的尝试:

将两个图像重新采样为小缩略图(例如 64 x 64),并将缩略图与某个阈值逐个像素进行比较。如果原始图像几乎相同,则重新采样的缩略图将非常相似甚至完全相同。这种方法可以处理尤其是在低光场景中可能出现的噪点。如果你去灰度可能会更好。

于 2008-10-10T03:37:57.073 回答
9

另一种测量两个图像之间相似度的简单方法:

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)

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

于 2018-03-30T13:00:09.200 回答
7

我正在专门解决如何计算它们是否“足够不同”的问题。我假设您可以弄清楚如何逐个减去像素。

首先,我会拍摄一堆没有任何变化的图像,并找出任何像素变化的最大量,这仅仅是因为捕捉的变化、成像系统中的噪声、JPEG 压缩伪影和照明的瞬间变化. 也许您会发现即使没有任何移动,也会出现 1 或 2 位差异。

然后对于“真实”测试,您需要这样的标准:

  • 如果最多 P 个像素相差不超过 E,则相同。

因此,也许,如果 E = 0.02,P = 1000,这将意味着(大约)如果任何单个像素的变化超过约 5 个单位(假设为 8 位图像)或超过 1000,它将是“不同的”像素根本没有任何错误。

这主要是作为一种很好的“分类”技术来快速识别足够接近而不需要进一步检查的图像。然后,“失败”的图像可能会更多地使用更精细/更昂贵的技术,例如,如果相机晃动一点,或者对照明变化更稳健,就不会出现误报。

我运行了一个开源项目OpenImageIO,其中包含一个名为“idiff”的实用程序,该实用程序将差异与这样的阈值进行比较(实际上更精细)。即使您不想使用此软件,您也可能需要查看源代码以了解我们是如何做到的。它在商业上使用了很多,并且开发了这种阈值技术,以便我们可以拥有一个用于渲染和图像处理软件的测试套件,其中的“参考图像”可能会因平台而异,或者我们对tha 算法,所以我们想要一个“容差范围内的匹配”操作。

于 2010-10-14T16:04:18.180 回答
7

我在工作中遇到了类似的问题,我正在重写我们的图像转换端点,我想检查新版本是否产生与旧版本相同或几乎相同的输出。所以我写了这个:

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 的项目非常有效(我意识到这是一个非常古老的帖子,但是为未来的 StackOverflowers 发布,他们也想在 python 中比较图像)。

于 2018-05-27T15:27:49.393 回答
5

给出的大多数答案都不会涉及照明水平。

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

于 2008-10-10T03:55:20.063 回答
3

你见过寻找相似图像的算法问题吗?查看它以查看建议。

我建议对您的帧进行小波变换(我已经使用 Haar 变换为此编写了 C 扩展);然后,比较两张图片之间最大(按比例)小波因子的索引,您应该得到一个数值相似性近似值。

于 2008-10-10T03:13:42.363 回答
3

我遇到了同样的问题,并编写了一个简单的 python 模块,该模块使用枕头的 ImageChops 比较两个相同大小的图像以创建黑白差异图像并总结直方图值。

您可以直接获得此分数,也可以与全黑与全白差异相比的百分比值。

它还包含一个简单的 is_equal 函数,可以在(包括)图像通过时提供模糊阈值。

该方法不是很精细,但可能对其他在同一问题上苦苦挣扎的人有用。

https://pypi.python.org/pypi/imgcompare/

于 2016-11-21T00:23:54.613 回答
3

如果现在回复太晚了,我深表歉意,但由于我一直在做类似的事情,我想我可以以某种方式做出贡献。

也许使用 OpenCV,您可以使用模板匹配。假设您使用的是网络摄像头,如您所说:

  1. 简化图像(可能是阈值?)
  2. 应用模板匹配并使用 minMaxLoc 检查 max_val

提示: max_val (或 min_val 取决于使用的方法)会给你数字,大数字。要获得百分比差异,请使用与相同图像匹配的模板——结果将是您的 100%。

伪代码举例说明:

previous_screenshot = ...
current_screenshot = ...

# simplify both images somehow

# get the 100% corresponding value
res = matchTemplate(previous_screenshot, previous_screenshot, TM_CCOEFF)
_, hundred_p_val, _, _ = minMaxLoc(res)

# hundred_p_val is now the 100%

res = matchTemplate(previous_screenshot, current_screenshot, TM_CCOEFF)
_, max_val, _, _ = minMaxLoc(res)

difference_percentage = max_val / hundred_p_val

# the tolerance is now up to you

希望能帮助到你。

于 2018-06-15T16:08:16.707 回答
2
import os
from PIL import Image
from PIL import ImageFile
import imagehash
  
#just use to the size diferent picture
def compare_image(img_file1, img_file2):
    if img_file1 == img_file2:
        return True
    fp1 = open(img_file1, 'rb')
    fp2 = open(img_file2, 'rb')

    img1 = Image.open(fp1)
    img2 = Image.open(fp2)

    ImageFile.LOAD_TRUNCATED_IMAGES = True
    b = img1 == img2

    fp1.close()
    fp2.close()

    return b





#through picturu hash to compare
def get_hash_dict(dir):
    hash_dict = {}
    image_quantity = 0
    for _, _, files in os.walk(dir):
        for i, fileName in enumerate(files):
            with open(dir + fileName, 'rb') as fp:
                hash_dict[dir + fileName] = imagehash.average_hash(Image.open(fp))
                image_quantity += 1

    return hash_dict, image_quantity

def compare_image_with_hash(image_file_name_1, image_file_name_2, max_dif=0):
    """
    max_dif: The maximum hash difference is allowed, the smaller and more accurate, the minimum is 0.
    recommend to use
    """
    ImageFile.LOAD_TRUNCATED_IMAGES = True
    hash_1 = None
    hash_2 = None
    with open(image_file_name_1, 'rb') as fp:
        hash_1 = imagehash.average_hash(Image.open(fp))
    with open(image_file_name_2, 'rb') as fp:
        hash_2 = imagehash.average_hash(Image.open(fp))
    dif = hash_1 - hash_2
    if dif < 0:
        dif = -dif
    if dif <= max_dif:
        return True
    else:
        return False


def compare_image_dir_with_hash(dir_1, dir_2, max_dif=0):
    """
    max_dif: The maximum hash difference is allowed, the smaller and more accurate, the minimum is 0.

    """
    ImageFile.LOAD_TRUNCATED_IMAGES = True
    hash_dict_1, image_quantity_1 = get_hash_dict(dir_1)
    hash_dict_2, image_quantity_2 = get_hash_dict(dir_2)

    if image_quantity_1 > image_quantity_2:
        tmp = image_quantity_1
        image_quantity_1 = image_quantity_2
        image_quantity_2 = tmp

        tmp = hash_dict_1
        hash_dict_1 = hash_dict_2
        hash_dict_2 = tmp

    result_dict = {}

    for k in hash_dict_1.keys():
        result_dict[k] = None

    for dif_i in range(0, max_dif + 1):
        have_none = False

        for k_1 in result_dict.keys():
            if result_dict.get(k_1) is None:
                have_none = True

        if not have_none:
            return result_dict

        for k_1, v_1 in hash_dict_1.items():
            for k_2, v_2 in hash_dict_2.items():
                sub = (v_1 - v_2)
                if sub < 0:
                    sub = -sub
                if sub == dif_i and result_dict.get(k_1) is None:
                    result_dict[k_1] = k_2
                    break
    return result_dict


def main():
    print(compare_image('image1\\815.jpg', 'image2\\5.jpg'))
    print(compare_image_with_hash('image1\\815.jpg', 'image2\\5.jpg', 7))
    r = compare_image_dir_with_hash('image1\\', 'image2\\', 10)
    for k in r.keys():
        print(k, r.get(k))


if __name__ == '__main__':
    main()
  • 输出:



    image2\5.jpg image1\815.jpg
    image2\6.jpg image1\819.jpg
    image2\7.jpg image1\900.jpg
    image2\8.jpg image1\998.jpg
    image2\9.jpg image1\1012 .jpg

  • 示例图片:

    • 815.jpg
      815.jpg

    • 5.jpg
      5.jpg

于 2017-05-22T07:04:31.633 回答
1

地球移动距离可能正是您所需要的。不过,实时实施可能有点繁重。

于 2008-10-10T02:48:35.567 回答
1

计算两幅图像的曼哈顿距离怎么样。这给了你 n*n 个值。然后你可以做一些像行平均这样的事情来减少到 n 个值,然后用一个函数来获得一个单一的值。

于 2008-10-10T02:53:30.610 回答
1

我对在三脚架上使用同一台相机拍摄的 jpg 图像非常幸运,方法是(1)大大简化(比如从 3000 像素宽到 100 像素宽甚至更少)(2)将每个 jpg 数组扁平化为一个向量 (3) 使用简单的相关算法成对关联序列图像以获得相关系数 (4) 平方相关系数以获得 r 平方(即一个图像中的可变性分数由下一个图像的变化解释) (5) 通常在我的应用程序中如果 r-square < 0.9,我说这两个图像是不同的,并且在两者之间发生了一些事情。

这在我的实现中既健壮又快速(Mathematica 7)

值得玩转您感兴趣的图像部分,并通过将所有图像裁剪到那个小区域来专注于该部分,否则将错过远离相机但重要的变化。

我不知道如何使用 Python,但我确信它也有相关性,不是吗?

于 2011-02-19T21:28:00.693 回答
1

您可以计算两个图像的直方图,然后计算Bhattacharyya 系数,这是一个非常快速的算法,我用它来检测板球视频中的镜头变化(在 C 中使用 openCV)

于 2011-02-24T06:00:48.360 回答
1

查看isk-daemon如何实现 Haar Wavelets 。您可以使用它的 imgdb C++ 代码即时计算图像之间的差异:

isk-daemon 是一个开源数据库服务器,能够将基于内容的(视觉)图像搜索添加到任何与图像相关的网站或软件中。

该技术允许任何与图像相关的网站或软件的用户在小部件上绘制他们想要查找的图像,并让网站回复他们最相似的图像,或者在每个图像详细信息页面上简单地请求更多相似的照片。

于 2012-01-16T21:11:08.810 回答
1

一种更有原则的方法是使用全局描述符来比较图像,例如 GIST 或 CENTRIST。如此处所述的散列函数也提供了类似的解决方案。

于 2017-11-29T00:53:36.650 回答
1

通过计算均方误差,有一个使用 numpy 的简单快速的解决方案:

before = np.array(get_picture())
while True:
    now = np.array(get_picture())
    MSE = np.mean((now - before)**2)

    if  MSE > threshold:
        break

    before = now
于 2019-07-18T16:26:12.413 回答
1

这是我编写的一个函数,它以 2 个图像(文件路径)作为参数,并返回两个图像的像素组件之间的平均差异。这对我来说非常有效,可以确定视觉上“相等”的图像(当它们不==相等时)。

(我发现 8 是确定图像是否基本相同的一个很好的限制。)

(如果不添加预处理,图像必须具有相同的尺寸。)

from PIL import Image

def imagesDifference( imageA, imageB ):
    A = list(Image.open(imageA, r'r').convert(r'RGB').getdata())
    B = list(Image.open(imageB, r'r').convert(r'RGB').getdata())
    if (len(A) != len(B)): return -1
    diff = []
    for i in range(0, len(A)):
        diff += [abs(A[i][0] - B[i][0]), abs(A[i][1] - B[i][1]), abs(A[i][2] - B[i][2])]
    return (sum(diff) / len(diff))
于 2021-02-27T19:19:16.033 回答
0

我认为您可以简单地计算两个图像的亮度之间的欧几里德距离(即 sqrt(差异平方和,逐个像素)),如果这低于某个经验阈值,则认为它们相等。你最好包装一个 C 函数。

于 2008-10-10T02:48:15.910 回答
0

有许多指标可用于评估两个图像是否看起来像/看起来像多少。

我不会在这里深入任何代码,因为我认为这应该是一个科学问题,而不是技术问题。

通常,这个问题与人类对图像的感知有关,因此每种算法都有其对人类视觉系统特征的支持。

经典方法是:

可见差异预测器:用于评估图像保真度的算法(https://www.spiedigitallibrary.org/conference-proceedings-of-spie/1666/0000/Visible-differences-predictor--an-algorithm-for-the-评估/10.1117/12.135952.short?SSO=1 )

图像质量评估:从错误可见性到结构相似性 ( http://www.cns.nyu.edu/pub/lcv/wang03-reprint.pdf )

FSIM:图像质量评估的特征相似度指数(https://www4.comp.polyu.edu.hk/~cslzhang/IQA/TIP_IQA_FSIM.pdf

其中,SSIM(Image Quality Assessment: From Error Visibility to Structural Similarity)最容易计算,开销也很小,另一篇论文《基于梯度相似度的图像质量评估》(https://www.semanticscholar .org/paper/Image-Quality-Assessment-Based-on-Gradient-Liu-Lin/2b819bef80c02d5d4cb56f27b202535e119df988)。

还有更多其他方法。如果您对艺术感兴趣/真正关心艺术,请查看 Google Scholar 并搜索“视觉差异”、“图像质量评估”等内容。

于 2019-05-07T15:57:16.883 回答
0

使用 SSIM 测量 2 个图像之间的结构相似性指数度量。

于 2021-09-09T12:43:45.847 回答