Jak wykryć, że dwa obrazy są „takie same”, nawet jeśli jeden ma nieco inny współczynnik kadrowania / proporcji?

11

Mam dwa różne obrazy:

w 100px z wprowadź opis zdjęcia tutajlub 400pxwprowadź opis zdjęcia tutaj

i

o szerokości 100 pikseli wprowadź opis zdjęcia tutajlub 400 pikseliwprowadź opis zdjęcia tutaj

Jak widać, oba są wyraźnie „takie same” z ludzkiego punktu widzenia. Teraz chcę programowo wykryć, że są takie same. Używam magii obrazu za pomocą rubinowego klejnotu o nazwie rmagicktak:

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

Chociaż działa to dobrze w przypadku obrazów o tym samym współczynniku / kadrowaniu, nie jest idealne, gdy mają nieco inne kadrowanie i zostało zmienione do tej samej szerokości.

Czy można to zrobić w przypadku zdjęć z innym kadrowaniem? Interesuje mnie rozwiązanie, w którym mogę powiedzieć coś takiego: jeden obraz jest zawarty w drugim i obejmuje około 90% tego.

PS. Mogę uzyskać obrazy w wyższej rozdzielczości, jeśli to pomoże (np. Podwójne)

Niels Kristian
źródło
2
Nie jestem pewien co do RMagick, ale comparenarzędzie wiersza poleceń ImageMagick ma -subimage-searchprzełącznik.
Stefan
To ciekawe, jak wyglądałoby takie polecenie?
Niels Kristian
2
Nigdy go nie użyłem, może to pomaga: stackoverflow.com/q/29062811/477037
Stefan
Dzięki, to świetna informacja. Nie mogę jednak wymyślić, jak to zrobić z ruby ​​...
Niels Kristian
1
Czy obrazy są niskiej jakości? Jeśli nie, udostępnij większą wersję zdjęć o wyższej jakości.
MH304

Odpowiedzi:

6

Możesz rzucić okiem na dopasowanie funkcji. Chodzi o to, aby znaleźć funkcje na dwóch obrazach i dopasować je. Ta metoda jest powszechnie stosowana do znajdowania szablonu (powiedzmy logo) na innym obrazie. Cechę można w istocie opisać jako rzeczy, które ludzie mogą uznać za interesujące na obrazie, takie jak narożniki lub otwarte przestrzenie. Istnieje wiele rodzajów technik wykrywania cech, jednak moim zaleceniem jest użycie niezmiennej transformacji cech (SIFT) jako algorytmu wykrywania cech. SIFT jest niezmienny dla translacji obrazu, skalowania, obrotu, częściowo niezmienny dla zmian oświetlenia i odporny na lokalne zniekształcenie geometryczne. Wygląda na to, że pasuje do specyfikacji, w której obrazy mogą mieć nieco inne proporcje.

Biorąc pod uwagę dwa dostarczone obrazy, oto próba dopasowania funkcji za pomocą dopasowywania funkcji FLANN . Aby ustalić, czy dwa obrazy są takie same, możemy oprzeć je na pewnym z góry określonym progu, który śledzi liczbę dopasowań, które przejdą test współczynnika opisany w Wyróżniających cechach obrazu z Keypoints Scale-Invariant autorstwa Davida G. Lowe'a . Prostym wyjaśnieniem testu jest to, że test proporcji sprawdza, czy dopasowania są niejednoznaczne i powinny zostać usunięte, można to potraktować jako technikę usuwania wartości odstających. Możemy policzyć liczbę dopasowań, które przejdą ten test, aby ustalić, czy dwa obrazy są takie same. Oto wyniki dopasowania funkcji:

Matches: 42

Kropki oznaczają wszystkie wykryte dopasowania, podczas gdy zielone linie oznaczają „dobre dopasowania”, które pomyślnie przeszły test stosunku. Jeśli nie użyjesz testu proporcji, wszystkie punkty zostaną narysowane. W ten sposób możesz użyć tego filtra jako progu, aby zachować tylko najlepiej dopasowane funkcje.


Zaimplementowałem to w Pythonie, nie znam się zbyt dobrze na Railsach. Mam nadzięję, że to pomogło, powodzenia!

Kod

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
źródło
2
Super ciekawe podejście, spróbuję i wrócę ...
Niels Kristian
PS. Zaktualizowałem obrazy w większej skali
Niels Kristian
1
@nathancy Czy na twoim przykładzie zielone kropki pasują, ale niebieskie nie? Wygląda na to, że jest zbyt wiele niedopasowanych kropek?
Draco Ater
2
@Draco Po dobrym pytaniu niebieskie kropki reprezentują wszystkie mecze, a my rysujemy tylko „dobre mecze”, które pozytywnie przejdą test proporcji na zielono. Jeśli nie użyjesz testu proporcji, wszystkie punkty zostaną narysowane, ale filtrujemy za pomocą testu proporcji, aby narysować „lepsze” dopasowania. W ten sposób OP może wykorzystać ten test jako próg, aby zachować tylko najlepiej dopasowane funkcje. Więc wszystkie niebieskie kropki to funkcje znalezione przez SIFT, ale filtrujemy, aby zachować dobre, które są narysowane na zielono
nathancy
Dzięki. konkurencja była trudna w odpowiedziach, wiele świetnych :-)
Niels Kristian
4

Ponieważ ImageMagick jest bardzo starym, zaawansowanym i wielofunkcyjnym narzędziem, trudno byłoby zbudować interfejs, który obejmowałby większość funkcji. Mimo, że jest świetny, rmagick nie zbliża się do objęcia wszystkich funkcji (podobnie jak wiele prób, jakie podjął Python).

Wyobrażam sobie, że w wielu przypadkach użycia będzie wystarczająco bezpieczne i o wiele łatwiejsze po prostu wykonanie metody wiersza poleceń i odczytanie z niej. W rubinie będzie to wyglądać tak;

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

Omówię ważne rzeczy, a następnie omówię dodatkowe notatki.

Polecenie używa porównania magii, aby sprawdzić, czy drugi obraz ( small) jest podobrazem pierwszego (large ). Ta funkcja nie sprawdza, czy małe jest ściśle mniejsze niż duże (zarówno wysokość, jak i szerokość). Liczba podana dla podobieństwa wynosi 0,2 (błąd 20%), a wartość podanych zdjęć wynosi około 0,15. Możesz to dostroić! Uważam, że obrazy, które są ścisłym podzbiorem, mają mniej niż 0,01.

  • Jeśli chcesz mniej błędów (mniejsze liczby) w przypadkach, w których nakładasz się w 90%, ale na drugim obrazie jest kilka dodatkowych rzeczy, których nie ma na pierwszym, możesz go uruchomić raz, a następnie przyciąć pierwszy duży obraz, w którym znajduje się podobraz , uruchom go ponownie z przyciętym obrazem jako „małym” i oryginalnym „małym” obrazem jako dużym.
  • Jeśli naprawdę chciałeś mieć ładny interfejs obiektowy w Ruby, rmagick używa MagicCore API. To (link do dokumentów) jest prawdopodobnie tym, czego chcesz użyć, aby go zaimplementować, i możesz sam otworzyć program rmagick lub spakować tekst.
  • Korzystanie z open3 uruchomi wątek ( patrz dokumenty ). Zamykanie stderri stdoutnie jest „konieczne”, ale powinieneś.
  • Obraz „temp”, który jest trzecim argumentem, określa plik, na którym zostanie wyprowadzona analiza. Po szybkim spojrzeniu nie mogłem znaleźć sposobu, aby tego nie wymagać, ale po prostu automatycznie się nadpisuje i warto zapisać go na debugowanie. Na przykład wyglądałoby to tak;

wprowadź opis zdjęcia tutaj

  • Pełny wynik ma format 10092,6 (0,154003) @ 0,31. Pierwsza liczba to wartość rmse z 655535, druga (której używam) to znormalizowany procent. Dwie ostatnie cyfry oznaczają lokalizację oryginalnego obrazu, od którego zaczyna się mały obraz.
  • Ponieważ nie ma obiektywnego źródła prawdy na temat tego, jak „podobne” są obrazy, wybrałem RMSE (zobacz więcej opcji metrycznych tutaj ). Jest to dość powszechna miara różnic między wartościami. Licznik błędów bezwzględnych (AE) może wydawać się dobrym pomysłem, jednak wydaje się, że niektóre oprogramowanie do przycinania nie zachowuje dokładnie pikseli, więc może być konieczne dostosowanie fuzz i nie jest to znormalizowana wartość, więc musisz porównać liczbę błędów z rozmiarem obrazu i tym podobne.
Carol Chen
źródło
1
To są naprawdę świetne informacje, Carol. Dzięki
Niels Kristian
Ciekawe, jak to działa w innych przypadkach!
Carol Chen,
1
Dzięki za super świetną odpowiedź. Gdybym mógł, dałem ci za to również nagrodę w wysokości 100 pensów :-)
Niels Kristian
3

Pobierz histogram obu zdjęć i porównaj je. Działałoby to bardzo dobrze w przypadku przycinania i powiększania, chyba że nastąpiła z tego powodu zbyt drastyczna zmiana.

Jest to lepsze niż obecne podejście, w którym obrazy są odejmowane bezpośrednio. Ale to podejście wciąż ma niewiele.

Raviteja Narra
źródło
Dzięki za radę, przyjrzę się jej.
Niels Kristian
To nie jest bardzo przydatna odpowiedź, ponieważ nie pokazuje, jak osiągnąć cel. Jest to odpowiednik „Google tego terminu i sam go wymyśl”.
anothermh
Histogram to jedna z pierwszych rzeczy, których ludzie uczą się w przetwarzaniu obrazu. Jeśli niektórzy muszą google, to przepraszam.
Raviteja Narra
3

Zwykle dopasowanie szablonów daje dobry wynik w takich sytuacjach. Dopasowywanie szablonów to technika znajdowania obszarów obrazu, które pasują (są podobne) do obrazu szablonu (drugi obraz). Ten algorytm daje ocenę za najlepszą pozycję zmodowaną na obrazie źródłowym (drugi).

W opencv przy użyciu TM_CCOEFF_NORMED metody daje wynik od 0 do 1. Jeśli wynik wynosi 1, oznacza to, że obraz szablonu jest dokładnie częścią (Rect) obrazu źródłowego, ale jeśli masz niewielką zmianę rozjaśnienia lub perspektywy między dwa obrazki, wynik byłby niższy niż 1.

Teraz Rozważając próg oceny podobieństwa, możesz dowiedzieć się, czy są one takie same, czy nie. Ten próg można uzyskać za pomocą prób i błędów na kilku przykładowych obrazach. Próbowałem twoich zdjęć i uzyskałem wynik 0,823863 . Oto kod (opencv C ++) i wspólny obszar między dwoma obrazami, uzyskany przez dopasowanie:

wprowadź opis zdjęcia tutaj

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
źródło
Dzięki za super świetną odpowiedź. Gdybym mógł, dałem ci za to również nagrodę w wysokości 100 pensów :-)
Niels Kristian
2

Rozważ metodę find_similar_region . Użyj mniejszego z dwóch obrazów jako obrazu docelowego. Wypróbuj różne wartości atrybutów fuzz na obrazie i na obrazie docelowym.

Użytkownik RMagick
źródło
Dzięki, ale nie mogę sprawić, żeby działało - czy możesz?
Niels Kristian