Wie erkenne ich, dass zwei Bilder „gleich“ sind, auch wenn eines ein geringfügig unterschiedliches Zuschneiden / Verhältnis aufweist?


11

Ich habe zwei verschiedene Bilder:

in 100px mit Geben Sie hier die Bildbeschreibung einoder 400pxGeben Sie hier die Bildbeschreibung ein

und

in 100px Breite Geben Sie hier die Bildbeschreibung einoder 400pxGeben Sie hier die Bildbeschreibung ein

Wie Sie sehen können, sind die beiden aus menschlicher Sicht eindeutig "gleich". Jetzt möchte ich programmgesteuert feststellen, dass sie gleich sind. Ich habe Bildmagie über den Rubinstein verwendet, der rmagickso heißt:

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

Dies funktioniert zwar gut für Bilder mit demselben Verhältnis / Zuschneiden, ist jedoch nicht ideal, wenn sie geringfügig unterschiedlich beschnitten sind und auf die gleiche Breite angepasst wurden.

Gibt es eine Möglichkeit, dies für Bilder mit unterschiedlichem Zuschneiden zu tun? Ich interessiere mich für eine Lösung, bei der ich etwas sagen kann wie: Ein Bild ist im anderen enthalten und deckt etwa 90% davon ab.

PS. Ich kann die Bilder in höherer Auflösung bekommen, wenn das hilft (zB das Doppelte)


2
Ich bin mir bei RMagick nicht sicher, aber das compareBefehlszeilentool von ImageMagick verfügt über einen -subimage-searchSchalter.
Stefan

Das ist interessant, wie würde ein solcher Befehl aussehen?
Niels Kristian

2
Ich habe es selbst nie benutzt, vielleicht hilft das: stackoverflow.com/q/29062811/477037
Stefan

Danke, das ist eine großartige Information. Ich kann jedoch nicht herausfinden, wie man das mit Ruby macht ...
Niels Kristian

1
Sind die Bilder von geringer Qualität? Wenn nein, teilen Sie bitte eine größere Version der Bilder mit mehr Qualität.
MH304

Antworten:


6

Vielleicht möchten Sie einen Blick auf die Funktionsübereinstimmung werfen. Die Idee ist, Features in zwei Bildern zu finden und sie abzugleichen. Diese Methode wird häufig verwendet, um eine Vorlage (z. B. ein Logo) in einem anderen Bild zu finden. Ein Merkmal kann im Wesentlichen als Dinge beschrieben werden, die Menschen in einem Bild interessant finden würden, wie z. B. Ecken oder offene Räume. Es gibt viele Arten von Merkmalerkennungstechniken. Ich empfehle jedoch, eine skaleninvariante Merkmalstransformation (SIFT) als Merkmalerkennungsalgorithmus zu verwenden. SIFT ist unveränderlich gegenüber Bildübersetzung, Skalierung, Drehung, teilweise unveränderlich gegenüber Beleuchtungsänderungen und robust gegenüber lokaler geometrischer Verzerrung. Dies scheint Ihrer Spezifikation zu entsprechen, bei der die Bilder leicht unterschiedliche Verhältnisse haben können.

In Anbetracht der beiden bereitgestellten Bilder wird hier versucht, die Funktionen mithilfe des FLANN-Feature-Matchers abzugleichen . Um festzustellen, ob die beiden Bilder gleich sind, können wir sie auf einen vorgegebenen Schwellenwert stützen, der die Anzahl der Übereinstimmungen verfolgt, die den Verhältnis-Test bestehen, der in Unterscheidungsbildmerkmale aus skalierungsinvarianten Schlüsselpunkten von David G. Lowe beschrieben ist . Eine einfache Erklärung für den Test ist, dass der Verhältnis-Test prüft, ob Übereinstimmungen nicht eindeutig sind und entfernt werden sollten. Sie können ihn als Ausreißer-Entfernungstechnik behandeln. Wir können die Anzahl der Übereinstimmungen zählen, die diesen Test bestehen, um festzustellen, ob die beiden Bilder gleich sind. Hier sind die Ergebnisse der Funktionsübereinstimmung:

Matches: 42

Die Punkte stellen alle erkannten Übereinstimmungen dar, während die grünen Linien die "guten Übereinstimmungen" darstellen, die den Verhältnis-Test bestehen. Wenn Sie den Verhältnis-Test nicht verwenden, werden alle Punkte gezogen. Auf diese Weise können Sie diesen Filter als Schwellenwert verwenden, um nur die am besten übereinstimmenden Funktionen beizubehalten.


Ich habe es in Python implementiert, ich bin nicht sehr vertraut mit Rails. Hoffe das hilft, viel Glück!

Code

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

2
Super interessanter Ansatz, ich werde es versuchen und zurückkommen ...
Niels Kristian

PS. Ich habe die Bilder in größerem Maßstab aktualisiert
Niels Kristian

1
@nathancy Ist es so, dass in Ihrem Beispiel grüne Punkte übereinstimmen, blaue jedoch nicht? Sieht aus wie es zu viele unübertroffene Punkte gibt?
Draco Ater

2
@DracoAter gute Frage, die blauen Punkte repräsentieren alle Übereinstimmungen, während wir nur "gute Übereinstimmungen" zeichnen, die den Verhältnis-Test in Grün bestehen. Wenn Sie den Verhältnis-Test nicht verwenden, werden alle Punkte gezogen, aber wir filtern mit dem Verhältnis-Test, um die "besseren" Übereinstimmungen zu zeichnen. Auf diese Weise kann OP diesen Test als Schwellenwert verwenden, um nur die am besten übereinstimmenden Funktionen beizubehalten. Alle blauen Punkte sind also die Merkmale, die SIFT gefunden hat, aber wir filtern, um die guten zu behalten, die in Grün gezeichnet sind
nathancy

Vielen Dank. Der Wettbewerb war hart für die Antworten, viele großartige :-)
Niels Kristian

4

Da ImageMagick sehr alt, fortschrittlich und mit vielen Funktionen ausgestattet ist, ist es schwierig, eine Benutzeroberfläche zu erstellen, die die meisten Funktionen abdeckt. So großartig es auch ist, rmagick deckt nicht alle Funktionen ab (und auch nicht die vielen Versuche, die Python unternommen hat).

Ich stelle mir für viele Anwendungsfälle vor, dass es sicher genug und viel einfacher ist, einfach eine Befehlszeilenmethode auszuführen und daraus zu lesen. In Rubin sieht das so aus;

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

Ich werde wichtige Dinge behandeln und dann über zusätzliche Notizen sprechen.

Der Befehl verwendet magick compare, um zu überprüfen, ob das zweite Bild ( small) ein Teilbild des ersten ( large) ist. Diese Funktion überprüft nicht, ob klein streng kleiner als groß ist (sowohl Höhe als auch Breite). Die Zahl, die ich für die Ähnlichkeit angegeben habe, ist 0,2 (20% Fehler), und der Wert für die von Ihnen bereitgestellten Bilder beträgt ungefähr 0,15. Vielleicht möchten Sie dies fein abstimmen! Ich finde, dass Bilder, die eine strenge Teilmenge sind, weniger als 0,01 erhalten.

  • Wenn Sie weniger Fehler (kleinere Zahlen) in Fällen wünschen, in denen Sie 90% Überlappung haben, das zweite Bild jedoch einige zusätzliche Elemente enthält, die das erste nicht enthält, können Sie es einmal ausführen und dann das erste große Bild dort zuschneiden, wo das Teilbild enthalten ist Führen Sie es dann erneut aus, wobei das zugeschnittene Bild das "kleine" und das ursprüngliche "kleine" Bild das große ist.
  • Wenn Sie wirklich eine schöne objektorientierte Oberfläche in Ruby haben möchten, verwendet rmagick die MagicCore-API. Dieser Befehl (Link zu Dokumenten) ist wahrscheinlich das, was Sie verwenden möchten, um ihn zu implementieren, und Sie können einen PR öffnen, um den Cext selbst zu rmagieren oder zu verpacken.
  • Mit open3 wird ein Thread gestartet ( siehe Dokumentation ). Schließen stderrund stdoutist nicht "notwendig", aber du sollst.
  • Das "temporäre" Bild, das das dritte Argument ist, gibt eine Datei an, in die eine Analyse ausgegeben werden soll. Mit einem kurzen Blick konnte ich keinen Weg finden, es nicht zu benötigen, aber es wird einfach automatisch überschrieben und könnte gut zum Debuggen gespeichert werden. Für Ihr Beispiel würde es so aussehen;

Geben Sie hier die Bildbeschreibung ein

  • Die vollständige Ausgabe erfolgt im Format 10092,6 (0,154003) @ 0,31. Die erste Zahl ist der rmse-Wert von 655535, die zweite (die ich verwende) ist ein normalisierter Prozentsatz. Die letzten beiden Zahlen geben die Position des Originalbilds an, von dem aus das kleine Bild beginnt.
  • Da es keine objektive Wahrheitsquelle für "ähnliche" Bilder gibt, habe ich RMSE ausgewählt (weitere Metrikoptionen finden Sie hier ). Es ist ein ziemlich häufiges Maß für Unterschiede zwischen Werten. Eine absolute Fehleranzahl (Absolute Error Count, AE) scheint eine gute Idee zu sein. Es scheint jedoch, dass einige Beschneidungsprogramme die Pixel nicht perfekt erhalten, sodass Sie möglicherweise den Fuzz anpassen müssen und es sich nicht um einen normalisierten Wert handelt. Dann müssten Sie die Fehleranzahl vergleichen mit der Größe des Bildes und so weiter.

1
Das sind einige wirklich gute Informationen, Carol. Danke
Niels Kristian

Neugierig zu wissen, wie das für Ihre anderen Fälle funktioniert!
Carol Chen

1
Danke für die super tolle Antwort. Wenn ich könnte, hätte ich dir auch 100p Belohnung dafür gegeben :-)
Niels Kristian

3

Holen Sie sich das Histogramm beider Bilder und vergleichen Sie sie. Dies würde für Crop und Zoom sehr gut funktionieren, es sei denn, es gibt aufgrund dieser zu drastische Änderungen.

Dies ist besser als der derzeitige Ansatz, bei dem Sie die Bilder direkt subtrahieren. Dieser Ansatz hat jedoch noch wenige.


Vielen Dank für den Rat, den ich mir ansehen werde.
Niels Kristian

Dies ist keine sehr nützliche Antwort, da sie nicht zeigt, wie das Ziel erreicht werden kann. Es ist das Äquivalent von "Google diesen Begriff und finde es selbst heraus."
weiterer

Das Histogramm ist eines der ersten Dinge, die Menschen in der Bildverarbeitung lernen. Wenn einige es googeln müssen, dann entschuldige ich mich zutiefst.
Raviteja Narra

3

Normalerweise hat der Vorlagenabgleich in diesen Situationen ein gutes Ergebnis. Der Vorlagenabgleich ist eine Technik zum Auffinden von Bereichen eines Bildes, die mit einem Vorlagenbild (zweites Bild) übereinstimmen (ähnlich sind). Dieser Algorithmus gibt eine Punktzahl für die beste Position im Quellbild (die zweite).

In opencv mit der Methode TM_CCOEFF_NORMED wird die Punktzahl zwischen 0 und 1 angegeben. Wenn die Punktzahl 1 ist, bedeutet dies, dass das Vorlagenbild genau ein Teil (Rect) des Quellbilds ist, wenn Sie jedoch eine geringfügige Änderung der Beleuchtung oder Perspektive dazwischen haben Bei den beiden Bildern wäre die Punktzahl niedriger als 1.

Wenn Sie nun einen Schwellenwert für die Ähnlichkeitsbewertung berücksichtigen, können Sie herausfinden, ob sie gleich sind oder nicht. Dieser Schwellenwert kann durch Ausprobieren einiger Beispielbilder ermittelt werden. Ich habe deine Bilder ausprobiert und die Punktzahl 0,823863 erhalten . Hier ist der Code (opencv C ++) und der gemeinsame Bereich zwischen den beiden Bildern, der durch das Matching erhalten wird:

Geben Sie hier die Bildbeschreibung ein

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

Danke für die super tolle Antwort. Wenn ich könnte, hätte ich dir auch 100p Belohnung dafür gegeben :-)
Niels Kristian

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.