Schnelleres Subimage-Matching gesucht

Ich spiele aktuell damit, Informationen aus Bildern zu parsen.
Problem: Ich habe einen Satz unterschiedlicher Icons, bei denen der Hintergrund ausgenommen ist.
Diese Icons kommen irgendwo in einem größerem Bild vor (oder auch nicht). Teilweise können die Icons auch verdeckt sein.

Was mich interessiert ist das beste Matching der Bilder (hat z.B. das icon 30 Pixel, davon sind 10 nicht pink und es existiert eine Position im großen Bild, an der 8 von den 10 Pixeln exakt matchen, dann hätte ich gerne den Wert 8/10 heraus).
Im ersten Ansatz habe ich das kleine Bild über das große geschoben und dann Pixel für Pixel verglichen. Das funktioniert zwar super, aber ist auch krauchend langsam (>10 sekunden für den Vergleich von 20 300x12 bildern mit 10 12x12 icons)

Ich könnte mir gut vorstellen, dass es dazu schon bessere Lösungen gibt, bin beim suchen bis jetzt aber nur auf ähnliche (oder sehr abgehobene) Ansätze gestoßen.

Kleines KSKB mit 3 Testbildern:

import java.io.File;

import javax.imageio.ImageIO;

public class MatchingTryout {

	private static final int TEMPLATE_COLOR = 0xffff00ff;

	public static void main(String[] args) throws Exception {
		BufferedImage image = ImageIO.read(new File("test1.png"));
		BufferedImage icon1 = ImageIO.read(new File("test2.png"));
		BufferedImage icon2 = ImageIO.read(new File("test3.png"));
		System.out.println(maxMatch(image, icon1));
		System.out.println(maxMatch(image, icon2));
	}

	private static double maxMatch(BufferedImage image, BufferedImage icon) {
		double maxMatch = Double.MIN_VALUE;
		// halbes icon über die seiten verschieben für abgeschnittene bilder
		// (mehr als 50% verdeckt: kein matching)
		for (int x = -icon.getWidth() / 2; x < image.getWidth()
				+ icon.getWidth() / 2; x++) {
			for (int y = -icon.getHeight() / 2; y < image.getHeight()
					+ icon.getHeight() / 2; y++) {
				double match = matchAt(image, x, y, icon);
				if (match > maxMatch) {
					maxMatch = match;
				}
			}
		}
		return maxMatch;
	}

	private static double matchAt(BufferedImage image, int x, int y,
			BufferedImage icon) {
		int matches = 0;
		for (int ix = 0; ix < icon.getWidth(); ix++) {
			for (int iy = 0; iy < icon.getHeight(); iy++) {
				int currentX = ix + x;
				int currentY = iy + y;
				if (currentX >= 0 && currentX < image.getWidth()
						&& currentY >= 0 && currentY < image.getHeight()) {
					int rgb = icon.getRGB(ix, iy);
					if (rgb != TEMPLATE_COLOR
							&& image.getRGB(currentX, currentY) == rgb) {
						matches++;
					}
				}
			}
		}
		return (double) matches / realPixels(icon);
	}

	// nur zur demo, wert wird nicht jedes mal neu berechnet =)
	private static int realPixels(BufferedImage image) {
		int count = 0;
		for (int x = 0; x < image.getWidth(); x++) {
			for (int y = 0; y < image.getHeight(); y++) {
				if (image.getRGB(x, y) == TEMPLATE_COLOR) {
					count++;
				}
			}
		}
		return image.getWidth() * image.getHeight() - count;
	}
}```
Bild

Icon1 (Matching 1.0)

Icon2 (Matching 0.7758620689655172)


Danke für Antworten :idea:
Gruß

Mit

    public static BufferedImage convertToARGB(BufferedImage image)
    {
        BufferedImage newImage = new BufferedImage(
            image.getWidth(), image.getHeight(),
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }
    
    public static void main(String[] args) throws Exception {
        BufferedImage image = convertToARGB(ImageIO.read(new File("test1.png")));
        BufferedImage icon1 = convertToARGB(ImageIO.read(new File("test2.png")));
        BufferedImage icon2 = convertToARGB(ImageIO.read(new File("test3.png")));
...

ist’s schonmal 'ne Ecke schneller, aber ich schau nochmal weiter…

*** Edit ***

Wenn man diesen Teil mit

        // halbes icon über die seiten verschieben für abgeschnittene bilder
        // (mehr als 50% verdeckt: kein matching)
        for (int x = -icon.getWidth() / 2; x < image.getWidth() + icon.getWidth() / 2; x++) {
            for (int y = -icon.getHeight() / 2; y < image.getHeight() + icon.getHeight() / 2; y++) {

ändert zu

        for (int x = 0; x < image.getWidth() - icon.getWidth(); x++) {
            for (int y = 0; y < image.getHeight() - icon.getHeight(); y++) {

spart man sich diese manuelle Bounds-Abfrage in der innersten Schleife vom match. Das bringt einen nicht unerheblichen Zeitvorteil. Natürlich muss man den Rand dann getrennt betrachten, um dort nach einem “halben Icon” zu suchen, aber … da der größte Teil der Stellen, die man überprüfen muss, NICHT am Rand liegen, sondern in der Fläche (bzw. die Fläche mit der Kantenlänge quadratisch ansteigt) könnte sich der Mehraufwand bei der Implementierung da sehr schnell lohnen - speziell, wenn die Bilder größer werden.

*** Edit ***

Wenn man sich direkt die Arrays holt, wird’s schon recht schnell - davon ausgehend, dass die Bilder ohnehin nicht gezeichnet werden sollen. Dann noch die Schleifen (x/y) vertauschen, wegen des Cachings (schlägt bei größeren Bildern vermutlich ziemlich plötzlich und ziemlich heftig zu).

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.io.File;

import javax.imageio.ImageIO;

public class MatchingTryout {

    private static final int TEMPLATE_COLOR = 0xffff00ff;

    
    public static BufferedImage convertToARGB(BufferedImage image)
    {
        BufferedImage newImage = new BufferedImage(
            image.getWidth(), image.getHeight(),
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }
    
    public static void main(String[] args) throws Exception {
        BufferedImage image = convertToARGB(ImageIO.read(new File("test1b.png")));
        BufferedImage icon1 = convertToARGB(ImageIO.read(new File("test2.png")));
        BufferedImage icon2 = convertToARGB(ImageIO.read(new File("test3.png")));
        
        System.out.println(image.getType());
        System.out.println(icon1.getType());
        System.out.println(icon2.getType());
        
        long before = 0;
        long after = 0;
        double result = 0;
        
        for (int i=0; i<10; i++)
        {
	        before = System.nanoTime();
	        result = maxMatch(image, icon1);
	        after = System.nanoTime();
	        System.out.println("Duration "+(after-before)/1e6+"ms result "+result);
	
	        before = System.nanoTime();
	        result = maxMatch(image, icon2);
	        after = System.nanoTime();
	        System.out.println("Duration "+(after-before)/1e6+"ms result "+result);
        }
    }

    // Warning: Makes image "unmanaged"!
    private static int[] getData(BufferedImage image)
    {
    	DataBuffer imageDataBuffer = image.getRaster().getDataBuffer();
    	DataBufferInt imageDataBufferInt = (DataBufferInt)imageDataBuffer;
    	int imageArray[] = imageDataBufferInt.getData();
    	return imageArray;
    }
    
    // TODO: Consider the border!
    private static double maxMatch(BufferedImage image, BufferedImage icon) {
    	
    	int imageArray[] = getData(image);
    	int iconArray[] = getData(icon);
    	
        double maxMatch = Double.MIN_VALUE;
        for (int x = 0; x < image.getWidth() - icon.getWidth(); x++) {
            for (int y = 0; y < image.getHeight() - icon.getHeight(); y++) {
                double match = matchAt(image, imageArray, x, y, icon, iconArray);
                if (match > maxMatch) {
                    maxMatch = match;
                }
            }
        }
        return maxMatch;
    }

    private static double matchAt(BufferedImage image, int imageArray[], int x, int y, BufferedImage icon, int iconArray[]) {
        int matches = 0;
        for (int iy = 0; iy < icon.getHeight(); iy++) {
        	for (int ix = 0; ix < icon.getWidth(); ix++) {
                int currentX = ix + x;
                int currentY = iy + y;
                int rgb = iconArray[ix + iy * icon.getWidth()];
                int ref = imageArray[currentX + currentY * image.getWidth()];
                if (rgb != TEMPLATE_COLOR && ref == rgb) {
                	matches++;
                }
            }
        }
        return (double) matches / realPixels(iconArray);
    }

    // nur zur demo, wert wird nicht jedes mal neu berechnet =)
    private static int realPixels(int imageArray[]) {
        int count = 0;
        for (int i=0; i<imageArray.length; i++)
        {
        	if (imageArray** == TEMPLATE_COLOR) {
        		count++;
            }
        }
        return imageArray.length - count;
    }
}

Alles weitere wäre entweder “klein-klein-Kram” (Multiplikationen in den Schleifen vermeiden etc), (der aber ggf. auch nochmal merkbar was bringen kann!), oder eben ein komplett anderer Ansatz. Das Problem sollte ja nicht so unüblich sein. Könnte man da nicht mit Prefix Sums irgendwas dengeln? Ja, da müßte man dann nachdenken…

Wow, danke erstmal für die ausführliche Antwort =)
Ich habe mal einen Test ohne die Ränder laufen lassen, die Genauigkeit ist immer noch ausreichend für meine Anwendung.

Den Templatefarben-Vergleich in der matchAt-Methode habe ich auch rausgeworfen, die Templatefarbe kommt real nie vor, falls doch wäre die Abweichung auszuhalten.

Mit den Änderungen läuft das ganze ca Faktor 400 schneller, das reicht mir für meine Anwendung locker aus.
Auf die Konvertierung von den geladenen Bildern zu ARGB wäre ich garantiert nicht gekommen :rolleyes:.
Normalerweise zeichne ich soviel hin und her, dass ich überhaupt keine anderen Types verwende (außer vielleicht mal Graustufen).
ImageIO gibt bei read für png offenbar TYPE_4BYTE_ABGR bzw TYPE_3BYTE_ABGR zurück…

Gruß

400 ist doch mal 'ne Hausnummer :smiley: Trotzdem habe ich mir ein „TODO“ geschrieben - das ganze mal mit prefix sums auszuprobieren. Damit könnte es RICHTIG schnell werden. Insbesodere würde man die Geschwindigkeit damit praktisch unabhängig von der Icon-Größe bekommen. Schlabber. Lechz. :smiley:

Hallo @Marco13 ich hab vor etwas Zeit mal in diesen Udacity Kurs reingeschnuppert bei dem es mehr oder weniger um CUDA geht. https://www.udacity.com/course/cs344 Und in diesem Kurs geht es in den Beispielen/Aufgaben um Bildbearbeitung mithilfe der GPU und somit eben auch um genau dieses Aufgabengebiet von Firephoenix (Lesson 4 Red Eye Removal). Das ganze Parallel auf 1000 Kernen. Naja und daher wollte ich dich als Inventor von JCuda darauf Aufmerksam machen, dass da das eine ganz gut zum anderen passen würde. Ich hätte da evtl. sogar die ein oder andere Idee aber leider keine passende Graka um es dann auch tatsächlich zu verwirklichen.

Ja, der Gedanke ist natürlich nahe liegend. Die schon erwähnten „Prefix Sums“ sind quasi DER klassische Building-Block für viele, viele Algorithmen, die auf den ersten Blick „sequentiell aussehen“, aber damit dann doch effizient auf einer parallelen Maschine implementiert werden könnten. Eine Abstraktionsstufe weiter oben ist dieses „Fenster, das über ein großes Bild geschoben wird“ natürlich auch eng verwandt mit weiteren Klassikern, wie Convolution und Box Filter, aber das ist schon nochmal etwas anderes als der hier konkret vorliegende Fall.

Ich gehe davon aus, dass es zu diesem und ähnlichen Fragestellungen schon vieeeeel Literatur gibt. Auch wenn ich mich Hobbymäßig im Rahmen von JCuda und JOCL „mit sowas“ beschäftige, kann ich natürlich in jedem der unendlich vielen Themenfelder, wo GPU programming eigesetzt wird oder werden könnte, nur die Spitze des Eisberges betrachten, und der größte Teil (d.h. die Forschungsergebnisse der letzten 50 Jahre, die mit dem jeweilgen Thema zu tun haben) bleiben (mir) weitestgehend verborgen. Von Linearer Algebra und Numerik über FFT, Simulation, Bildverarbeitung und 1000 andere Themen: Es gibt einfach zu viel,
um sich überall auch nur annähernd auf den „aktuellen Stand der Forschung“ zu bringen bzw.
um sich auch nur annähernd überall auf den „aktuellen Stand der Forschung“ zu bringen.

Diese Bild-In-Bild-Suche an sich ließe sich selbst ohne ausgeklügelte Techniken leicht parallelisieren - geradezu trivial: Zwei for-schleifen über x/y, jeweils von 0 bis 1000 —> 1000000 Threads und fertig. Der einzige „Stolperstein“ ist, dass man den Maximalwert sucht, und man da dann ggf. auf Atomics zurückgreifen muss, aber da das Maximum vermutlich nur an SEHR wenigen dieser 1000000 Punkte gefunden wird, dürfte das vernachlässigbar sein.

Der Vorteil, den ich bei den Prefix Sums vermute, wäre aber die Unabhängigkeit von der Größe des Icons (bzw. von seiner Breite). Ob das „trivial parallelisierte“ oder irgendwas ausgeklügeltes dann am Ende besser performt, hängt wahrscheinlich nicht zuletzt davon ab, wie das Test-Setup aussieht (Bildgröße, Icongröße, Anzahl Icons, Bildinhalt etc).

Ich werde ggf. mal versuchen, den Prefix-Ansatz zu implementieren - nur interessehalber. Aber erstmal in Java. Nach CUDA bzw. OpenCL portieren kann man das dann ggf. immernoch (wenn man schon weiß dass oder WIE es grundsätzlich funktionieren könnte). Gerade wenn es um ggf. etwas trickreiches Index-Gefrickel geht, fängt man an, ArrayIndexOutOfBoundsExceptions zu lieeeben - im Vergleich zum Aufhängen, Crashen oder Müll-liefern, wozu CUDA und OpenCL bei solchen Fehlern eher neigen :wink:

Auf jeden Fall könnten die verlinkten Videos mich … entweder motivieren, oder davon abhalten (weil’s alles schon so gemacht wurde, und deswegen langweilig ist ;)) - die schau’ ich mir mal an.

Ich nochmal,
mal ein bisschen ausführlicher. Allerdings eher Pseudocode. Wenn man sich ein bischen hinsetzt, dann bekommt man das auch recht flüssig mit Cuda zum laufen.

Annahme: Ausgangslage ein großes Bild (GB) mit 300 x 300 Pixeln, kleines Bild (KB) mit 12 x 12 Pixeln.

  1. Gesuchtes Ergebnis ist eine Matrix (M) mit 289 x 289 ints, für die Gilt
M**** = Summe von {
(GB**** = KB[0][0]) ... (GB**[i+11]==KB[0][11])
...
(GB[i+11]** = KB[11][0]) ... (GB[i+11][i+11]==KB[11][11])
}
  1. Damit M effizient berechnet werden kann und man üblicherweise von 1024 Rechenkernen ausgeht, teilt man die kernel folgendermaßen auf, dass ein Kernel einen 32 x 32 Pixel großen Part berechnet. D.h. jeder Kernel greift nur auf maximal 43 x 43 Pixel zu. Damit sollte man mit dem L2 Cache für GB gut zu Rande kommen. Und das KB sollte dort auch noch genügend Platz finden. L2 damit es so richtig fluppt.

  2. Man berechnet die Zelle bei der M ihr maximum hat. Das sollte mit Cuda auch recht gut in O=log(n) (Vermutung) gehen. Da gibt es massig Literatur zu. Oder einfach die Indizes nach dem max sortieren. Das geht mit der Graka auch recht ordentlich.

  3. Das Feld in dem das Maximum steht, wäre dann z.B. die linke obere Ecke des KB auf dem GB.

Ich bin mir ziemlich sicher, dass das vieles in den Schatten stellt, das man zur Zeit auf der CPU hinbekommt.

Ja, ein wichtiger Optimierungsschritt wäre, das “Icon” und seine Umgebung in den “L2” zu bekommen, der bei CUDA “Shared Memory” heißt, und bei OpenCL “Local Memory” (“Local Memory” in CUDA ist dann nochmal was anderes - die Begriffe sind da etwas ungünstig gewählt…). Allerdings ist der ja im Moment meistens leider noch SEHR klein, und alle Threads eines Blocks müssen ihn sich teilen - zudem kann das Vermeiden von Bank Conflicts ziemlich tricky sein. Die neueren CUDA-Versionen schwächen diese Probleme etwas ab (automatischer L2-Cache, weniger kritische Bank Conflicts etc), aber trotzdem würde das vermutlich schon SEHR kompliziert, wenn man auch mit “Icons” hantieren wollte, die garantiert größer sind, als das, was in den L2/Shared reinpasst…

Ich werfe mal noch einen anderen Ansatz in die Runde: Boosting. Schaut auf den ersten Blick vielleicht abgehoben aus, aber wenn man sich mal einen Nachmittag lang hinsetzt um sich einzulesen, dann kann man das eigentlich problemlos implementieren.

Die Grundidee dabei ist folgende: Man definiert sich verschiedene schwache Klassifikatoren*, von denen jeder für sich zwar miserable Ergebnisse liefert, die dafür aber sehr schnell sind. Wenn man nun genug von diesen miserablen Einzelergebnissen zusammenrechnet, dann kann man damit durchaus auf ein gutes Gesamtergebnis kommen. Die Objekterkennung von Viola und Jones basiert beispielsweise auf diesem Prinzip. Dabei haben die sich überlegt, das es zwar schwierig ist zu entscheiden das Objekt z an Position x/y zu sehen ist, es aber leicht sein kann zu entscheiden das Objekt z NICHT an Position x/y zu sehen ist. Daraus haben sie eine Kaskade von Klassifikatoren aufgebaut. Grundidee: Die Klassifikatoren entscheiden nicht mehr Ja/Nein sondern Vielleicht/Nein. Ein einzelner Klassifikator der Kaskade kann sehr schnell entscheiden ob ein Objekt an der gegebenen Position möglicherweise oder mit Sicherheit nicht zu sehen ist. Im zweiten Fall können wir den Test sofort abbrechen und zu Position x+1/y übergehen. Im ersten Fall muss man noch einen zweiten (etwas besseren) Klassifikator befragen. Und wenn der ebenfalls möglicherweise meldet, dann eben noch einen dritten usw, halt solange bis die gewünschte Irrtumswahrscheinlichkeit erreicht hat.

  • Allgemein: Ein Klassifikator wäre diesem Fall eine Komponente, die entscheidet ob an Position x/y eines Bildes das Icon Nummer z zu sehen ist. Von einem sogenannten schwachen Klassifikator wird dabei nur verlangt, das er in mehr als 50% aller Fälle richtig entscheidet (aka “besser als Würfeln”). Wenn man nun mehrere dieser schwachen Klassifikatoren befragt, und diese jeweils eine Irrtumswahrscheinlichkeit < 0,5 haben, dann lässt sich die Gesamtirrtumswahrscheinlichkeit beliebig weit herunterdrücken, halt abhängig davon wieviele schache Klassifikatoren man hernimmt (und wie gut diese tatsächlich sind).

Der Ansatz mit den Klassifizieren klingt auch nicht schlecht (bisschen Overkill für mein Experiment, aber vielleicht probiere ich sowas einfach mal aus).
Irgendwo habe ich noch ein Paper rumfliegen, wo so etwas ähnliches zum Tracken von Features in bewegten Bildern verwendet wird.
Leicht angepasst für meine Anwendung würde das ganze etwa so aussehen:
Man definiert sich eine Funktion um 2 Pixel zu vergleichen, z.B. f(p1,p2)=r(p1)+g(p1)+b(p1) > r(p2)+g(p2)+b(p2)
Anschließend erstellt man sich den Satz Icons, den man erkennen will.
Aus diesen baut man sich einen Baum auf, jeder Knoten im Baum repräsentiert einen Test, bei dem 2 Pixel verglichen werden (z.B. x=3y=5 mit x=4y=6). Je nachdem wie der Test ausfällt, sortiert man die Icons in den linken oder rechten Teilbaum.
Der Test für jeden Knoten sollte so gewählt werden, dass er die Icons möglichst 50/50 trennt.
Sucht man jetzt nach einem Icon, verschiebt man wieder pixelweise einen bildausschnitt über das große bild. Auf dem Ausschnitt führt man jeweils die Tests aus dem Baum durch. Sobald man in einem Teilbaum landet, der das gesuchte Icon nicht enthält kann man abbrechen (sollten bei einem guten Baum ca log(Anzahl Icons) operationen sein. Besteht man die Tests für das Blatt mit dem Icon, kann man sich den Abschnitt genauer Anschauen (z.b. mit der aktuellen Methode, jeden Pixel zu vergleichen).
Das ganze kann man dann noch erweitern, z.B. in dem man bei Fehlschlägen (Blatt gefunden, aber kein gutes Matching für das Icon) den Bildausschnitt als negativ-Beispiel als Blatt in den Baum aufnimmt (führt zu einem neuaufbau einiger Baumknoten).
(Anpassung von http://en.wikipedia.org/wiki/ID3_algorithm)
Alternativ kann man auch einen Entscheidungsbaum für jedes Icon aufbauen (mehrere Positiv/Negativbeispiele), diese lassen sich dann auch parallel abarbeiten.
Das macht allerdings mehr Sinn, wenn man wirkliche Trainingsdaten hat, die sich auch (leicht) verändern, meine Icons ändern aber normalerweise nicht die Pixel =)

Wenn es schon um “andere Ansätze” geht, wäre es interess- oder relev-ant zu wissen, inwieweit der tatsächliche Wert, der da zurückgegeben wird, relevant sein muss. Im Moment ist der (wenn ich das richtig sehe) klipp und klar definiert als: Prozentsatz der gleichen Pixel. Reicht es, wenn das “irgendein” Wert ist, der einen Vergleich erlaubt? Muss der Wert über verschiedene Bild/Icon-Kombinationen hinweg vergleichbar sein?

Konkret auf meinen Anwendungsfall bezogen: Nein.
Allgemein geht es nur darum, dass man ein beliebiges Bild gegeben hat und dort die Icons findet, jeweils mit Position und möglichst den richtigen Icon für den Ausschnitt.

Mein Anwendungsproblem sehe ich aber als erledigt, es macht ja im Moment das was es soll und das auch noch in einer brauchbaren Zeit.
Trotzdem finde ich die Diskussion über das Thema ziemlich spannend, vielleicht findet sich ja mal ein Anwendungsfall, wo man ein komplexeres/leistungsfähigeres System benötigt.

Ja sicher, das ganze ist erstmal rein akademisch. Aber das, was ich zu den Prefix Sums ursprünglich im Kopf hatte, funktioniert so nicht für beliebige Icon-Inhalte. Wenn man wirklich eine performantere Implementierung braucht, hat man aber zumindest die Optionen der trivialen Parallelisierung, oder andere “Optimierungen” - wobei die ersten, die mir da einfallen würden, speziell auf der GPU keinen Sinn machen würden: Eine wäre, dass man beim Vergleich berücksichtigt, wie groß das bisher beste Matching war. Wenn man bei 1000 (relevanten) Pixels schon ein Matching von 900 Pixeln gefunden hat, und gerade einen weiteren Vergleich macht, kann man, wenn man bei den ersten 100 der geprüften Pixel KEINE Übereinstimmungen gefunden hat, ja schon sicher sagen, dass man nicht mehr mehr als 900 Übereinstimmungen finden können wird. Eine andere wäre, dass man nicht nur die relevanten Pixel zählt, sondern tatsächlich die Koordinaten der relevanten Pixel berechnet, und dann nur die wirklich für das Matching verwendet.

Aber wie gesagt, welche Optimierung (oder Art der Implementierung) wie viel bringt, hängt da sicher vom Anwendungsfall ab. Bei der ersten: Wenn man das Icon schon mit einem Matching von 1.0 in der oberen linken Ecke findet, und der Rest sehr “unähnlich” zum Icon ist, wird das sicher ziemlich viel bringen. Wenn aber alles sehr “gleichförmig” aussieht (also an allen Stellen ähnlich hohe Matching-Werte erreicht werden), oder das “beste” Matching immernoch sehr “schlecht” ist, und/oder das beste Matching in der unteren, rechten Ecke liegt, macht man sich damit nur unnötige Arbeit. Bei der zweiten: Wenn ALLE Pixel des Icons relevant sind, spart man sich nichts durch das explizite Berechnen der relevanten Pixel.