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

У меня есть два разных образа:

в 100px с введите здесь описание изображения или 400px введите здесь описание изображения

и

шириной 100 пикселей введите здесь описание изображения или 400px введите здесь описание изображения

Как видите, с человеческой точки зрения они явно «одинаковы». Теперь я хочу программно определить, что они одинаковы. Я использовал магию изображений через рубиновый драгоценный камень под названием rmagick вот так:

img1 = Magick::Image.from_blob(File.read("image_1.jpeg")).first
img2 = Magick::Image.from_blob(File.read("image_2.jpeg")).first

if img1.difference(img2).first < 4000.0 # I have found this to be a good threshold, but does not work for cropped images
  puts "they are the same!!!"
end

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

Есть ли способ сделать это для изображений с разной обрезкой? Меня интересует решение, в котором я могу сказать что-то вроде: одно изображение содержится внутри другого и охватывает где-то, например. 90% этого.

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


person Niels Kristian    schedule 31.01.2020    source источник
comment
Не уверен насчет RMagick, но инструмент командной строки ImageMagick compare имеет -subimage-search переключатель.   -  person Stefan    schedule 31.01.2020
comment
Вот интересно, как бы выглядела такая команда?   -  person Niels Kristian    schedule 31.01.2020
comment
Спасибо, это отличная информация. Однако я не могу понять, как это сделать из рубина...   -  person Niels Kristian    schedule 31.01.2020
comment
Изображения такого низкого качества? Если нет, пожалуйста, поделитесь увеличенной версией изображений с более высоким качеством.   -  person MH304    schedule 04.02.2020
comment
Готово, смотрите обновленные изображения...   -  person Niels Kristian    schedule 04.02.2020


Ответы (5)


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

Учитывая ваши два предоставленных изображения, вот попытка сопоставить функции, используя FLANN средство сопоставления функций. Чтобы определить, являются ли два изображения одинаковыми, мы можем основывать их на некотором заранее определенном пороге, который отслеживает количество совпадений, прошедших тест соотношения, описанный в Отличительные особенности изображения на основе масштабно-инвариантных ключевых точек Дэвид Г. Лоу. Простое объяснение теста состоит в том, что тест отношения проверяет, являются ли совпадения неоднозначными и должны быть удалены, вы можете рассматривать его как метод удаления выбросов. Мы можем подсчитать количество совпадений, прошедших этот тест, чтобы определить, совпадают ли два изображения. Вот результаты сопоставления функций:

Matches: 42

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


Я реализовал это на Python, я не очень хорошо знаком с Rails. Надеюсь это поможет. Удачи!

Код

import numpy as np
import cv2

# Load images
image1 = cv2.imread('1.jpg', 0)
image2 = cv2.imread('2.jpg', 0)

# Create the sift object
sift = cv2.xfeatures2d.SIFT_create(700)

# Find keypoints and descriptors directly
kp1, des1 = sift.detectAndCompute(image2, None)
kp2, des2 = sift.detectAndCompute(image1, None)

# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)   # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0,0] for i in range(len(matches))]

count = 0
# Ratio test as per Lowe's paper (0.7)
# Modify to change threshold 
for i,(m,n) in enumerate(matches):
    if m.distance < 0.15*n.distance:
        count += 1
        matchesMask[i]=[1,0]

# Draw lines
draw_params = dict(matchColor = (0,255,0),
                   # singlePointColor = (255,0,0),
                   matchesMask = matchesMask,
                   flags = 0)

# Display the matches
result = cv2.drawMatchesKnn(image2,kp1,image1,kp2,matches,None,**draw_params)
print('Matches:', count)
cv2.imshow('result', result)
cv2.waitKey()
person nathancy    schedule 04.02.2020
comment
Супер интересный подход, я попробую и вернусь... - person Niels Kristian; 04.02.2020
comment
@nathancy В вашем примере зеленые точки совпадают, а синие нет? Похоже, слишком много непарных точек? - person Draco Ater; 04.02.2020
comment
@DracoAter хороший вопрос, синие точки представляют все совпадения, в то время как мы рисуем только хорошие совпадения, которые проходят тест на соотношение, зеленым цветом. Если вы не используете тест соотношения, то будут нарисованы все точки, но мы фильтруем с помощью теста соотношения, чтобы получить лучшие совпадения. Таким образом, OP может использовать этот тест в качестве порога, чтобы сохранить только наиболее подходящие функции. Таким образом, все синие точки — это функции, которые были обнаружены SIFT, но мы фильтруем, чтобы сохранить хорошие, которые нарисованы зеленым цветом. - person nathancy; 05.02.2020
comment
Спасибо. конкуренция была жесткой на ответы, много замечательных :-) - person Niels Kristian; 10.02.2020

Поскольку ImageMagick — очень старый, продвинутый и многофункциональный инструмент, было бы сложно создать интерфейс, охватывающий большинство функций. Каким бы замечательным он ни был, rmagick (как и многие попытки, предпринятые Python) не приближается к охвату всех функций.

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

require 'open3'

def check_subimage(large, small)
    stdin, stdout, stderr, wait_thr = Open3.popen3("magick compare -subimage-search -metric RMSE #{large} #{small} temp.jpg")
    result = stderr.gets
    stderr.close
    stdout.close
    return result.split[1][1..-2].to_f < 0.2
end

if check_subimage('a.jpg', 'b.jpg')
    puts "b is a crop of a"
else
    puts "b is not a crop of a"
end

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

Команда использует магическое сравнение, чтобы проверить, является ли второе изображение (small) частью первого (large). Эта функция не проверяет, что маленький строго меньше большого (как по высоте, так и по ширине). Число, которое я поставил для сходства, составляет 0,2 (ошибка 20%), а значение для предоставленных вами изображений составляет около 0,15. Вы можете настроить это! Я обнаружил, что изображения, которые являются строгим подмножеством, получают менее 0,01.

  • Если вам нужно меньше ошибок (меньшие числа) в случаях, когда у вас есть перекрытие 90%, но второе изображение имеет некоторые дополнительные элементы, которых нет в первом, вы можете запустить его один раз, а затем обрезать первое большое изображение до того места, где содержится субизображение. , затем снова запустите его с обрезанным изображением в качестве «маленького» и исходным «маленьким» изображением в качестве большого.
  • Если вам действительно нужен хороший объектно-ориентированный интерфейс в Ruby, rmagick использует MagicCore API. Эта (ссылка на документацию) команда, вероятно, является тем, что вы хотите использовать для ее реализации, и вы можете открыть PR для rmagick или упаковать cext самостоятельно.
  • Использование open3 запустит поток (см. документацию). Закрытие stderr и stdout не является «необходимым», но вы должны это сделать.
  • Изображение «temp», которое является третьим аргументом, указывает файл для вывода анализа. Бегло взглянув, я не смог найти способ не требовать его, но он просто перезаписывается автоматически, и его можно было бы сохранить для отладки. Для вашего примера это будет выглядеть так;

введите здесь описание изображения

  • Полный вывод имеет формат 10092,6 (0,154003) @ 0,31. Первое число — среднеквадратичное значение из 655 535, второе (которое я использую) — нормализованный процент. Последние два числа представляют собой местоположение исходного изображения, с которого начинается маленькое изображение.
  • Поскольку нет объективного источника достоверной информации о том, насколько «похожи» изображения, я выбрал RMSE (см. дополнительные параметры метрики здесь). Это довольно распространенная мера различий между значениями. Абсолютный счетчик ошибок (AE) может показаться хорошей идеей, однако кажется, что некоторые программы для обрезки не полностью сохраняют пиксели, поэтому вам, возможно, придется настроить размытие, и это не нормализованное значение, поэтому вам придется сравнить количество ошибок. с размером изображения и еще много чего.
person Carol Chen    schedule 06.02.2020
comment
Это действительно отличная информация, Кэрол. Спасибо - person Niels Kristian; 06.02.2020
comment
Любопытно узнать, как это работает для других ваших случаев! - person Carol Chen; 06.02.2020
comment
Спасибо за супер отличный ответ. Если бы я мог, я дал бы тебе награду в 100 пенсов и за это тоже :-) - person Niels Kristian; 10.02.2020

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

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

person Raviteja Narra    schedule 03.02.2020
comment
Спасибо за совет посмотрю. - person Niels Kristian; 04.02.2020
comment
Это не очень полезный ответ, поскольку он не демонстрирует, как достичь цели. Это эквивалент Google этого термина, и сами разберитесь. - person anothermh; 09.02.2020
comment
Гистограмма — это одна из первых вещей, которую люди изучают при обработке изображений. Если кому-то приходится гуглить, то я глубоко извиняюсь. - person Raviteja Narra; 09.02.2020

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

В opencv с использованием метода TM_CCOEFF_NORMED дает оценку от 0 до 1. Если оценка равна 1, это означает, что изображение шаблона является точной частью (Rect) исходного изображения. , но если у вас есть небольшое изменение в освещении или перспективе между двумя изображениями, оценка будет ниже 1.

Теперь, рассматривая порог для оценки сходства, вы можете узнать, одинаковы они или нет. Этот порог можно получить методом проб и ошибок на нескольких образцах изображений. Я попробовал ваши изображения и получил оценку 0,823863. Вот код (opencv C++) и общая область между двумя изображениями, полученная путем сопоставления:

введите здесь описание изображения

Mat im2 = imread("E:/1/1.jpg", 1);
//Mat im2;// = imread("E:/1/1.jpg", 1);
Mat im1 = imread("E:/1/2.jpg", 1);

//im1(Rect(0, 0, im1.cols - 5, im1.rows - 5)).copyTo(im2);

int result_cols = im1.cols - im2.cols + 1;
int result_rows = im1.rows - im2.rows + 1;

Mat result = Mat::zeros(result_rows, result_cols, CV_32FC1);

matchTemplate(im1, im2, result, TM_CCOEFF_NORMED);

double minVal; double maxVal;
Point minLoc; Point maxLoc;
Point matchLoc;

minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());

cout << minVal << " " << maxVal << " " << minLoc << " " << maxLoc << "\n";
matchLoc = maxLoc;

rectangle(im1, matchLoc, Point(matchLoc.x + im2.cols, matchLoc.y + im2.rows), Scalar::all(0), 2, 8, 0);
rectangle(result, matchLoc, Point(matchLoc.x + im2.cols, matchLoc.y + im2.rows), Scalar::all(0), 2, 8, 0);

imshow("1", im1);
imshow("2", result);
waitKey(0);
person MH304    schedule 07.02.2020
comment
Спасибо за супер отличный ответ. Если бы я мог, я дал бы тебе награду в 100 пенсов и за это тоже :-) - person Niels Kristian; 10.02.2020

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

person An RMagick User    schedule 01.02.2020