Как я могу определить разницу между двумя изображениями? - PullRequest
160 голосов
/ 10 октября 2008

Вот что я хотел бы сделать:

Я делаю снимки с веб-камеры через равные промежутки времени. Вроде как время перерыва. Однако, если ничего действительно не изменилось, то есть картинка в значительной степени выглядит так же, я не хочу сохранять последний снимок.

Я предполагаю, что есть какой-то способ количественной оценки разницы, и мне пришлось бы эмпирически определять порог.

Я ищу простоту, а не совершенство. Я использую Python.

Ответы [ 21 ]

245 голосов
/ 14 октября 2010

Общая идея

Вариант 1. Загрузка обоих изображений в виде массивов (scipy.misc.imread) и вычисление поэлементной (попиксельной) разницы. Рассчитайте норму разности.

Вариант 2. Загрузка обоих изображений. Рассчитайте некоторый вектор признаков для каждого из них (например, гистограмму). Рассчитать расстояние между векторами объектов, а не изображениями.

Однако есть некоторые решения, которые нужно принять в первую очередь.

Вопросы

Сначала вы должны ответить на эти вопросы:

  • Являются ли изображения одинаковой формы и размера?

    Если нет, возможно, вам придется изменить их размер или обрезать. Библиотека PIL поможет сделать это на Python.

    Если они взяты с одинаковыми настройками и тем же устройством, они, вероятно, одинаковы.

  • Хорошо ли выровнены изображения?

    Если нет, вы можете сначала запустить взаимную корреляцию, чтобы сначала найти лучшее выравнивание. SciPy имеет функции для этого.

    Если камера и сцена неподвижны, изображения, вероятно, будут хорошо выровнены.

  • Всегда ли выдержка изображений одинакова? (Легкость / контрастность одинаковы?)

    Если нет, вы можете нормализовать изображения.

    Но будьте осторожны, в некоторых ситуациях это может принести больше вреда, чем пользы. Например, один яркий пиксель на темном фоне сделает нормализованное изображение совершенно другим.

  • Важна ли информация о цвете?

    Если вы хотите заметить изменение цвета, у вас будет вектор значений цвета на точку, а не скалярное значение, как на изображении в оттенках серого. Вам нужно больше внимания при написании такого кода.

  • Есть ли четкие края на изображении? Они могут двигаться?

    Если да, вы можете сначала применить алгоритм обнаружения ребер (например, рассчитать градиент с помощью преобразования Собеля или Превитта, применить некоторый порог), затем сравнить ребра в первом изображении с ребрами во втором.

  • Есть ли шум на изображении?

    Все датчики загрязняют изображение некоторым количеством шума. Недорогие датчики имеют больше шума. Возможно, вы захотите применить некоторое шумоподавление, прежде чем сравнивать изображения. Blur - самый простой (но не самый лучший) подход.

  • Какие изменения вы хотите заметить?

    Это может повлиять на выбор нормы для различия между изображениями.

    Подумайте об использовании нормы Манхэттена (сумма абсолютных значений) или нулевой нормы (количество элементов, не равных нулю) для измерения степени изменения изображения. Первый скажет вам, сколько изображение выключено, последний покажет только, сколько пикселей отличаются.

Пример

Я предполагаю, что ваши изображения хорошо выровнены, одинакового размера и формы, возможно, с разной экспозицией. Для простоты я преобразую их в оттенки серого, даже если они являются цветными (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 - это двухмерные массивы 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 .

Обновление: соответствующие методы

Поскольку речь идет о видеопоследовательности, где кадры могут быть почти одинаковыми, а вы ищете что-то необычное, я хотел бы упомянуть некоторые альтернативные подходы, которые могут иметь отношение:

  • фоновое вычитание и сегментация (для обнаружения объектов переднего плана)
  • разреженный оптический поток (для обнаружения движения)
  • сравнение гистограмм или другой статистики вместо изображений

Я настоятельно рекомендую взглянуть на книгу «Обучение OpenCV», главы 9 (Части изображения и сегментация) и 10 (Отслеживание и движение). Первый учит использовать метод вычитания фона, второй дает некоторую информацию о методах оптического потока. Все методы реализованы в библиотеке OpenCV. Если вы используете Python, я предлагаю использовать OpenCV ≥ 2.3 и его cv2 модуль Python.

Самый простой вариант вычитания фона:

  • узнать среднее значение μ и стандартное отклонение σ для каждого пикселя фона
  • сравнить текущие значения пикселей с диапазоном (μ-2σ, μ + 2σ) или (μ-σ, μ + σ)

Более продвинутые версии учитывают временные ряды для каждого пикселя и обрабатывают нестатические сцены (например, движущиеся деревья или траву).

Идея оптического потока состоит в том, чтобы взять два или более кадров и назначить вектор скорости каждому пикселю (плотный оптический поток) или некоторым из них (разреженный оптический поток). Для оценки разреженного оптического потока вы можете использовать метод Лукаса-Канаде (он также реализован в OpenCV). Очевидно, что если поток большой (среднее среднее по максимальным значениям поля скорости), то в кадре что-то движется, и последующие изображения более различны.

Сравнение гистограмм может помочь обнаружить внезапные изменения между последовательными кадрами. Этот подход использовался в Courbon et al, 2010 :

Сходство последовательных кадров. Измеряется расстояние между двумя последовательными кадрами. Если оно слишком высокое, это означает, что второй кадр поврежден и, следовательно, изображение устранено. Расстояние Кульбака – Лейблера , или взаимная энтропия, на гистограммах двух кадров:

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

где p и q - используются гистограммы кадров. Порог фиксируется на 0,2.

75 голосов
/ 10 октября 2008

Простое решение:

Кодируйте изображение как jpeg и найдите существенное изменение в размере файла .

Я реализовал нечто похожее с миниатюрами видео и добился большого успеха и масштабируемости.

54 голосов
/ 13 октября 2008

Вы можете сравнить два изображения, используя функции из PIL .

import Image
import ImageChops

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

diff = ImageChops.difference(im2, im1)

Объект diff - это изображение, в котором каждый пиксель является результатом вычитания значений цвета этого пикселя во втором изображении из первого изображения. Используя разностное изображение, вы можете сделать несколько вещей. Самым простым является функция diff.getbbox(). Он скажет вам минимальный прямоугольник, который содержит все изменения между вашими двумя изображениями.

Вероятно, вы можете реализовать аппроксимации других вещей, упомянутых здесь, используя функции из PIL.

19 голосов
/ 10 октября 2008

Два популярных и относительно простых метода: (а) евклидово расстояние уже предложено, или (б) нормализованная взаимная корреляция Нормализованная взаимная корреляция имеет тенденцию быть заметно более устойчивой к изменениям освещения, чем простая взаимная корреляция. В Википедии дается формула для нормализованной взаимной корреляции . Существуют и более сложные методы, но они требуют немного больше работы.

Используя синтаксис, похожий на 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) )

при условии, что i1 и i2 являются массивами 2D-изображений в градациях серого.

13 голосов
/ 10 октября 2008

Тривиальная вещь, которую стоит попробовать:

Повторно скомпонуйте оба изображения к маленьким миниатюрам (например, 64 x 64) и сравните пиксели за пикселем с определенным порогом. Если исходные изображения почти одинаковы, уменьшенные эскизы будут очень похожими или даже точно такими же. Этот метод заботится о шуме, который может возникнуть, особенно в условиях низкой освещенности. Может быть даже лучше, если вы перейдете в оттенках серого.

7 голосов
/ 14 октября 2010

Я обращаюсь конкретно к вопросу о том, как вычислять, если они "достаточно разные". Я предполагаю, что вы можете понять, как вычитать пиксели один за другим.

Во-первых, я бы взял несколько изображений с ничего не меняющими и выяснил, какую максимальную величину может изменить любой пиксель только из-за различий в захвате, шума в системе обработки изображений, артефактов сжатия JPEG, и мгновенные изменения в освещении. Возможно, вы обнаружите, что следует ожидать разницы в 1 или 2 бита, даже когда ничего не происходит.

Тогда для «настоящего» теста вам нужен такой критерий:

  • то же самое, если до P пикселей отличаются не более чем на E.

Так что, возможно, если E = 0,02, P = 1000, это будет означать (приблизительно), что он будет «другим», если какой-либо один пиксель изменится более чем на ~ 5 единиц (при условии 8-битных изображений), или если более 1000 пикселей имели какие-либо ошибки.

Это предназначено, главным образом, как хороший метод "сортировки", чтобы быстро идентифицировать изображения, которые достаточно близки, чтобы не нуждаться в дальнейшем рассмотрении. В этом случае изображения, которые «проваливаются», могут в большей степени соответствовать более сложной / дорогостоящей методике, которая не давала бы ложных срабатываний, например, если камера слегка дрожала, или была более устойчивой к изменениям освещения.

Я запускаю проект с открытым исходным кодом, OpenImageIO , который содержит утилиту под названием "idiff", которая сравнивает различия с пороговыми значениями, подобными этим (на самом деле, даже более сложными). Даже если вы не хотите использовать это программное обеспечение, вы можете посмотреть на источник, чтобы увидеть, как мы это сделали. Он широко используется в коммерческих целях, и этот метод пороговой разработки был разработан для того, чтобы у нас мог быть набор тестов для программного обеспечения рендеринга и обработки изображений с «опорными изображениями», которые могут иметь небольшие отличия от платформы к платформе или поскольку мы внесли небольшие изменения в алгоритмы, поэтому мы хотели, чтобы операция «соответствовала в пределах допуска».

5 голосов
/ 10 октября 2008

Большинство ответов не относятся к уровням освещения.

Я бы сначала нормализовал изображение до стандартного уровня освещенности, прежде чем делать сравнение.

4 голосов
/ 27 мая 2018

У меня была похожая проблема на работе, я переписывал конечную точку преобразования изображений и хотел убедиться, что новая версия выдает тот же или почти такой же вывод, что и старая версия. Итак, я написал это:

https://github.com/nicolashahn/diffimg

, который работает с изображениями одинакового размера и на уровне пикселей, измеряет разницу значений в каждом канале: R, G, B (, A), принимает среднюю разницу этих каналов, а затем усредняет разница по всем пикселям, и возвращает соотношение.

Например, для 10х10 изображений с белыми пикселями и того же изображения, но один пиксель изменился на красный, разница в этом пикселе составляет 1/3 или 0,33 ... (RGB 0,0,0 против 255, 0,0), а для всех остальных пикселей равен 0. При 100 пикселях всего 0,33 ... / 100 = разность изображения ~ 0,33%.

Я считаю, что это отлично подойдет для проекта OP (я понимаю, что это очень старая статья, но она публикуется для будущих StackOverflowers, которые также хотят сравнивать изображения в python).

4 голосов
/ 30 марта 2018

Еще один приятный, простой способ измерить сходство между двумя изображениями:

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.

3 голосов
/ 10 октября 2008

Вы видели Алгоритм поиска похожих изображений вопрос? Проверьте это, чтобы увидеть предложения.

Я бы предложил вейвлет-преобразование ваших фреймов (для этого я написал расширение C с использованием преобразования Хаара); затем, сравнивая индексы наибольших (пропорционально) вейвлет-факторов между двумя изображениями, вы должны получить числовое приближение подобия.

...