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

StackOverflow https://stackoverflow.com/questions/189943

Вопрос

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

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

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

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

Это было полезно?

Решение

Общая идея

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

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

Однако сначала нужно принять несколько решений.

Вопросы

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

  • Имеют ли изображения одинаковую форму и размер?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пример

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

P.S.Весь compare.py сценарий.

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

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

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

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

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

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

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

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

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

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

$$ 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.getbbox(). Он скажет вам минимальный прямоугольник, который содержит все изменения между вашими двумя изображениями.

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

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

Используя подобный синтаксису синтаксис,

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

Большинство ответов не будут касаться уровней освещения.

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

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

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.

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

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).

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

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

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

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

<Ол>
  • Упростить изображения (возможно, с пороговым значением?)
  • Примените сопоставление с шаблоном и проверьте 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-квадрата (то есть доля изменчивости на одном изображении, объясняемая изменением на следующем) (5) обычно в моем приложении, если r-квадрат & Lt; 0.9, я говорю, что два изображения разные, и что-то произошло между ними.

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

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

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

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

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

      

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

         

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

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

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

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

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

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

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

    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()
    
    • выходной сигнал:

      Ложь
      Верно
      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

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

    Существует множество метрик для оценки того, как выглядят два изображения / как они выглядят.

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

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

    Классические подходы:

    Предиктор видимых различий: алгоритм оценки точности изображения ( https://www.spiedigitallibrary.org/conference-proceedings-of-spie/1666/0000 /Visible-differences-predictor--an-algorithm-for-the-assessment-of/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 (оценка качества изображения: от видимости ошибок до структурного сходства), а его издержки также невелики, как сообщается в другой статье & «Оценка качества изображения на основе градиентного сходства» Quot; ().

    Есть много других подходов. Взгляните на Google Scholar и найдите что-то вроде & Quot; визуальное отличие & Quot ;, & Quot; оценка качества изображения & Quot; и т. Д., Если вы заинтересованы / действительно заботитесь об искусстве .

    scroll top