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

11

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

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

а также

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

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

Нильс Кристиан
источник
2
Не уверен насчет RMagick, но compareинструмент командной строки ImageMagick имеет -subimage-searchпереключатель.
Стефан
Интересно, как будет выглядеть такая команда?
Нильс Кристиан
2
Никогда не использовал его сам, может быть, это помогает: stackoverflow.com/q/29062811/477037
Стефан
Спасибо, это отличная информация. Я не могу понять, как сделать это из рубина, однако ...
Нильс Кристиан
1
Изображения низкого качества? Если нет, пожалуйста, поделитесь увеличенной версией изображений с большим качеством.
MH304

Ответы:

6

Вы можете взглянуть на соответствие функций. Идея состоит в том, чтобы найти функции на двух изображениях и сопоставить их. Этот метод обычно используется для поиска шаблона (скажем, логотипа) в другом изображении. По сути, особенность может быть описана как вещи, которые люди могут найти интересными на изображении, такие как углы или открытые пространства. Существует много типов методов обнаружения признаков, однако я рекомендую использовать масштабно-инвариантное преобразование признаков (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()
nathancy
источник
2
Супер интересный подход, я попробую и вернусь ...
Нильс Кристиан
PS. Я обновил изображения в большем масштабе
Нильс Кристиан
1
@nathancy Это так, что на вашем примере зеленые точки совпадают, а синие нет? Похоже, есть слишком много непревзойденных точек?
Драко Атер
2
@DracoAter Хороший вопрос, синие точки представляют все совпадения, в то время как мы рисуем только «хорошие совпадения», которые проходят тест отношения в зеленом цвете. Если вы не используете тест отношения, тогда все точки будут нарисованы, но мы отфильтруем, используя тест отношения, чтобы получить «лучшие» совпадения. Таким образом, OP может использовать этот тест в качестве порогового значения, чтобы сохранить только наиболее подходящие функции. Таким образом, все синие точки - это функции, которые обнаружил SIFT, но мы фильтруем их, чтобы сохранить те, которые нарисованы зеленым цветом
nathancy
Спасибо. Конкурс был трудным для ответов, многие великие :-)
Нильс Кристиан
4

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

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

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

  • Полный вывод в формате 10092,6 (0,154003) @ 0,31. Первое число - это значение rmse из 655535, второе (которое я использую) - нормализованный процент. Последние два числа представляют местоположение исходного изображения, с которого начинается маленькое изображение.
  • Поскольку не существует объективного источника правды о том, насколько «похожи» изображения, я выбрал RMSE (см. Дополнительные параметры метрики здесь ). Это довольно распространенная мера различий между ценностями. Абсолютный счетчик ошибок (AE) может показаться хорошей идеей, однако кажется, что некоторые программы кадрирования не совсем сохраняют пиксели, поэтому вам, возможно, придется настроить фазз, а это не нормализованное значение, поэтому вам придется сравнивать счетчик ошибок с размером изображения и еще много чего.
Кэрол Чен
источник
1
Это действительно отличная информация, Кэрол. Спасибо
Нильс Кристиан
Любопытно узнать, как это работает для других ваших дел!
Кэрол Чен
1
Спасибо за супер отличный ответ. Если бы я мог, я бы дал вам 100p награду за это тоже :-)
Niels Kristian
3

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

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

Равитея Нарра
источник
Спасибо за совет, я посмотрю на него.
Нильс Кристиан
Это не очень полезный ответ, поскольку он не демонстрирует, как достичь цели. Это эквивалент «Google этот термин и выяснить это самостоятельно».
раз
Гистограмма - одна из первых вещей, которую люди изучают при обработке изображений. Если кому-то придется гуглить, то я глубоко извиняюсь.
Равитея Нарра
3

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

В 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);
MH304
источник
Спасибо за супер отличный ответ. Если бы я мог, я бы дал вам 100p награду за это тоже :-)
Niels Kristian
2

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

Пользователь RMagick
источник
Спасибо, однако я не могу заставить это работать - не так ли?
Нильс Кристиан