Как я могу оценить разницу между двумя изображениями?

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

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

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

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

20 ответов


общая идея

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

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

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

вопросы

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

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

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

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

  • образы совпадают?

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

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

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

    если нет, вы можете захотеть нормализовать картинки.

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

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

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

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

    если да, вы можете сначала применить алгоритм обнаружения края (например, вычислить градиент с помощью Sobel или Prewitt transform, apply some threshold), затем сравните ребра на первом изображении с ребрами на втором.

  • есть ли шум на фотографии?

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

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

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

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

пример

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

П. С. Все compare.py сценарий.

обновление: актуальные техники

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

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

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

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

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

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

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

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

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

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

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


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

закодировать изображение как в формате JPEG и ищите существенное изменение в размер.

я реализовал что-то подобное с эскизами видео и имел большой успех и масштабируемость.


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

import Image
import ImageChops

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

diff = ImageChops.difference(im2, im1)

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


два популярных и относительно простых метода: (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) )

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


тривиальная вещь, чтобы попробовать:

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


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

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

тогда для" реального " теста вам нужен такой критерий:

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

Итак, возможно, если E = 0.02, P = 1000, это будет означать (приблизительно), что он будет "другим", если какой-либо один пиксель изменится более чем на ~5 единиц (предполагая 8-битные изображения), или если более 1000 пикселей будут иметь какие-либо ошибки вообще.

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

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


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

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


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

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


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

https://github.com/nicolashahn/diffimg

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

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

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


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

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

  1. упростить изображения (возможно, порог?)
  2. применить соответствие шаблонов и проверить max_val с minMaxLoc

Совет: 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

надеюсь, что это помогает.


расстояние движителей Земли может быть именно то, что вам нужно. Это может быть abit тяжелый для реализации в режиме реального времени.


Как насчет вычисления Расстояние Манхэттен из двух изображений. Это дает вам n * n значений. Затем вы можете сделать что-то вроде среднего значения строки, чтобы уменьшить до n значений, и функцию над этим, чтобы получить одно единственное значение.


Мне очень повезло с jpg-изображениями, сделанными с той же камерой на штативе (1) значительно упрощение (например, от 3000 пикселей в ширину до 100 пикселей в ширину или даже меньше) (2) сплющивание каждого массива jpg в один вектор (3) попарная корреляция последовательных изображений с помощью простого алгоритма корреляции для получения коэффициента корреляции (4) коэффициент квадратной корреляции для получения R-Квадрата (i.e доля изменчивости на одном изображении, объясняемая изменением на следующем) (5) в целом в моем приложении, если r-квадрат

Это надежно и быстро в моей реализации (Mathematica 7)

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

Я не знаю, как использовать Python, но уверен, что он делает корреляции тоже нет?


вы можете вычислить гистограмму обоих изображений, а затем вычислить Коэффициент Бхаттачариа, это очень быстрый алгоритм, и я использовал его для обнаружения изменений выстрела в видео крикета (в C с помощью openCV)


проверьте, как вейвлеты Хаара реализуются isk-daemon. Вы можете использовать код imgdb C++ для вычисления разницы между изображениями на лету:

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

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


У меня была та же проблема, и я написал простой модуль python, который сравнивает два изображения одинакового размера с помощью ImageChops подушки для создания черно-белого изображения diff и суммирует значения гистограммы.

вы можете получить либо этот балл напрямую, либо процентное значение по сравнению с полным черным против белого различия.

Он также содержит простую функцию is_equal, с возможностью предоставления нечеткого порога под (и включая) изображение проходит как равное.

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

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


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


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

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 app для измерения и визуализации подобных изображений с помощью Tensorflow.


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', 6))
    r = compare_image_dir_with_hash('image1\', image2\', 10)
    for k in r.keys():
        print(k, r.get(k))


if __name__ == '__main__':
    main()
  • выход:

    False
    Правда
    \рис2 5.рис1 формате JPG\815.формат JPG
    \рис2 6.рис1 формате JPG\819.формат JPG
    \рис2 7.рис1\JPG и 900.формат JPG
    \рис2 8.рис1\jpg-файла 998.формат JPG
    \рис2 9.рис1\JPG и 1012.формат JPG

  • пример фотографии:

    • 815.формат jpg
      815.jpg

    • 5.формат jpg
      5.jpg


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