Diplomarbeit Keypoint-Detektion und Deskriptoren
Transcription
Diplomarbeit Keypoint-Detektion und Deskriptoren
AG ROBOTERSYSTEME FACHBEREICH INFORMATIK AN DER TECHNISCHEN UNIVERSITÄT KAISERSLAUTERN Diplomarbeit Keypoint-Detektion und Deskriptoren-Berechnung auf der Grafikkarte Fabian Zimmermann 21. Februar 2007 Keypoint-Detektion und Deskriptoren-Berechnung auf der Grafikkarte Diplomarbeit Arbeitsgruppe Robotersysteme Fachbereich Informatik Technische Universität Kaiserslautern Fabian Zimmermann Tag der Ausgabe : 30. Juni 2006 Tag der Abgabe : 21. Februar 2007 Betreuer Betreuer : Prof. Dr. Karsten Berns : Dipl.-Inform. Tim Braun Ich erkläre hiermit, die vorliegende Diplomarbeit selbstständig verfasst zu haben. Die verwendeten Quellen und Hilfsmittel sind im Text kenntlich gemacht und im Literaturverzeichnis vollständig aufgeführt. Kaiserslautern, den 21. Februar 2007 (Fabian Zimmermann) Vorwort An dieser Stelle möchte ich mich bei allen bedanken, die zum Gelingen dieser Arbeit beigetragen haben. Dies gilt besonders für die Mitglieder der Arbeitsgruppe Robotersysteme. Seit nunmehr über zwei Jahren habe ich regelmäßig in dieser Gruppe gearbeitet. Zunächst als Praktikant. Später habe ich dort mein Seminar, meine Projektarbeit und schließlich meine Diplomarbeit geschrieben. Dabei durfte ich mit vielen interessanten Menschen zusammenarbeiten und konnte eine Menge lernen. Mein besonderer Dank gilt meinem Betreuer Tim. Bei einem für uns beide neuen Thema, dem General Purpose Computing on Graphics Processing Units, hat er mir immer hilfreich zur Seite gestanden. Ohne seine Ratschläge und seine unermüdliche Hilfe bei der Fehlersuche hätte ich diese Arbeit wohl nicht so erfolgreich zu Ende bringen können. Desweitern danke ich Philipp und Christopher für ihre hilfreichen Verbesserungsvorschläge, besonders in der Schlussphase. Zuletzt möchte ich mich noch bei Marlis für die vielen Stunden, die sie mit dem Korrekturlesen dieser Arbeit verbracht hat, bedanken. Inhaltsverzeichnis 1 Einleitung 9 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.2 Ziel der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.3 Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2 Verwandte Arbeiten 2.1 SIFT . . . . . . . . . . . . . . . . . . . . . . . 2.2 SIFT-Varianten und andere Featuredetektoren 2.2.1 PCA-Sift . . . . . . . . . . . . . . . . . 2.2.2 Integralbild . . . . . . . . . . . . . . . 2.2.3 Reduced SIFT Features . . . . . . . . 2.2.4 Andere Vereinfachungen . . . . . . . . 2.2.5 GLOH . . . . . . . . . . . . . . . . . . 2.3 SIFT auf schnellerer Hardware . . . . . . . . . 2.3.1 FPGA-Versionen . . . . . . . . . . . . 2.3.2 SIFT auf der GPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 14 14 15 15 15 15 16 16 16 SIFT-Algorithmus Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . Pyramiden . . . . . . . . . . . . . . . . . . . . . . . . . . Ablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ermitteln potentieller Keypoints . . . . . . . . . . . . . . 3.4.1 Aufbau der Gausspyramide . . . . . . . . . . . . 3.4.2 Die DoG-Pyramide . . . . . . . . . . . . . . . . . 3.4.3 Finden potentieller Keypoints . . . . . . . . . . . 3.4.4 Mathematischer Hintergrund des DoG-Detektors . 3.5 Filterung und Subpixellokalisierung . . . . . . . . . . . . 3.5.1 Filterung von Punkten mit niedrigem Kontrast . 3.5.2 Kanteneliminierung . . . . . . . . . . . . . . . . . 3.5.3 Subpixellokalisierung . . . . . . . . . . . . . . . . 3.6 Berechnung der Orientierung . . . . . . . . . . . . . . . . 3.6.1 Gradientenberechnung . . . . . . . . . . . . . . . 3.6.2 Orientierungshistogramme . . . . . . . . . . . . . 3.7 Berechnung der Deskriptoren . . . . . . . . . . . . . . . 3.7.1 Drehung der Umgebung . . . . . . . . . . . . . . 3.7.2 Berechnung der Histogramme . . . . . . . . . . . 3.7.3 Normalisieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 18 18 20 20 23 24 26 27 27 27 29 31 31 31 35 36 36 37 3 Der 3.1 3.2 3.3 3.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 4 GPGPU 4.1 Allgemeine Informationen . . . . . . . . . 4.2 Die Grafikkarte . . . . . . . . . . . . . . . 4.2.1 Das Stream Modell . . . . . . . . . 4.2.2 Aufbau der GPU . . . . . . . . . . 4.3 Prinzipielles Vorgehen . . . . . . . . . . . 4.4 Besonderheiten der GPU-Programmierung 4.4.1 Gather vs. Scatter . . . . . . . . . 4.4.2 Texturen . . . . . . . . . . . . . . . 4.4.3 Fragmentprogramme . . . . . . . . 4.4.4 In Texturen rendern . . . . . . . . 4.4.5 Vertexkoordinaten . . . . . . . . . 4.4.6 Texturkoordinaten . . . . . . . . . 4.4.7 Rücklesen der Daten . . . . . . . . 4.4.8 Multiple Render Targets . . . . . . 4.5 Die Programmiersprache Cg . . . . . . . . 4.5.1 Vektordatentypen . . . . . . . . . . 4.5.2 Semantics . . . . . . . . . . . . . . Inhaltsverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Andere GPU-Implementierungen 5.1 OpenVIDIA . . . . . . . . . . . . . . . . . . . . 5.2 Diplomarbeit von Sebastian Heymann . . . . . . 5.2.1 Packen der Daten . . . . . . . . . . . . . 5.2.2 Keypointsuche auf der CPU . . . . . . . 5.2.3 Keypointsuche auf der GPU . . . . . . . 5.2.4 Berechnung der Gradienten . . . . . . . 5.2.5 Deskriptorenberechnung . . . . . . . . . 5.3 GPU-based Video Feature Tracking von Sudipta 5.4 Realtime SIFT von Cameron Upright . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . N. Sinha . . . . . . 6 Realisierung 6.1 Programm-Ablauf . . . . . . . . . . . . . . . . . . . . 6.1.1 Initialisierung . . . . . . . . . . . . . . . . . . 6.1.2 Laden der Bilder . . . . . . . . . . . . . . . . 6.1.3 Erste Oktave . . . . . . . . . . . . . . . . . . 6.1.4 Oktave vorbereiten . . . . . . . . . . . . . . . 6.1.5 Keypoints finden und Deskriptoren berechnen 6.1.6 Texturen verkleinern . . . . . . . . . . . . . . 6.2 Packen der Bilddaten . . . . . . . . . . . . . . . . . . 6.3 Aufbau der Pyramidenteile . . . . . . . . . . . . . . . 6.3.1 Gaussfaltung . . . . . . . . . . . . . . . . . . 6.3.2 Differenzbilder . . . . . . . . . . . . . . . . . 6.4 Finden der Keypoints . . . . . . . . . . . . . . . . . . 6.4.1 Bereich in dem nach Keypoints gesucht wird . 6.4.2 Test für komplettes Fragment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 39 40 40 40 41 42 42 43 43 44 44 44 45 45 45 46 47 . . . . . . . . . 49 49 51 51 53 53 54 54 55 57 . . . . . . . . . . . . . . 59 59 61 61 61 61 63 63 63 67 67 70 71 72 72 Inhaltsverzeichnis 6.5 6.6 6.7 6.8 7 6.4.3 Tests in einzelnen Farbkanälen . . . . . . . . . . . . . . Gradientengrößen und Richtungen . . . . . . . . . . . . . . . . Orientierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.6.1 Erzeugen der Gaussmaskentextur . . . . . . . . . . . . 6.6.2 Rendern der Orientierungshistogramme . . . . . . . . . 6.6.3 Berechnung im Shaderprogramm . . . . . . . . . . . . 6.6.4 Rücklesen der Histogramme . . . . . . . . . . . . . . . 6.6.5 Berechnungen auf der CPU . . . . . . . . . . . . . . . Deskriptoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.7.1 Ablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.7.2 Erzeugen der Ausgabetextur . . . . . . . . . . . . . . . 6.7.3 Gaussmaskentextur . . . . . . . . . . . . . . . . . . . . 6.7.4 Rendern der Histogramme . . . . . . . . . . . . . . . . 6.7.5 Das Cg-Programm . . . . . . . . . . . . . . . . . . . . 6.7.6 Die übergebenen Parameter . . . . . . . . . . . . . . . 6.7.7 Berechnungen auf der CPU . . . . . . . . . . . . . . . 6.7.8 Einschränkungen gegenüber der CPU-Implementierung Nächste Oktave berechnen . . . . . . . . . . . . . . . . . . . . 7 Auswertung 7.1 Bewertung einzelner Zwischenergebnisse . . . . . 7.1.1 Gaussbilder . . . . . . . . . . . . . . . . . 7.1.2 Differenzbilder . . . . . . . . . . . . . . . 7.1.3 Position der Keypoint . . . . . . . . . . . 7.1.4 Orientierungen . . . . . . . . . . . . . . . 7.2 Qualität des Deskriptors . . . . . . . . . . . . . . 7.2.1 Matchen der Keypoints . . . . . . . . . . . 7.2.2 Testszenarien . . . . . . . . . . . . . . . . 7.3 Laufzeit . . . . . . . . . . . . . . . . . . . . . . . 7.3.1 Vergleich mit anderen Implementierungen 7.3.2 Laufzeit einzelner Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 77 78 78 78 79 80 80 81 81 82 83 83 83 84 85 85 85 . . . . . . . . . . . 87 87 87 88 88 88 88 89 90 91 92 94 8 Zusammenfassung und Ausblick 99 8.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 8.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 A Verwendete Hardware 101 A.1 Grafikkarte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 A.2 Rechner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 B Verschiedene Profile C Cg C.1 C.2 C.3 C.4 C.5 Anlegen eines Cg-Kontextes . . . . . . . Erzeugen und Laden der Cg-Programme Cg-Beispielcode . . . . . . . . . . . . . . Ausführung des Cg-Programms . . . . . Semantics . . . . . . . . . . . . . . . . . 103 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 105 105 105 106 107 8 Inhaltsverzeichnis D OpenGL-Befehle 109 D.1 Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 D.2 Rendern von Rechtecken . . . . . . . . . . . . . . . . . . . . . . . . . 109 D.3 Rendern einzelner Punkte . . . . . . . . . . . . . . . . . . . . . . . . 110 E Übertragen und Rücklesen der Daten 111 E.1 CPU auf GPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 E.2 GPU auf CPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 F Multiple Render Targets F.1 Aufruf in OpenGL . . . . . . . . . F.1.1 Binden der Ausgabetexturen F.1.2 Festlegen der Attachments . F.2 Cg-Programm . . . . . . . . . . . . F.3 Rücklesen . . . . . . . . . . . . . . Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 . 113 . 113 . 113 . 114 . 115 119 1. Einleitung 1.1 Motivation Die Bedeutung der Bildverarbeitung in der Robotik hat in den vergangenen Jahren kontinuierlich zugenommen. So lassen sich aus der Verarbeitung verschiedener Kamerabilder oder ganzer Videoströme viele brauchbare Informationen berechnen. Eine häufige Anwendung bei der Verarbeitung von Bildinformationen ist das Wiederfinden besonderer Stellen oder Punkte eines Bildes in anderen Bildern. Dadurch lassen sich beispielsweise bestimmte Objekte, von denen vorher Aufnahmen gemacht wurden, auf anderen Bildern wiederentdecken. Außerdem können die Bewegungen verschiedener dieser Punkte in einer Videosequenz verfolgt werden. Wenn die Punkte bestimmten Objekten zugeordnet werden können, lassen sich damit auch diese Objekte in einem Videostrom verfolgen. Dies lässt sich u.a. zur Visuellen Odometrie verwenden. Ein weiterer für die Robotik wichtiger Ansatz ist das Erkennen von charakteristischen Gegenständen wie beispielsweise Landmarken. Bei einem mit Kameras ausgestatteten Roboter tauchen diese Gegenstände irgendwann in den Eingangsbildern auf und sollen direkt erkannt werden. Dabei hat sich das folgende Vorgehen bewährt. Auf dem zu untersuchenden Bild werden mit Hilfe eines Detektors nur die besonders charakteristischen Punkte, die sogenannten Interest- oder Keypoints, ausgewählt. Für die Auswahl dieser Punkte gibt es gewisse Anforderungen, wie Invarianz gegenüber verschiedenen Bildveränderungen (siehe dazu auch [Zimmermann 06]). Dies ist notwendig, damit die Punkte auch auf einem veränderten Bild, beispielsweise auf dem nächsten Bild einer Videosequenz oder aber auch wie beim Erkennen von besonderen Gegenständen in einem vollkommen anderen Bild, gefunden werden. So sollten möglichst die gleichen Keypoints ausgewählt werden, unabhängig davon, ob sich Lichtverhältnisse ändern oder ob der Blickwinkel auf eine Szene ein anderer ist. Für die ausgewählten Keypoints werden dann sogenannte Deskriptoren oder FeatureVektoren berechnet. Ein solcher Deskriptor soll seinen Keypoint möglichst so beschreiben, dass dieser später auf anderen Bildern dadurch identifiziert werden kann. 10 1. Einleitung Abbildung 1.1: Gematchte Keypoints Dafür wird die Umgebung des Keypoints betrachtet. Aus dieser Umgebung wird ein für den Keypoint möglichst charakteristischer Vektor berechnet. Auch diese Deskriptoren müssen möglichst invariant gegenüber den oben genannten Bildveränderungen sein. So soll für den selben Keypoint auf zwei unterschiedlichen Bildern der gleiche oder zumindest ein ähnlicher Deskriptor berechnet werden, auch wenn sich beispielsweise die Position oder die Orientierung der Kamera relativ zu dem Gegenstand, auf dem der Keypoint liegt, verändert hat. Oder wenn die Kamera nun einen anderen Abstand zu diesem Gegenstand hat. Dadurch ändert sich u.a. die Größe des Bildes und damit die Größe des Keypoints. Trotzdem sollte der Deskriptor zumindest ähnlich sein. Nur so können die auf verschiedenen Bildern gefundenen Keypoints mit Hilfe der Deskriptoren einander zugeordnet werden. Dieser Vorgang wird Matchen genannt. Dafür werden die Deskriptoren der Keypoints mehrerer Bilder verglichen. Falls die Deskriptoren zweier Keypoints komplett oder zumindest annähernd übereinstimmen, gelten diese beiden Punkte als gematcht. So weiß man, wo sich Punkte aus dem einen Bild im anderen Bild befinden. 1.2. Ziel der Arbeit 11 Abbildung 1.1 zeigt wie die Keypoints zweier Bilder gematcht werden. Hier wurde von einer Packung Basmatireis eine Aufnahme gemacht. Dies ist oben im Bild zu sehen. Dadurch lässt sich diese Packung im unteren Bild zwischen mehreren Gegenständen wiederfinden. Die Abbildung wurde mit Hilfe des von David Lowe zur Verfügung gestellten Programms1 zum Matchen von Keypoints erstellt. 1.2 Ziel der Arbeit Ziel der Arbeit war es einen Algorithmus zu implementieren, der Keypoints findet und für diese Keypoints Feature-Vektoren mit den oben genannten Eigenschaften berechnet. Der im Jahre 1999 von David Lowe entwickelte und im Jahre 2004 noch einmal verbesserte SIFT-Algorithmus erfüllt alle Anforderungen, die an einen Algorithmus zur Detektion von Keypoints und Berechnung von Deskriptoren gestellt werden (siehe [Lowe 99] und [Lowe 04]). Dieser Algorithmus wird in Kapitel 3 ausführlich vorgestellt. Eine wichtige Eigenschaft besitzt er allerdings nicht: eine hohe Geschwindigkeit bei der Ausführung auf einem handelsüblichen Computer. Der SIFT ist so komplex und rechenintensiv, dass er für Echtzeitanwendungen nicht oder nur bedingt geeignet ist. Ziel dieser Diplomarbeit war es deshalb, eine schnelle und damit anwendbare Version des SIFT-Algorithmus zu implemetieren. Allerdings wurde dieser Algorithmus schon sehr stark optimiert, so dass jede weitere Veränderung wahrscheinlich zu einer Verschlechterung der Qualität führen würde. Eine starke Verringerung der Komplexität ist ohne erhebliche Qualitätseinbußen kaum möglich. Außerdem sind bereits eine handvoll auch auf Geschwindigkeit optimierte Implementierungen im Netz verfügbar (siehe Kapitel 2.1). Die dort erzielten Beschleunigungen sind allerdings für viele Anwendungen noch nicht ausreichend. Deshalb wurde in dieser Arbeit ein anderer Weg gewählt, um den SIFT zu beschleunigen. Ein weit verbreitetes Mittel zur Erhöhung der Geschwindigkeit langsamer Algorithmen ist ihre Ausführung auf schnellerer und für die entsprechende Aufgabe spezialisierter Hardware. Inzwischen haben alle aktuellen Computer eine extrem schnelle Hardwarekomponente eingebaut, die Grafikkarte. Sie hat den großen Vorteil einzelne, mehrfach vorkommende Berechnungen parallel ausführen zu können. Da neuere Grafikkarten relativ frei programmierbar sind, ist eine Umsetzung komplexer Algorithmen auf der Grafikkarte möglich. Für eine sinnvolle Umsetzung müssen diese Algorithmen gut parallelisierbar sein. Dabei können sie allerdings häufig nicht eins zu eins übertragen werden. Da im SIFT viele Schritte auf allen Pixeln eines Bildes parallel durchgeführt werden müssen, scheint dieser Algorithmus geeignet durch Ausführung auf der Grafikkarte beschleunigt zu werden. Ziel der Arbeit war es daher, durch die Umsetzung des SIFT-Algorithmus auf der Grafikkarte ein schnelles Programm zur Detektion von Keypoints und zur Berechnung ihrer Deskriptoren zu erhalten. Dabei sollte ein Konzept zur Verlagerung der einzelnen Teile auf die Grafikkarte entwickelt und umgesetzt werden. Die bei der 1 http://www.cs.ubc.ca/~lowe/keypoints/ 12 1. Einleitung Umsetzung notwendigen Veränderungen gegenüber bereits vorhandenen CPU-Implementierungen sollten dokumentiert werden. Anschließend sollten die bei der Umsetzung auf der Grafikkarte erzielten Ergebnisse mit denen anderer SIFT-Implementierungen verglichen werden. 1.3 Aufbau der Arbeit Kapitel 2 gibt einen groben Überblick über bereits vorhandene Implementierungen des SIFT und vergleichbarer Algorithmen, sowohl auf der CPU als auch auf anderen Hardwarekomponenten. Der SIFT-Algorithmus selbst wird in Kapitel 3 detailliert erläutert. In Kapitel 4 wird das unter dem Namen General Purpose Computing on Graphics Processing Units bekannte Konzept, die Grafikkarte auch zur Berechnung von NichtGrafik-Algorithmen zu verwenden, ausführlich vorgestellt. Kapitel 5 behandelt bereits vorhandene Umsetzungen des SIFT auf der Grafikkarte, da diese eine wichtige Grundlage für diese Arbeit darstellen. Im Kapitel 6 wird dann die eigene Implementierung des SIFT auf der GPU erläutert. Dabei werden Unterschiede zu den bereits vorhandenen GPU-Umsetzungen und insbesondere zu der Referenzimplementierung auf der CPU, dem LibSIFT, aufgezeigt und begründet. In Kapitel 7 werden schließlich die Ergebnisse präsentiert und Auskunft über die Qualität und Geschwindigkeit dieser Umsetzung im Vergleich zu anderen Implementierungen gegeben. Schließlich werden noch die Resultate zusammengefasst und es wird ein Ausblick gegeben. 2. Verwandte Arbeiten 2.1 SIFT Der SIFT-Algorithmus wurde von David Lowe im Jahre 1999 erstmals veröffentlicht (siehe [Lowe 99]). Dieser Algorithmus detektiert in einem Bild verschiedene Keypoints. Für diese Keypoints werden neben der Lage auch ihre Orientierung und Größe ermittelt. Abhängig von Größe und Orientierung eines jeden Keypoints wird ein sogenannter Deskriptor berechnet. Dadurch, dass bei der Berechnung des Deskriptors Größe und Orientierung berücksichtigt werden, ist er skalen- und rotationsinvariant. In [Lowe 04] stellt David Lowe eine Verbesserung seines Algorithmus vor. Die Keypoints werden nun mit Subpixelgenauigkeit ermittelt, was die Qualität der gefundenen Punkte und ihrer Deskriptoren deutlich erhöht. Der Algorithmus ist noch einmal ausführlich in Kapitel 3 erläutert. In den USA ist der SIFT-Algorithmus durch das Patent Nr.6,711,293 geschützt. Lowe selbst stellt auf seiner Homepage1 eine Closed Source Variante des Algorithmus zur Verfügung. Diese Implementierung benötigt die Eingangsbilder im binären pgm-Format. Die gefundenen Keypoints werden zusammen mit den Deskriptoren als Textdatei zurückgegeben. Inzwischen existieren auch einige Open Source Implementierungen des SIFT. Eine Auflistung verschiedener Versionen findet sich im Internet2 . • Andrea Vedaldi stellt auf seinen Seiten3 sowohl eine Matlabversion des SIFTs als auch eine C++-Version namens SIFT++ zum Download bereit. • Eine C-Implementierung inklusive Methoden zum Matchen der Keypoints hat Rob Hess4 entwickelt. 1 http://www.cs.ubc.ca/~lowe/keypoints/ http://people.csail.mit.edu/albert/ladypack/wiki/index.php/Known_ implementations_of_SIFT 3 http://vision.ucla.edu/~vedaldi/code/ 4 http://www.cs.wustl.edu/~rstancha/oss.php 2 14 2. Verwandte Arbeiten • Roman Stanchak hat eine auf OpenCV basierende C++-Bibliothek5 mit verschiedenen Wrappern veröffentlicht. Neben Modulen zur Gesichtserkennung und anderen nützlichen Features befindet sich darin auch eine SIFT-Implementierung. • Von Sebastian Nowozin gibt es eine C#-Version des SIFT, LIBSift6 genannt. Diese SIFT-Version diente bei der Umsetzung auf der GPU als Referenzimplementierung. Eine ausführliche Beschreibung des SIFT erfolgt in Kapitel 3. 2.2 SIFT-Varianten und andere Featuredetektoren Da der SIFT sehr rechenintensiv und damit für viele Anwendungen zu langsam ist, gibt es einige Versuche diesen Algorithmus zu beschleunigen. Außerdem wurden Anstrengungen unternommen, andere Algorithmen zur Detektion von Keypoints und zur Berechnung von Feature-Vektoren zu entwickeln mit dem Ziel, ähnlich gute Ergebnisse bei geringerer Laufzeit zu erhalten. Daneben gibt es noch eine Reihe älterer Algorithmen zur Berechnung von Deskriptoren. 2.2.1 PCA-Sift Yan Ke und Rahul Sukthankar stellen in [Ke 04] als Alternative zu Lowes SIFT den PCA-SIFT vor. Dieser Algorithmus verwendet die Principal Component Analysis (PCA) zur Berechnung der Deskriptoren. Um Keypoints zu detektieren und ihre Subpixelposition und Orientierung zu berechnen, werden beim PCA-SIFT die selben Methoden angewendet wie beim Original-SIFT. Einzig und allein in der Berechnung der Deskriptoren unterscheiden sich die beiden Algorithmen. Beim PCA-SIFT wird zunächst ein Eigenraum berechnet. Dies muss nur einmal für ein zu untersuchendes Bild oder eine gesamte Bilderserie geschehen. Anschließend werden auf die selbe Weise wie beim SIFT Keypoints ermittelt. Für jeden Keypoint wird ein 41 × 41 Pixel großes, um den Keypoint zentriertes und entsprechend der berechneten Orientierung gedrehtes Fenster betrachtet. In diesem Fenster werden sowohl in horizontaler als auch in vertikaler Richtung Gradienten berechnet. Dies ergibt für jede der beiden Richtungen 39 × 39 Werte. Der berechnete Gradientenvektor hat damit eine Dimension von 2 × 39 × 39 = 3042. Dieser Vektor wird auf den zuvor berechneten Eigenraum, der eine viel geringere Dimension besitzt, projiziert. Eine sinnvolle Dimension des Eigenraumes und damit der gespeicherten Eigenvektoren ist 36, was eine enorme Verkleinerung gegenüber den Feature-vektoren des SIFT ist. Diese haben üblicherweise eine Dimension von 128.7 Durch diese Verkleinerung werden Speicherbedarf und Rechenaufwand beim Matchen erheblich reduziert. Inwieweit dies jedoch den größeren Rechenaufwand bei der Berechnung der Deskriptoren wieder aufwiegt, ist zumindest laut [Mikolajczyk 05] umstritten. 5 http://www.cs.wustl.edu/~rstancha/oss.php http://user.cs.tu-berlin.de/~nowozin/libsift/ 7 http://www.cs.cmu.edu/~yke/pcasift/ 6 2.2. SIFT-Varianten und andere Featuredetektoren 2.2.2 15 Integralbild Eine andere Idee ist, schon bei der Detektion der Keypoints Rechenzeit einzusparen, indem die Keypoints statt in einer aufwendig zu berechnenden Gausspyramide in einem Integralbild gesucht werden. Diesen Ansatz verfolgen sowohl Michael Grabner, Helmut Grabner und Horst Bischof mit ihrem Fast approximated SIFT [Grabner 06] als auch Herbert Bay, Zinne Tuytelaars und Luc Van Gool mit ihrem SURF [Bay 06]. 2.2.3 Reduced SIFT Features In [Ledwich 06] schlagen Luke Ledwich und Stefan Williams vor, bei der Berechnung der SIFT-Deskriptoren auf Orientierungen zu verzichten. Die Idee dahinter ist folgende: Solange die Keypoints nur im Gebäudeinnern benötigt werden, hat man es häufig mit planaren Flächen wie Wänden, Böden und Decken zu tun. Außerdem drehen sich die mit einer Kamera im Inneren aufgenommenen Bilder selten um die Kamera-Achse. Deshalb kann bei geringem Qualitätsverlust auf die Berechnung von Keypoint-Orientierungen verzichtet werden. Allerdings sind die dadurch gewonnenen Performancegewinne nicht besonders groß. 2.2.4 Andere Vereinfachungen Falls die berechneten Deskriptoren nur zum Erkennen bestimmter Objekte im Raum dienen sollen, finden sich einige andere Ansätze den Rechenaufwand zu minimieren. So schlagen Vincent Lepetit, Julien Pilet und Pascal Fua [Lepetit 04] vor, auf die aufwendige Berechnung affin invarianter Deskriptoren zu verzichten. Stattdessen wird das zu trackende Objekt aus unterschiedlichen Perspektiven betrachtet. Für jede der Perspektiven werden Keypoints und dazugehörige Deskriptoren ermittelt. Falls das zu trackende Objekt weitgehend planar ist, lassen sich die verschiedenen Ansichten einfach mittels unterschiedlicher affiner Transformationen berechnen, ansonsten wird ein 3-D-Modell benötigt. Beim eigentlichen Tracken wird ermittelt, aus welcher Ansicht ein Objekt betrachtet wird, und versucht die entsprechenden Deskriptoren zu matchen. 2.2.5 GLOH Abbildung 2.1: Aufteilung der Keypointumgebung beim GLOH und beim SIFT. In [Mikolajczyk 05] wird neben dem Vergleich verschiedener Deskriptoren auch der GLOH vorgestellt. Er ist der Versuch einen noch stabileren Deskriptor als den SIFT zu entwickeln. Wie beim SIFT werden auch hier Gradientenhistogramme als FeatureVektoren verwendet. Dabei werden die Unterregionen, an denen Histogramme ermittelt werden, anders eingeteilt als beim SIFT. Statt in ein Gitter mit 16 Unterregionen wird die Umgebung eines Keypoints in drei radiale Regionen aufgeteilt. Die beiden äußeren Regionen werden noch einmal in je acht Teile aufgeteilt. Dadurch erhält man 16 2. Verwandte Arbeiten hier 17 Histogramme. Jedes Histogramm hat beim GLOH anders als beim SIFT 16 statt 8 Bins. Daraus ergeben sich 17 × 16 = 272 Bins. Abbildung 2.1 zeigt die Aufteilung der Umgebung eines Keypoints beim GLOH im Vergleich zum SIFT. Dieser Deskriptor hat sich als stabiler erwiesen, allerdings hat er einen noch höheren Rechenaufwand als der SIFT und kommt deswegen für viele Echtzeitanwendungen mit der zur Zeit vorhandenen Hardware nicht oder noch nicht infrage. 2.3 SIFT auf schnellerer Hardware Neben Veränderungen des Algorithmus gibt es den Versuch durch besondere Hardware den SIFT-Algorithmus ausreichend schnell zu machen. 2.3.1 FPGA-Versionen In seiner Diplomarbeit [Chati 06] hat Harding Djakou Chati gezeigt, welche Schwierigkeiten die Umsetzung des SIFT-Algorithmus auf einem FPGA (Field Programmable Gate Array) bereitet. In [Se 04] präsentieren Stephen Se, Ho-Kong Ng, Piotr Jasiobedzki und Tai-Jing Moyung einen Roboter zur Exploration fremder Planeten. Auf diesem Roboter läuft eine FPGA-Version des SIFT. 2.3.2 SIFT auf der GPU Inzwischen existieren eine Reihe von Umsetzungen auf der Grafikkarte. So findet sich im OpenVIDIA-Projekt8 von James Fung [Fung 05a] eine abgespeckte Variante des SIFT, die auf der GPU läuft. Daneben haben Sebastian Heymann [Heymann 05a], Sudipta N. Sinha [Sinha 06a] und Cameron Upright [Upright 06] den SIFT für die Grafikkarte implementiert. Wegen ihrer Bedeutung für diese Arbeit werden die GPU-Implementierungen in Kapitel 5 näher erläutert. Von den hier erwähnten vier GPU-Lösungungen ist zum Zeitpunkt der Fertigstellung dieser Arbeit nur der Code des OpenVIDIA-Projekts zum Download verfügbar. 8 http://openvidia.sourceforge.net 3. Der SIFT-Algorithmus In diesem Kapitel wird der SIFT-Algorithmus vorgestellt. Dieser Algorithmus detektiert auf einem gegebenen Bild Keypoints und berechnet für diese Punkte charakteristische Deskriptoren. So lassen sich Keypoints von verschiedenen Bildern einander zuordnen. Der SIFT verwendet dabei keinerlei Farbinformationen. Deshalb müssen die Eingabebilder zur Verarbeitung erst in Graustufenbilder umgewandelt werden. 3.1 Algorithmus Der SIFT-Algorithmus in der verbesserten Variante aus dem Jahre 2004 [Lowe 04] lässt sich in vier aufeinander folgende Schritte aufteilen. 1. Ermittlung potentieller Keypoints Zunächst werden die potentiellen Keypoints ermittelt. Dafür wird eine Difference of Gaussian oder DoG-Pyramide aufgebaut und in ihr nach Maxima oder Minima gesucht. 2. Filterung und Subpixellokalisierung Anschließend werden diese Punkte nach gewissen Kriterien gefiltert. Für die verbleibenden Keypoints wird eine subpixel-genaue Position berechnet. 3. Berechnung der Keypoint-Orientierung Für jeden Keypoint wird eine Orientierung ermittelt. Falls ein Punkt mehr als eine dominante Richtung besitzt, werden weitere Keypoints an dieser Position mit unterschiedlichen Orientierungen angelegt. 4. Berechnung der Deskriptoren Als letztes wird für jeden verbliebenen Keypoint ein einzigartiger, charakteristischer Feature-Vektor berechnet. Dieser Feature-Vektor muss invariant gegenüber verschiedenen Änderungen sein. In den folgenden Abschnitten werden zunächst der grundlegende Ablauf und anschließend die vier verschiedenen Schritte ausführlich beschrieben. Dort, wo es notwendig ist, werden die erforderlichen mathematischen Grundlagen erläutert. 18 3. Der SIFT-Algorithmus 3.2 Pyramiden In den folgenden Abschnitten ist des öfteren von sogenannten Pyramiden die Rede. Diese Pyramiden werden aus verschiedenen Bildern aufgebaut. Eine Bildpyramide besteht dabei aus mehreren Oktaven, die wiederum aus einzelnen Stufen aufgebaut sind. Bilder der gleichen Größe bilden die Stufen einer Oktave. In welchem Verhältnis die Bilder einer Oktave sonst zueinander stehen, hängt von der Art der Pyramide ab. Auf Oktaven, die weit oben in der Pyramide liegen, sind die Bilder kleiner als auf Oktaven weiter unten. Stufe Oktave Abbildung 3.1: In Bildpyramiden bilden die Bilder einer Größe die Stufen einer Oktave. Abbildung 3.1 zeigt schematisch die Bilder einer Pyramide mit drei Oktaven. Jede Oktave besteht hier aus vier Stufen. 3.3 Ablauf In diesem Abschnitt wird noch einmal der genaue Ablauf des klassischen SIFT erläutert, da er die Grundlage für die GPU-Implementierung bildet. Allerdings gibt es auch große Unterschiede zwischen dem klassischen SIFT auf der CPU und der Variante auf der GPU. Wie in Abbildung 3.2 zu sehen ist, wird aus dem Eingangsbild zunächst die komplette Gausspyramide mit mehreren Oktaven aufgebaut. Aus der Gausspyramide werden anschließend die komplette DoG-Pyramide und die komplette Gradientenpyramide berechnet. Erst danach wird nach Keypoints in der DoG-Pyramide gesucht. Für diese Keypoints wird die Orientierung ermittelt. Mit Hilfe dieser Informationen können die Deskriptoren berechnet werden. 3.3. Ablauf 19 Eingangsbild Eingangsbild OriginalSIFT 0. DoG-Pyramide Gausspyramide 2. 1. Gradientenpyramide 3. Finden der Keypoints 4. Deskriptoren Orientierung 5. 6. Abbildung 3.2: Ablauf des SIFT-Algorithmus auf der CPU 20 3.4 3. Der SIFT-Algorithmus Ermitteln potentieller Keypoints Wichtig bei der Ermittlung der Keypoints ist, dass ihre Wahl invariant gegenüber verschiedenen Änderungen des Bildes ist. Damit später auch invariante Deskriptoren berechnet werden können, ist es notwendig neben der Position der Keypoints auch ihre Orientierung und Größe zu kennen. Die potentiellen Keypoints werden mittels eines DoG-Detektors bestimmt, wobei DoG für Difference of Gaussian steht. Dieser Detektor findet in erster Linie sogenannte Blobs als Keypoints. Blobs sind gleichförmige Regionen einer bestimmten Größe. Daneben werden aber auch Keypoints an Kanten und Ecken gefunden. Die Suche nach Keypoints mit einem DoG-Detektor lässt sich in zwei verschiedene Schritte aufteilen. Zunächst erfolgt der Aufbau einer sogenannten Gausspyramide. Diese Gausspyramide wird verwendet, um eine DoG-Pyramide, bestehend aus Differenzbildern, zu berechnen. In der DoG-Pyramide wird dann nach Extremstellen gesucht. Sowohl Gausspyramide als auch DoG-Pyramide werden noch zur Berechnung der Keypoint-Orientierungen und der Deskriptoren verwendet. In den nächsten Abschnitten werden der Aufbau der Gausspyramide, der Aufbau der DoG-Pyramide und das Finden der Keypoints beschrieben. Anschließend wird der zugrunde liegende mathematische Hintergrund erläutert. 3.4.1 Aufbau der Gausspyramide Abbildung 3.3: Gausspyramide mit drei verschiedenen Oktaven und sechs Stufen pro Oktave. Von der Stufe einer Oktave zur nächsthöheren Stufe gelangt man durch eine Gaussglättung. Die nächstkleinere Oktave wird durch die Verkleinerung der zweithöchsten Stufe der aktuellen Oktave erreicht. Zum Aufbau der sogenannten Gausspyramide wird das Eingangsbild mit verschiedenen Gaussmasken gefaltet. So entstehen mehrere gaussgeglättete Bilder. Diese Bilder bilden die Stufen der ersten Oktave der Pyramide. Anschließend wird eines der geglätteten Bilder auf ein Viertel seiner ursprünglichen Größe verkleinert. Auch dieses verkleinerte Bild wird mit verschiedenen Gaussmasken gefaltet. So entstehen die 3.4. Ermitteln potentieller Keypoints 21 Stufen der nächsten Oktave der Pyramide. Dieser Vorgang wird so oft wiederholt, bis die Bilder auf einer Pyramidenoktave eine minimale Größe erreicht haben oder bis ein anderes vorher festgelegtes Abbruchkriterium erreicht wurde. Zum Verkleinern des Bildes kann einfach jeder zweite Pixel in x- und in y-Richtung verwendet werden. Dies ist möglich, da durch die Gaussglättung in jedem Pixel auch Informationen der Nachbarpixel stecken. Deshalb ist eine zusätzliche Interpolation beim Verkleinern nicht mehr notwendig. Gaussfaltungen Abbildung 3.4: Durch verschiedene Gaussfaltungen wird das Originalbild geglättet. Das Glätten des Bildes erfolgt durch eine Gaussfaltung. Das zu glättende Bild wird mit der Gaussfunktion G gefaltet. Dabei gilt: G(σ, x, y) = 1 − x2 +y2 2 e 2σ 2πσ 2 (3.1) Das geglättete Bild Iˆ wird damit folgendermaßen aus dem Originalbild I berechnet. Für den Pixel an der Stelle (x0 , y0 ) gilt: ˆ 0 , y0 ) = I(x ∞ X ∞ X G(σ, x, y) · I(x0 + x, y0 + y) (3.2) x=−∞ y=−∞ ˆ 0 , y0 ). Je weiter ein Pixel von x0 , y0 entfernt ist, desto weniger Einfluss hat er auf I(x Dabei ist der Einfluss von Pixeln, die weiter als 3σ entfernt sind, minimal. Daher kann die Faltungsoperation durch eine Gaussmaske der Größe 3σ realisiert werden. Für Pixel die weniger als 32 σ Pixel vom Rand entfernt liegen, müssen andere Gaussmasken verwendet werden. Bei der Gaussfaltung handelt es sich um einen separierbaren Filter. Deshalb können statt einer zweidimensionalen Faltung auch zwei eindimensionale Faltungen verwendet werden. Bei einer Gaussmaskengröße von n verringert sich so der Rechenaufwand von n2 auf 2n Operationen pro Pixel. Abbildung 3.5 zeigt verschiedene eindimensionale Gaussfunktionen mit den im SIFT verwendeten Standardabweichungen. 22 3. Der SIFT-Algorithmus 0.08 G1 G2 G3 G4 G5 G6 0.07 0.06 0.05 0.04 0.03 0.02 0.01 0 -10 -5 0 5 10 Abbildung 3.5: Verschiedene eindimensionale Gaussfunktionen Zum Glätten verwendete σ Sei s eine natürliche Zahl (wie sich herausstellen wird, die Anzahl der Stufen der später berechneten DoG-Pyramide, auf denen nach Keypoints gesucht wird), dann müssen s+3 Faltungen vorgenommen und somit auch s+3 Stufen der Gausspyramide s+2 1 berechnet werden. Diese Faltungen werden mit σ, 2 s σ, . . . , 2 s σ vorgenommen. Es hat sich gezeigt, dass s = 3 ein sinnvoller Wert ist. Das zu verzerrende Bild√wird also mit Standardabwei√ den √ 5 √ 2 Gaussfunktionen mit 4 chungen σ1 = σ, σ2 = 3 2σ, σ3 = 3 2 σ, σ4 = 2σ, σ5 = 3 2 σ und σ6 = 3 2 σ gefaltet. √ Jedes Bild in der Gausspyramide ist mit der 3 2-fachen Standardabweichung wie sein direkter Vorgänger in der Gausspyramide gefaltet. σn+1 = √ 3 2σn (3.3) Die zum Falten verwendeten Gaussmasken sollten mindestens die dreifache Größe der Standardabweichungen haben. Deshalb werden die Masken insbesondere für die letzten Faltungen relativ groß. Dies kann vermieden werden, indem die Faltungen rekursiv vorgenommen werden. Statt das Eingangsbild I0 mit fünf verschiedenen Sigmas zu falten, wird das n + 1-te Bild In+1 durch Faltung des n-ten Bildes In berechnet. Sei G(σ̂) eine Faltung mit σ̂ und G(σ̃) eine Faltung mit σ̃. Dann gilt für das Hintereinanderanwenden beider Faltungen: √ (3.4) G(σ̂) · G(σ̃) = G( σ̂ 2 + σ̃ 2 ) Hier ist σ̂ = σn und Gesucht ist σ̃ q σ̂ 2 + σ̃22 = σn+1 = √ 3 2σn . 3.4. Ermitteln potentieller Keypoints 23 √ 2 3 ⇒ σ̂ 2 + σ̃ 2 = 2 σ̂ 2 √ 3 σ̃ 2 = ( 22 − 1)σ̂ 2 q √ 2 3 σ̃ = 2 − 1σ̂ In+1 wird durch eine Verzerrung von In mit Standardabweichung σn0 erzeugt. Dabei gilt für σn0 : σ00 = σ σ10 σn0 q = .. . √ 3 q = q = 2 2 −1 √ 3 √ 3 2 !n 2 −1 σ 2 0 2 − 1σn−1 Die Gaussbilder können also rekursiv aus anderen Gaussbildern berechnet werden. 0 aus In−1 entstanden ist, erhält man In+1 durch Wenn In durch die Glättung mit σn−1 q√ 2 3 0 Glättung von In mit 2 − 1 · σn−1 . 3.4.2 Die DoG-Pyramide Abbildung 3.6: Erzeugen eines Differenzbildes aus zwei benachbarten Bildern aus der Gausspyramide 24 3. Der SIFT-Algorithmus Nach dem Aufbau der Gausspyramide wird eine Pyramide aus Differenzbildern berechnet. Die Stufen dieser sogenannten DoG-Pyramide sind aus der Differenz zweier benachbarter Gaussbilder entstanden. Sei In das mit σn geglättete Bild und In+1 das mit σn+1 geglättete Bild. Dann gilt für das Differenzbild Dn folgendes: Dn (x, y) = In+1 (x, y) − In (x, y) (3.5) Abbildung 3.6 zeigt zwei benachbarte, geglättete Bilder der Gausspyramide, aus denen ein Differenzbild berechnet wird. 3.4.3 Finden potentieller Keypoints Abbildung 3.7: Auf der mittleren Stufe wird nach Keypoints gesucht. Ihr Wert muss entweder größer oder kleiner sein als der all seiner Nachbarn auf der eigenen Stufe und auf den benachbarten Stufen. Um die Positionen potentieller Keypoints zu ermitteln, werden die Maxima und Minima in der DoG-Pyramide gesucht. Dabei werden alle Stufen mit Ausnahme der ersten und letzten Stufe einer Oktave durchsucht. 3.4. Ermitteln potentieller Keypoints 25 Jeder Pixel auf den untersuchten DoG-Bildern wird mit seinen acht Nachbarn verglichen. Um Keypoint zu sein muss er echt größer oder echt kleiner als alle diese Nachbarpixel sein. Es muss also gelten: Dn (x, y) < min{Neighbors(Dn , x, y)} (3.6) Dn (x, y) > max{Neighbors(Dn , x, y)} (3.7) oder mit Neighbors(Dn , x, y) = [ {Dn (x + i, y + j)} (3.8) i,j∈{−1,0,1},i6=j Außerdem findet ein Vergleich mit den Nachbarpixeln auf der darüberliegenden und der darunterliegenden Stufe statt. Sei o.B.d.A. D(x, y) größer als seine Nachbarn auf der gleichen Stufe. Dann muss zusätzlich gelten: Dn (x, y) > max{Neighbors(Dn+1 , x, y), Dn+1 (x, y)} (3.9) Dn (x, y) > max{Neighbors(Dn−1 , x, y), Dn−1 (x, y)} (3.10) und Nur Pixel, die echte lokale Maxima oder Minima in der Gausspyramide sind, haben die Chance als Keypoints detektiert zu werden. Diese Punkte werden weiteren Filterungen unterworfen. Abbildung 3.7 zeigt drei benachbarte Bilder der DoG-Pyramide. Auf dem mittleren Bild wird nach potentiellen Keypoints gesucht. Die gefundenen Keypoints sind rechts zu sehen. Abbildung 3.8: Der Punkt x wird mit seinen Nachbarn auf der gleichen Stufe und auf den beiden Nachbarstufen verglichen. Diese Punkte sind hier durch einen grünen Kreis angedeutet. Nur wenn x echt größer oder echt kleiner als alle seine Nachbarn ist, kann er Keypoint sein. [Lowe 04] 26 3. Der SIFT-Algorithmus In Abbildung 3.8 ist der Vergleich eines Punktes mit seinen Nachbarpunkten zu sehen. Der Punkt x wird hier mit seinen Nachbarn auf der gleichen Stufe und mit denen der benachbarten Stufen verglichen. 3.4.4 Mathematischer Hintergrund des DoG-Detektors Hinter der Berechnung eines DoG-Bildes durch die Differenz zweier Gaussbilder steckt eigentlich die Faltung mit einer dreidimensionalen DoG-Funktion h. Sollen nur die Positionen der Keypoints zu berechnet werden, ist die direkte Faltung mit verschiedenen DoG-Funktionen der einfachere und schnellere Weg um die DoGPyramide aufzubauen. Allerdings werden die Gaussbilder sowohl für die Orientierungsberechnung als auch für die Berechnung der Deskriptoren benötigt. Deshalb ist hier der Bau in zwei Schritten der bessere Weg. Das Eingangsbild wird bei der direkten Berechnung der DoG-Pyramide mit einer DoG-Funktion h gefaltet. Diese Funktion ist die Differenz aus zwei verschiedenen Gaussfunktionen. Seien Gn und Gn+1 Gaussfunktionen, mit denen das Eingangsbild gefaltet wird, um die zwei benachbarten Gaussbilder In und In+1 zu erhalten. Dann lässt sich Dn = In+1 − In auch durch die direkte Faltung des Eingangsbildes mit der DoGFunktion hn berechnen. Für hn gilt: hn (x, y) = Gn+1 (x, y) − Gn (x, y) (3.11) h(x,y) 0.03 0.025 0.02 0.015 0.01 0.005 0 -0.005 -10 -5 10 5 0 0 5 -5 10 -10 Abbildung 3.9: Zweidimensionale DoG-Funktion Abbildung 3.9 zeigt eine zweidimensionale DoG-Funktion h. Mit dieser Funktion kann das Eingangsbild gefaltet werden. Im Ergebnisbild wird dann nach Extremstellen gesucht. Diese entstehen hauptsächlich an Stellen im Bild, an denen sich gleichförmige Flächen von der Größe der DoG-Glocke befinden. Diese Stellen heißen Blobs. 3.5. Filterung und Subpixellokalisierung 27 Für die Suche nach Keypoints wird das Eingangsbild mit mehreren verschiedenen DoG-Funktionen gefaltet und auf mehreren Bildern nach Extremstellen gesucht. Abbildung 3.10 zeigt die verschiedenen DoG-Funktionen. 0.03 h1 h2 h3 h4 h5 0.025 0.02 0.015 0.01 0.005 0 -0.005 -10 -5 0 5 10 Abbildung 3.10: Verschiedene eindimensionale DoG-Funktionen 3.5 Filterung und Subpixellokalisierung Einige der gefundenen Punkte sind als Keypoints ungeeignet und müssen deshalb wieder verworfen werden. Zusätzlich sollen die verbliebenen Keypoints subpixelgenau lokalisiert werden. 3.5.1 Filterung von Punkten mit niedrigem Kontrast Alle potentiellen Keypoints müssen einen gewissen betraglichen Mindestwert threshold besitzen. So wird verhindert, dass zufällige, durch Bildrauschen entstandene Peaks als Keypoints gefunden werden. Durch die Gaussglättung haben solche Punkte nur einen betraglich sehr kleinen Wert auf den DoG-Bildern und werden verworfen. Sei an der Stelle (x, y) ein potentieller Keypoint. Dann muss gelten: Dn (x, y) > threshold (3.12) Andernfalls wird dieser Punkt verworfen. Im LibSIFT beträgt der Wert von threshold 0,0075. 3.5.2 Kanteneliminierung Der DoG-Keypointdetektor findet auch Keypoints an im Bild auftretenden Kanten. Diese Keypoints sind allerdings ziemlich instabil. So unterscheidet sich ein auf einer Kante gefundener Keypoint häufig kaum von anderen Punkten dieser Kante. Auch 28 3. Der SIFT-Algorithmus die Umgebungen unterschiedlicher Punkte einer Kante unterscheiden sich relativ wenig. Dadurch sind die Deskriptoren für verschiedene Punkte der selben Kante gleich oder zumindest sehr ähnlich. Deshalb sollten diese Punkte wieder verworfen werden. Deswegen wird für jeden potentiellen Keypoint überprüft, ob er auf einer Kante liegt oder nicht. Charakteristisch für Punkte auf einer Kante sind ihre Hauptkrümmungen. Ein Kantenpunkt besitzt eine relativ große Hauptkrümmung κ1 quer über die Kante. Die zweite Hauptkrümmung κ2 dagegen verläuft entlang der Kante und ist daher sehr klein. Das Verhältnis der beiden Hauptkrümmungen κκ21 kann deshalb verwendet werden, um zu unterscheiden, ob ein Punkt an einer Kante liegt oder nicht. Das Verhältnis der Hauptkrümmungen lässt sich mit Hilfe der Hesse-Matrix H berechnen. H := Dxx Dxy Dxy Dyy ! (3.13) Dabei sind Dxx , Dyy und Dxy die zweifachen Ableitungen des Differenzbildes auf der entsprechenden Stufe in x-, in y- bzw. in x- und y-Richtung. Für den Punkt (x, y) gilt: Dxx = D(x + 1, y) + D(x − 1, y) − 2 · D(x, y) Dyy = D(x, y + 1) + D(x, y − 1) − 2 · D(x, y) 1 · ((D(x + 1, y + 1) − D(x + 1, y − 1)) − (D(x − 1, y + 1) − D(x − 1, y − 1))) Dxy = 4 Die Eigenwerte der Hesse-Matrix sind proportional zu den Hauptkrümmungen. Somit ist das Verhältnis der beiden Hauptkrümmungen gleich dem Verhältnis der beiden Eigenwerte. Als Kriterium ist genau dieses Verhältnis entscheidend. Seien α und β die beiden Eigenwerte von H. Dann gilt: spur(H) = Dxx + Dyy = α + β (3.14) det(H) = Dxx Dyy − (Dxy )2 = αβ (3.15) Sei o.B.d.A. α ≤ β. Sei außerdem r das Verhältnis zwischen den beiden Hauptkrümmungen, d.h. κ1 = rκ2 bzw. α = rβ mit r ≤ 1. Dann gilt: (r + 1)2 spur(H)2 (α + β)2 (rβ + β)2 = = = det(H) αβ rβ 2 r (3.16) Also hängt das Verhältnis von spur(H)2 und det(H) nur von dem Verhältnis der 2 beiden Hauptkrümmungen ab. Dabei ist (r+1) für r = 1 minimal, d.h also genau r dann, wenn beide Hauptkrümmungen gleich groß sind. Je kantenartiger ein Punkt ist, desto größer wird dieser Term. Deshalb wird für jeden potentiellen Keypoint überprüft, ob 3.5. Filterung und Subpixellokalisierung 29 spur(H)2 (r̂ + 1)2 < det(H) r̂ (3.17) für ein Grenzverhältnis r̂. Andernfalls wird dieser Punkt verworfen. Als geeignete Grenze hat sich laut [Lowe 04] r̂ = 10 herausgestellt. Im LibSIFT wird r̂ = 20 standardmäßig verwendet. Dadurch erhält man hier mehr Keypoints. 3.5.3 Subpixellokalisierung Die im SIFT verarbeiteten Bilder sind immer diskret. Die kleinste ansprechbare Einheit ist hier ein Pixel. Daher kann es vorkommen, dass die gefundenen Keypoints in einer kontinuierlichen Welt irgendwo zwischen vier benachbarten Pixeln liegen würden. Bei der Subpixellokalisierung wird versucht genau diese, nicht an das diskrete Raster der Pixel gebundene Position der Keypoints zu finden. In [Lowe 99] wurden die Keypoints nur pixelgenau lokalisiert. Eine Subpixellokalisierung war damals noch nicht vorgesehen. Allerdings hat sich gezeigt, dass die Qualität der Ergebnisse durch eine genauere Lokalisierung stark ansteigt. Besonders dann, wenn die Keypoints auf höheren Oktaven der DoG-Pyramide gefunden werden, ist eine Subpixellokalisierung notwendig. Ein Pixel im verkleinerten DoG-Bild umfasst hier einen Bereich, der von mehreren Pixeln im Originalbild abgedeckt wird. Damit die auf höheren Oktaven gefundenen Keypoints sinnvoll verwendet werden können, ist eine Subpixellokalisierung unvermeidlich. Mittels Subpixellokalisierung lässt sich die genaue Lage eines Keypoints innerhalb der DoG-Pyramide ermitteln. Neben der exakten Position in x- und y-Richtung wird auch die genaue Zwischenstufe in der Pyramide und damit die Größe des Keypoints ermittelt. Dafür wird versucht eine dreidimensionale Parabel durch die Nachbarpunkte des Keypoints in der DoG-Pyramide zu legen. Die genaue Lage des Keypoints ist durch die Lage des Maximums der Parabel in der DoG-Pyramide gegeben. Die Subpixellokalisierung erfolgt durch das Lösen eines linearen Gleichungssystems. Dafür wird eine dreidimensionale Hesse-Matrix mit gemischten, zweifachen Ableitungen gebildet. Zu lösen ist das Gleichungssystem Hx = d, wobei der Vektor d die einfachen Ableitungen in alle drei Richtungen enthält. A1 A2 A3 H := B1 B2 B3 C1 C2 C3 (3.18) mit A1 = Dn−1 (x, y) − 2 · Dn (x, y) + Dn+1 (x, y) 1 A2 = · (Dn+1 (x, y + 1) − Dn+1 (x, y − 1) − Dn−1 (x, y + 1) + Dn−1 (x, y − 1)) 4 1 A3 = · (Dn+1 (x + 1, y) − Dn+1 (x − 1, y) − Dn−1 (x + 1, y) + Dn−1 (x − 1, y)) 4 1 B1 = · (Dn+1 (x, y + 1) − Dn+1 (x, y − 1) − Dn−1 (x, y + 1) + Dn−1 (x, y − 1)) 4 30 3. Der SIFT-Algorithmus B2 = Dn (x, y − 1) − 2 · Dn (x, y) + Dn (x, y + 1) 1 B3 = · (Dn (x + 1, y + 1) − Dn (x − 1, y + 1) − Dn (x + 1, y − 1) + Dn (x − 1, y − 1)) 4 1 C1 = · (Dn+1 (x + 1, y) − Dn+1 (x − 1, y) − Dn−1 (x + 1, y) + Dn−1 (x − 1, y)) 4 1 C2 = · (Dn (x + 1, y + 1) − Dn (x − 1, y + 1) − Dn (x + 1, y − 1) + Dn (x − 1, y − 1)) 4 C3 = Dn (x − 1, y) − 2 · Dn (x, y) + Dn (x + 1, y) Dn+1 (x, y) − Dn−1 (x, y) 1 := D d n (x, y + 1) − Dn (x, y − 1) 2 Dn (x + 1, y) − Dn (x − 1, y) (3.19) Die Lösung des Gleichungssystems Hx = d enthält die Korrekturen in alle drei Richtungen. Die Lösung x hat dabei folgende Form: xT = (x̂, ŷ, ŝ). So wird die tatsächliche Lage der lokalen Extremstelle in der Pyramide gefunden. Falls die Korrektur in eine der Richtungen größer als 0,5 ist, liegt die Extremstelle näher an einem anderen Pixel als an den bisher detektierten. Deshalb wird die subpixelgenaue Lokalisierung an der entsprechenden benachbarten Position noch einmal vorgenommen. Anschließend wird wieder überprüft, ob die nun ermittelte Position der Extremstelle näher an einem anderen Pixel liegt. Sind die Korrekturen insgesamt zu groß oder in mehr als einer Richtung größer als 0,5, wird der Keypoint als zu instabil verworfen. Auch wenn zuviele Korrekturschritte notwendig sind, kann der Keypoint nicht verwendet werden. In günstigen Fällen konvergiert dieses Verfahren sehr schnell gegen die korrekte subpixelgenaue Position in der DoG-Pyramide. 3.6. Berechnung der Orientierung 3.6 31 Berechnung der Orientierung 3.6.1 Gradientenberechnung Abbildung 3.11: Berechnung der Gradientengröße und Richtung jedes Pixels. Da die berechneten Deskriptoren invariant gegenüber Drehungen sein sollen, muss für jeden Keypoint dessen Orientierung berechnet werden. So kann später die Berechnung der Deskriptoren abhängig von der Orientierung des Keypoints erfolgen. Dadurch werden diese Deskriptoren rotationsinvariant, wie in Kapitel 3.7.1 beschrieben ist. Für ein gaussgeglättetes Bild wird zunächst der Gradient eines jeden Pixels berechnet. Dieser setzt sich aus einer Magnitude m und einer Richtung θ zusammen, wobei θ ein Winkel zwischen 0 Grad und 360 Grad ist. Sei I das Bild, für das der Gradient berechnet werden soll. Dann gilt für den Gradienten an der Stelle (x, y): q (I(x + 1, y) − I(x − 1, y))2 + (I(x, y + 1) − I(x, y − 1))2 (3.20) θ(x, y) = arctan2(I(x, y + 1) − I(x, y − 1), I(x + 1, y) − I(x − 1, y)) (3.21) m(x, y) = Abbildung 3.11 zeigt ein gaussgeglättetes Bild und seine Gradienten, bestehend aus Gradientengröße und Gradientenrichtung. 3.6.2 Orientierungshistogramme Um nun die Orientierung eines Keypoints an der Stelle (x, y) zu berechnen, wird ein Histogramm über die Gradienten der umliegenden Region erstellt. Dieses Histogramm enthält 36 Bins, die Gradientenrichtungen zwischen 0 und 360 Grad in 10-Grad-Schritten abdecken. 32 3. Der SIFT-Algorithmus Abbildung 3.12: Berechnung der Orientierung eines jeden Keypoints Der Einfluss eines jeden Samples wird mit seiner Magnitude gewichtet. Zusätzlich wird er noch gaussgewichtet, abhängig von seinem Abstand zum Keypoint. Die dafür verwendete Gaussfunktion g wird mit einem σ mit dem 1,5-fachen Wert der Stufe des Keypoints berechnet. Für jeden Punkt in der Umgebung des Keypoints wird die Orientierung θ verwendet um zu entscheiden, welchen Bin dieser Punkt beeinflusst. Der Einfuss auf diesen Bin ergibt sich aus der Gradientengröße m, die zusätzlich mit der Gaussfunktion g gewichtet wird. Sei an (x, y) ein Keypoint. Dann beeinflusst der Punkt (x + ∆x , y + ∆y ) das Histogramm. Sei dabei bin(i) das Bin, welches aufgrund der Orientierung m(x+∆x , y+∆y ) ermittelt wurde. Dann gilt für den neuen Wert des Bins: ˆ bin(i) = bin(i) + g(∆x , ∆y ) · θ(x + ∆x , y + ∆y ) (3.22) In Abbildung 3.13 ist ein solches Histogramm zu sehen. Glätten des Histogramms Das so ermittelte Histogramm wird anschließend mehrfach geglättet, indem jeder Bin mit seinen beiden Nachbarbins linear interpoliert wird. Dadurch werden zum 3.6. Berechnung der Orientierung 33 Abbildung 3.13: Ungeglättetes Histogramm zur Orientierungsberechnung einen Fehler bei der Einordnung ins richtige Histogramm beseitigt. Zum anderen wird dadurch der Fall vermieden, dass sich zwischen zwei Bins mit maximalem Wert ein Bin mit niedrigerem Wert befindet. Dadurch könnte dort keine Orientierung berechnet werden. Im LibSIFT werden vier Glättungsschritte vorgenommen. Seien bin0 (i − 1), bin0 (i) und bin = (i + 1) drei benachbarte, ungeglättete Bins, dann gilt für den einmal geglätteten Bin bin1 (i) an der Stelle i: bin1 (i) = bin0 (i − 1) + bin0 (i) + bin0 (i + 1) 3 (3.23) Analog gilt für k ∈ {1, 2, 3}: bink+1 (i) = bink (i − 1) + bink (i) + bink (i + 1) 3 (3.24) Abbildung 3.14: Geglättetes Histogramm zur Orientierungsberechnung Die Orientierung eines Punktes befindet sich in der Nähe des Maximums im Histogramm. Sollte es mehrere dominante Orientierungen geben, werden zusätzliche Keypoints erzeugt. Für alle Bins, deren Wert mindestens 80% des größten Wertes beträgt und die selbst lokale Maxima sind, wird ein neuer Keypoint mit derselben Position und Größe aber mit anderer Orientierung erzeugt. Abbildung 3.14 zeigt ein Histogramm, das durch viermaliges Glätten aus dem in Abbildung 3.13 dargestellten, ungeglätteten Histogramm berechnet wurde. 34 3. Der SIFT-Algorithmus Genaue Ermittlung der Orientierung Um die genaue Orientierung eines Keypoints zu berechnen, werden neben dem gerade ermittelten Bin auch seine beiden Nachbarbins betrachtet, um so ein genaueres Ergebnis zu erhalten. Durch diese drei Bins wird eine Parabel gelegt. Die Extremstelle dieser Parabel liegt an der exakten Orientierung des Keypoints. Abbildung 3.15: Berechnung der genauen Orientierung eines Keypoints. Am Bin für 20 Grad befindet sich ein Maximum. Durch die Werte der Bins für 10, 20 und 30 Grad wird eine Parabel gelegt. Die Orientierung des Keypoints befindet sich am Maximum der Parabel, hier an 17 Grad. Abbildung 3.15 zeigt drei benachbarte Bins. Der mittlere Bin ist ein globales Maximum oder zumindest ein Bin, für den ein neuer Keypoint angelegt wurde. Durch die Werte dieser drei Bins lässt sich eindeutig eine Parabel vom Grad 2 legen. Das Maximum dieser Parabel und damit die Orientierung des untersuchten Keypoints liegt hier bei 17 Grad. 3.7. Berechnung der Deskriptoren 3.7 35 Berechnung der Deskriptoren Abbildung 3.16: Berechnung der Deskriptoren Bisher wurden Keypoints gefunden und ihre Größe und Orientierung berechnet. Nun sollen für diese Punkte charakteristische Feature-Vektoren oder Deskriptoren berechnet werden. Dies ist in Abbildung 3.16 angedeutet. Für die Berechnung wird die Umgebung der Keypoints betrachtet. In einer Umgebung um den gefundenen Keypoint werden wie schon zur Ermittlung der Orientierung die Gradienten, bestehend aus Magnitude m und Richtung θ, eines gaussgeglätteten Bildes ermittelt. Welches Bild aus der Gausspyramide dafür verwendet wird, ist abhängig von der Größe des Keypoints. Die Gradienten können schon beim Bau der Gausspyramide vorberechnet werden. Diese Umgebung umfasst üblicherweise 16 × 16 Pixel und ist entsprechend der Orientierung des Keypoints gedreht. Sie wird in sechzehn Unterregionen der Größe 4×4 aufgeteilt. Für jede dieser sechzehn Unterregionen wird ein Gradientenhistogramm erstellt. Ein Histogramm besteht aus acht Bins für die verschiedenen Gradientenrichtungen. Wie bei der Berechnung der Orientierung wird hier jeder Eintrag mit seiner Magnitude 36 3. Der SIFT-Algorithmus gewichtet. Außerdem werden die Einträge auch hier zusätzlich mit einer Gaussfunktion gewichtet. So beeinflusst eine Veränderung in weit vom Keypoint entfernten Punkten den Deskriptor nur minimal. Die insgesamt sechzehn Histogramme mit Einträgen in jeweils acht Bins bilden einen 128-dimensionalen Vektor, der nach einer Normalisierung als Deskriptor dient. Die einzelnen Schritte werden nun noch einmal ausführlich erwähnt. 3.7.1 Drehung der Umgebung Um jeden subpixelgenauen Keypoint wird eine Umgebung mit 16 × 16 Stellen, die in die verschiedenen Histogramme einfließen sollen, gelegt. Um tatsächlich rotationsinvariante Deskriptoren berechnen zu können, wurde die genaue Orientierung jedes Keypoints ermittelt. Um diese Orientierung wird die Umgebung gedreht. Dadurch treffen diese Stellen nicht immer genau den Mittelpunkt eines Pixels. Deshalb muss der Wert für das Histogramm zwischen den nächstliegenden Pixeln interpoliert werden. Orientierung 30° Keypoint Orientierung 0° Keypoint Abbildung 3.17: Drehung der Keypoint-Umgebung bei Orientierungen von 0 Grad und 30 Grad Abbildung 3.17 zeigt, wie die Umgebung zweier Keypoints abhängig von der KeypointOrientierung gedreht wird. Der eine Keypoint hat eine Orientierung von 0 Grad. Deshalb muss die Umgebung, die aus 16 Unterregionen besteht, nicht gedreht werden. Der andere Keypoint besitzt eine Orienierung von 30 Grad. Deshalb ist hier die Umgebung entsprechend gedreht. 3.7.2 Berechnung der Histogramme Zur Berechnung des Histogramms wird die gedrehte Umgebung in 16 Subregionen aufgeteilt. In jeder Subregion werden sechzehn Stellen betrachtet um das Gradientenhistogramm zu berechnen. Dieses Histogramm besitzt acht Bins. Nachdem an einer Stelle der Gradient berechnet wurde, werden die beiden Bins ermittelt, die dadurch beeinflusst werden. Dazu wird von der Gradientenorientierung an dieser Stelle die Orientierung des Keypoints abgezogen und verglichen zwischen welchen beiden Bins dieser Wert liegt. 3.7. Berechnung der Deskriptoren 37 Sei θ der Wert der Gradientenorientierung an der besagten Stelle und sei α die Orientierung des Keypoints. Dann wird zur Auswahl der Bins die Gradientenorientierung relativ zum Keypoint, hier mit θ̃ bezeichnet, verwendet. Es gilt: θ̃ = θ − α (3.25) Durch diese Differenz wird die Rotation des Keypoints rückgängig gemacht. Seien desweiteren βi und βi+1 die Winkel zu den Bins bin(i) und bin(i + 1). Dann werden diese beiden Bins beeinflusst, falls gilt: βi ≤ θ̃ < βi+1 (3.26) Der Gesamteinfluss ω, den eine Stelle auf die beiden Bins hat, ist das Produkt aus Gradientengröße m und einem Gaussgewicht w. Dieser Einfluss wird linear auf die beiden Bins aufgeteilt, abhängig vom Wert der relativen Gradientenorientierung verglichen mit den beiden Binwinkeln. Bei insgesamt acht Bins und damit einer Abdeckung jedes Bins von π4 gilt für neuen Werte ˆ ˆ + 1): der Bins bin(i) und bin(i θ̃ − βi ˆ bin(i) = bin(i) + ω · π (3.27) 4 ˆ + 1) = bin(i + 1) + ω · (1 − θ̃ − βi ) bin(i π (3.28) 4 Erst durch die Interpolation zwischen zwei Bins werden die Histogramme und damit der gesamte Deskriptor unanfällig gegenüber kleineren Ungenauigkeiten in der Orientierung. So werden alle sechzehn Histogramme berechnet. Um keine zu abrupten Brüche zwischen den verschiedenen Histogrammen eines Deskriptors zu haben, beeinflussen die Samples am Rand einer Subregion zusätzlich auch noch die Bins des Histogramms einer benachbarten Subregion. Dadurch wird das Histogramm stabiler gegenüber Ungenauigkeiten bei der genauen Positionierung. Durch die Berechnung der sechzehn Histogramme erhält man einen ersten Vektor, der allerdings noch normiert werden muss, um geeigneter Deskriptor zu sein. Abbildung 3.18 zeigt die Berechnung der Deskriptoren. Aus einer 16 × 16 Region um den Keypoint werden die Gradienten berechnet. Die Gradientenmagnituden werden mit einer zweidimensionalen Gaussfunktion geglättet, hier als Kreis angedeutet. Für jede der sechzehn Unterregionen wird ein Histogramm mit je acht Bins berechnet. 3.7.3 Normalisieren Damit der Deskriptor invariant gegenüber Helligkeitsveränderungen des Bildes ist, muss der bisher berechnete Histogrammvektor normalisiert werden. Sei d˜ := (d˜1 , . . . , d˜128 )T der noch unnormierte Histogrammvektor. Dann gilt für den normierten Deskriptor d folgendes: d= d˜ ˜ kdk (3.29) 38 3. Der SIFT-Algorithmus Abbildung 3.18: Hier ist die Berechnung eines Deskriptor zu sehen. Für jeden Keypoint werden die Gradientenmagnituden und Gradientenrichtungen in einer 16 × 16 Pixel großen Umgebung berechnet. Die Magnituden werden mit einer zweidimensionalen Gausskurve geglättet, die hier durch den Kreis angedeutet ist. [Heymann 05a] wobei v u 128 uX ˜ kdk := t d˜2i (3.30) i=1 und die Division komponentenweise durchgeführt wird. So wird der Anteil jedes Eintrags am Deskriptor berechnet. Allerdings sollen dabei einzelne Einträge keinen zu großen Einfluss bekommen. Aus diesem Grund werden alle Einträge, die größer als der vorher festgelegte Wert cap sind, auf diesen Wert gekappt. Anschließend muss der Vektor noch ein zweites mal normiert werden. Es gilt also eigentlich zunächst: d̃i dˆi = min( , cap) ˜ kdk (3.31) Für den endgültigen Deskriptor gilt dann: d= dˆ ˆ kdk (3.32) Im LibSIFT hat cap den Wert 0,2. Durch die Normierung erhält man einen 128-dimensionalen Vektor, der als Deskriptor verwendet werden kann. 4. GPGPU Moderne Grafikkarten sind inzwischen handelsüblichen CPUs in bezug auf ihre Rechenleistung oftmals überlegen. Deshalb gibt es das Bestreben, sehr rechenintensive Algorithmen auf die Grafikkarte auszulagern. Dieses unter dem Namen General Purpose Computing on Graphics Processing Units (kurz GPGPU ) bekannte Konzept erlangte erstmals im Jahre 2004 auf der Konferenz siggraph2004 größere Bekanntheit. Dort wurde in einem gleichnamigen Kurs1 die Möglichkeit präsentiert, auch Anwendungen, die nichts oder nur indirekt mit Grafikberechnung zu tun haben, auf der Grafikkarte berechnen zu lassen. Allerdings lassen sich Algorithmen nicht eins zu eins von der CPU auf die GPU übertragen. In diesem Kapitel wird die prinzipielle Arbeitsweise von Grafikkarten und ihre Anwendungsmöglichkeit für Nicht-Grafik-Algorithmen besprochen. Dabei wird speziell auf die bestehenden Einschränkungen und die daraus entstehenden Probleme eingegangen. 4.1 Allgemeine Informationen Seit Mitte der neunziger Jahre hat eine rasante Entwicklung der Grafikkarten stattgefunden. Sie wurden immer schneller und auch leistungsfähiger. Neben immer mehr festverdrahteten Funktionalitäten wurden auch Möglichkeiten zum flexibleren Einsatz der Grafikkarten geschaffen. Seit der zu Beginn des Jahres 2001 erschienenen GeForce 3 gibt es freiprogrammierbare Vertexshader und Fragmentshader. Damit war die Voraussetzung für GPGPU geschaffen. Viele nützliche Informationen zum Thema GPGPU finden sich auch im Netz. So bietet Dominik Göddeke auf seiner Homepage2 mehrere Tutorials an. Daneben bietet das GPGPU -Forum3 Einsteigern gute Hilfe. 1 http://www.gpgpu.org/s2004/ http://www.mathematik.uni-dortmund.de/~goeddeke/ 3 http://www.gpgpu.org/forums/ 2 40 4.2 4.2.1 4. GPGPU Die Grafikkarte Das Stream Modell Wie John Owens in [Owens 05] beschreibt, arbeiten Grafikkarten nach dem sogenannten Stream Programming Model. Dieses Modell wird hier kurz erläutert. Ein Stream ist eine Datenmenge des gleichen Typs. Auf diese Datenmenge werden Operationen durch sogenannte Kernels ausgeführt. Ein Kernel verarbeitet dabei einen oder mehrere Eingangsströme und schreibt in einen Ausgangsstrom. Dabei arbeitet der Kernel immer auf dem gesamten Stream. Die Berechnungen eines Elements des Ausgangsstroms sind dabei unabhängig von den Ergebnissen der Berechnungen anderer Elemente. Deshalb lassen sich diese Berechnungen gut parallelisieren. Abbildung 4.1: Die Grafikkarte als Stream Modell[Owens 05] Manchmal ist es allerdings bei der Berechnung notwendig, dass Daten zwischen den einzelnen Elementen eines Streams ausgetauscht werden. Dafür kann auf verschiedene Stellen des Eingangsstreams lesend zugegriffen werden, das sogenannte gather. Ein freier Schreibzugriff (scatter ) ist aber nicht möglich (Siehe auch Abschnitt 4.4.1). Im Stream Programming Model lassen sich verschiedene Kernels hintereinander anwenden. Der Ausgangsstrom des einen Kernels dient dann als Eingangsstrom eines anderen Kernels. In Abbildung 4.1 ist die Grafikkarte als Stream Prozessor zu sehen. Dabei wird ein Stream von Vertices an den Vertexshader übergeben, dieser programmierbare Shader kann die Eigenschaften der Vertices verändern. Nach der Verarbeitung durch weitere Kernels wird ein Fragment Stream erzeugt. Fragments kann man sich als potentielle Pixel vorstellen. Der Stream von Fragments wird dem programmierbaren Fragmentshader übergeben und dort weiterverarbeitet. Beim General Purpose Computing on Graphics Processing Units werden die meisten wesentlichen Berechnungen im Fragmentshader vorgenommen. 4.2.2 Aufbau der GPU Die für diese Arbeiten notwendige Funktionalität ist auf allen NVIDIA-Grafikkarten seit der GeForce 6 vorhanden. Deshalb ist die Architektur die GeForce 6 Serie besonders interessant. Abbildung 4.2 zeigt den Aufbau einer Grafikkarte dieser Serie. Für diese Arbeit wurde die GeForce 7600 verwendet. Die genauen Daten der verwendeten Grafikkarte sind in Kapitel A.1 beschrieben. 4.3. Prinzipielles Vorgehen 41 Abbildung 4.2: Architektur der GeForce 6 [Kilgariff 05] 4.3 Prinzipielles Vorgehen Beim General Purpose Computing on Graphics Processing Units werden die Berechnungen meistens im Fragmentshader vorgenommen. Dafür werden mit OpenGL oder DirectX auf der CPU geometrische Objekte erzeugt und diese auf der Grafikkarte gerendert. Das Ergebnis wird in den Framebuffer geschrieben. Statt den Inhalt des Framebuffers anzuzeigen, schreibt man ihn in eine zuvor erstellte Textur. Für die Berechnungen wird meist ein Rechteck von der Größe des Framebuffers gerendert. Dafür werden vier Vertices mit den Eckpunktkoordinaten des Rechtecks erzeugt. Zusätzlich können ihnen Informationen wie Texturkoordinaten oder Farbwerte übergeben werden. Dies ist aber für viele GPGPU -Anwendungen nicht notwendig. Die Vertices werden an die GPU geschickt, wo sie meist unverändert den Vertexshader passieren, da häufig nur ein Rechteck in der richtigen Größe gerendert werden soll. Anschließend wird das Rechteck gerastert. So wird für jeden Pixel im Framebuffer, in den die Ergebnisse später geschrieben werden, ein sogenanntes Fragment erzeugt. Dieses Fragment enthält je nach Lage die interpolierten Werte der Vertices. Diese Fragmente erreichen den Fragmentshader, wo die eigentliche Berechnung stattfindet. Im Fragmentshader arbeitet ein Shaderprogramm, das beispielsweise in Cg 42 4. GPGPU (siehe 4.5) geschrieben wurde. Die notwendigen Daten werden in Form von Texturen an den Fragmentshader übergeben. Die Ergebnisse der Berechnungen lassen sich direkt in eine Ausgabetextur rendern. Diese Textur kann entweder auf die CPU zurückgelesen werden oder als Eingabe für weitere Berechnungsschritte dienen. Der Ablauf bei GPGPU -Berechnungen ist in Abbildung 4.3 dargestellt. Rechteck mit openGL erstellen Vertexshader Rasterizer Framebuffer Berechnung Fragmentshader Ergebnis in Textur schreiben Daten aus Textur Abbildung 4.3: Ablauf bei Berechnungen auf der GPU 4.4 Besonderheiten der GPU-Programmierung In diesem Abschnitt werden die Besonderheiten beim Programmieren auf der Grafikkarte und Unterschiede zur CPU-Programmierung aufgezeigt. 4.4.1 Gather vs. Scatter Häufig muss in einem zu verarbeitenden Stream auf andere Stellen des Streams zugegriffen werden. Dafür ist Kommunikation notwendig. Dabei wird zwischen zwei verschiedenen Arten der Kommunikation unterschieden, dem gather und dem scatter. Beim gather werden Informationen aus anderen Bereichen des Speichers gelesen und verarbeitet. Dies ist in den Shaderprogrammen möglich. So kann im Fragmentshader wahlfrei auf beliebige Pixel einer Textur zugegriffen werden. Die zweite Zugriffsart ist das sogenannte scatter. Dabei wird an eine beliebige Stelle des Speichers etwas geschrieben. Dies ist auf der GPU nicht möglich. Im Fragmentshader ist immer genau festgelegt, welches Fragment oder Pixel gerade geschrieben wird. Nur in dieses Pixel kann dann geschrieben werden. Daraus ergeben sich eine Menge Schwierigkeiten. So können nicht beliebig viele Daten einer Berechnung zurückgegeben werden. Stattdessen wird bei einer Berechnung genau ein Pixel mit maximal vier Rückgabewerten 4.4. Besonderheiten der GPU-Programmierung gather wahlfreier Lesezugriff 43 scatter wahlfreier Schreibzugriff Abbildung 4.4: Die beiden Zugriffsarten gather und scatter : Während gather im Fragmentshader möglich ist, gibt es dort kein scatter. zurückgeliefert. Durch besondere Techniken wie Multiple Render Targets (siehe Abschnitt 4.4.8) und Packen von float-Werten als half (siehe Kapitel 6.6.2) lassen sich die Rückgabewerte pro Berechnung auf insgesamt 32 erhöhen. In Abbildung 4.4 sind die beiden Zugriffsarten zu sehen. 4.4.2 Texturen Die beiden bedeutenden Datentypen auf der GPU sind Vertex Arrays und Texturen. Da die meisten Berechnungen beim GPGPU im Fragmentshader und nicht im Vertexshader vorgenommen werden, sind Texturen die wichtigen Datentypen um große Datenmengen zu speichern. Mark Harris [Harris 05] vergleicht Texturen auf der GPU mit Arrays auf der CPU: . . . anywhere we would use an array of data on ” the CPU, we can use a texture on the GPU “. RGBA Die auf der Grafikkarte verwendeten Texturen besitzen im Normalfall vier Farbkanäle. Es ist zwar möglich Texturen mit weniger als vier Farbkanälen zu definieren. Die Grafikkarte ist aber darauf optimiert Vierkanaltexturen zu verarbeiten. Dadurch kann es vorkommen, dass durch die Verwendung anderer Texturen nicht nur Rechenschritte verloren gehen, sondern auch dass die Berechnungen länger dauern. Vierkanaltexturen besitzen einen Farbkanal für Rot, einen für Grün und einen für Blau. Außerdem besitzen sie einen sogenannten Alpha-Kanal, der die Transparenz jedes Pixels angibt. Diese vier Kanäle werden im folgenden mit R, G, B und A bezeichnet. Für GPGPU -Anwendungen werden in diese Kanäle Daten geschrieben. Dabei wird häufig kein Unterschied zwischen dem Alpha-Kanal und den drei Farbkanälen gemacht. 4.4.3 Fragmentprogramme Wie bereits erwähnt, erfolgen die wesentlichen Berechnungen im Fragmentshader. Auf allen Fragmenten des Eingangsstroms werden Berechnungen durchgeführt. Die Ergebnisse werden in den Framebuffer geschrieben. Auf der CPU müsste man über alle Elemente des Eingangsstroms iterieren, um diese Berechnungen durchzuführen. Auf der GPU finden diese Berechnungen quasi parallel statt. Wieviele der Ausgangspixel tatsächlich parallel berechnet werden, hängt vom Fragmentprogramm und von der Anzahl der Pipelines auf der Grafikkarte ab. 44 4. GPGPU Das Fragmentprogramm erhält seine Eingabedaten häufig in einer oder mehreren Texturen. Daneben lassen sich einzelne skalare Werte direkt von der CPU übermitteln. Der Fragmentshader kann frei programmiert werden. Dies kann entweder in Assember oder in einer der zur Shaderprogrammierung entwickelten Hochsprachen wie HSGL, GSGL oder Cg geschehen. In dieser Arbeit wurde Cg zur Shaderprogrammierung verwendet. Genaueres zu dieser Sprache findet sich in Abschnitt 4.5. 4.4.4 In Texturen rendern Die durch den Fragmentshader berechneten Pixel werden in den Framebuffer geschrieben. Statt sie dann direkt auf irgendeinem Bildschirm anzeigen zu lassen, ist es auch möglich sie in eine Textur zu rendern. Diese Möglichkeit wird bei GPGPU Anwendungen genutzt. Die in die Textur geschriebenen Daten können entweder auf die CPU zurückgelesen werden oder für weitere Berechnungen als Eingabe dienen. 4.4.5 Vertexkoordinaten Die beim Rendern verwendeten Vertexkoordinaten geben an, an welcher Stelle im Framebuffer und damit an welcher Stelle in der Ausgabetextur die berechneten Daten stehen. Falls, wie beim GPGPU üblich, den kompletten Framebuffer bedeckende Rechtecke gerendert werden, müssen nur die Koordinaten der vier Eckpunktvertices mit OpenGL angegeben werden (zusammen mit der Option GL QUADS). Für jedes Pixel in der Ausgabetextur, das zwischen den gerenderten Bildern der Eckpunkte liegt, wird automatisch ein Fragment erzeugt. Die Vertexkoordinaten bestimmen damit also den Bildbereich einer Berechnung auf der GPU [Harris 05]. 4.4.6 Texturkoordinaten Neben Vertexkoordinaten können für jeden Vertex noch Texturkoordinaten übergeben werden. Mittels dieser Texturkoordinaten kann auf Stellen in einer oder mehreren Eingangstexturen zugegriffen werden. Wird wieder ein Rechteck gerendert, können Texturkoordinaten für die Eckpunkte angegeben werden. Die Texturkoordinaten für ein Fragment werden dann durch Interpolation zwischen den Eckpunktkoordinaten berechnet. Jedem Vertex können auch mehrere, verschiedene Texturkoordinaten übergeben werden. Dies wird beispielsweise in Kapitel 6.6.2 genutzt um auf unterschiedliche Stellen in mehreren Texturen zugreifen zu können. Da die Texturkoordinaten verwendet werden, um auf bestimmte Stellen der Eingangstexturen zuzugreifen und diese Texturen beim GPGPU die Eingangsdaten enthalten, kann man sagen, dass sie hier den Definitionsbereich festlegen. Häufig sind beim GPGPU die Eingangstexturen genauso groß wie die Ausgabetexturen. Aus jedem Pixel einer Eingangstextur wird oftmals ein Pixel der Ausgabetextur berechnet. Deshalb unterscheiden sich Textur- und Vertexkoordinaten häufig nicht. 4.5. Die Programmiersprache Cg 4.4.7 45 Rücklesen der Daten Das Rücklesen von großen Datenmengen auf die CPU ist eine relativ teure Aktion. Deshalb sollten nur Daten, die unbedingt auf der CPU gebraucht werden, zurückgelesen werden. Deshalb macht es häufig Sinn, Operationen, die auf der CPU genauso schnell wie auf der GPU durchgeführt werden könnten, trotzdem auf der GPU laufen zu lassen, wenn dadurch der Datentransfer zwischen GPU und CPU minimiert wird. Um Daten von der GPU zur CPU zurückzulesen werden OpenGL Befehle verwendet. Damit lassen sich die Daten einer Textur in ein auf der CPU definiertes Array schreiben. Beim Zurücklesen wird ein rechteckiger Bereich auf der Textur angegeben, der auf die CPU übertragen wird. Dies ist häufig die komplette Textur. Falls nur ein gewisser Bereich für die Weiterverarbeitung auf der CPU notwendig ist, ist es besser nur diesen Bereich zu übertragen. Statt der ganzen Textur kann ein kleineres Rechteck oder ein einzelner Punkt transferiert werden. Dies wird u.a. in Kapitel 6.6.2 beim Zurückschreiben der Orientierungshistogramme genutzt. 4.4.8 Multiple Render Targets Auf neueren Grafikkarten kann statt in eine Textur in bis zu vier Texturen gleichzeitig gerendert werden. Dies wird mit Multiple Render Targets oder kurz MRT bezeichnet. Durch diese Technik können Fragmentprogramme bis zu vier RGBA-Texturen und damit bis zu sechzehn float-Werte berechnen und ausgeben. Beim MRT wird in sogenannte color attachments des Framebuffers geschrieben. An diese Attachments, von denen es vier gibt, lassen sich verschiedene Texturen binden. Die Attachments haben alle die gleiche Größe wie der Framebuffer. Dementsprechend müssen auch die Texturen diese Größe besitzen. Es ist also möglich in bis zu vier Texturen der gleichen Größe gleichzeitig zu schreiben. Im Fragmentshaderprogramm muss dafür durch ein Semantic (siehe 4.5.2) angegeben werden, in welches Attachment geschrieben werden soll. Multiple Render Targets haben eine große Bedeutung für GPGPU -Anwendungen. In Abschnitt 4.4.1 wurde erläutert, dass im Fragmentshader kein wahlfreier Schreibzugriff (scatter ) auf die Ausgangsdaten besteht. Stattdessen ist das Pixel, in welches geschrieben wird, durch das zu bearbeitende Fragment bereits festgelegt. Dadurch können eigentlich nur vier float-Werte pro Berechnung geschrieben werden. Durch MRT vervierfacht sich die Anzahl der möglichen Ausgabewerte. Abbildung 4.5 zeigt den Fragmentshader, der Multiple Render Targets besitzt. Im Anhang F ist die Syntax zum Verwenden von MRTs beschrieben. 4.5 Die Programmiersprache Cg Zur Programmierung der Shader neuerer Grafikkarten gibt es inzwischen mehrere Sprachen. Eine davon ist die von NVIDIA entwickelte Sprache Cg. Dabei ist Cg die Abkürzung für C for Graphics. Diese Sprache besitzt eine relativ ähnliche Syntax wie 46 4. GPGPU Fragmentshader Render Target 1 Render Target 2 Render Target 3 Render Target 4 Abbildung 4.5: Multiple Render Targets die Sprache C. Da sich die Programmierung von GPU und CPU stark unterscheidet, gibt es auch einige Unterschiede zwischen Cg und C. Cg kann sowohl für die Programmierung von Vertexshadern als auch für die Programmierung von Fragmentshadern verwendet werden. In diesem Abschnitt werden zwei wichtige Besonderheiten von Cg erwähnt, die besonderen Datentypen für Vektoren und die sogenannten Semantics. Diese und weiterführende Informationen zu Cg finden sich in [Fernando 03]. Außerdem steht in Anhang C ein Beispielprogramm und anderes Wissenswertes zu Cg. 4.5.1 Vektordatentypen Um bestimmte Berechnungen auf der GPU besonders effizient durchführen zu können, gibt es in Cg4 neben einfachen Datentypen wie float oder int, die auch in C existieren, noch spezielle Vektordatentypen. So gibt es für zwei-, drei- und vierdimensionale Vektoren die Datentypen float2, float3 und float4 bzw. int2, int3 und int4. Da in alle Register auf der GPU genau vierdimensionale Vektoren passen, hat dieser Datentyp besondere Bedeutung. Auf die einzelnen Komponenten eines vierdimensionalen Vektors kann mit Hilfe der Suffixe x, y, z und w zugegriffen werden. Alternativ können auch r, g, b und a verwendet werden. Diese Suffixe werden beim Swizzling verwendet. Aus einem vierdimensionalen Vektor lässt sich mit Hilfe des Swizzle-Operators ein bis zu vierdimensionaler Vektor auslesen. Dabei lassen sich mit einer einzigen Anweisung die einzelnen Komponenten eines Vektors in eine beliebige Reihenfolge bringen. Sei vektor ein Vektor vom Typ float4. Dann ist beispielsweise vektor.wx oder vektor.ar ein zweidimensionaler Vektor, der in der ersten Komponente den Wert der ursprünglich vierten Komponente und in der zweiten Komponente den Wert der ursprünglich ersten Komponente enthält. Das Swizzling ist auf den meisten modernen Grafikkarten eine freie Operation, d.h. sie benötigt keine zusätzlichen Taktzyklen zur Berechnung. 4 Diese Datentypen sind Teil der Cg Standard Library 4.5. Die Programmiersprache Cg 47 Das Gegenstück zum Swizzling ist das Write Masking. Hier werden einfach einzelne Komponenten eines Vektors geschrieben. Sei beispielsweise vektor4 ein vierdimensionaler und vektor2 ein zweidimensionaler Vektor. Dann werden durch vektor4.xy = vektor2 die beiden ersten Komponenten von vektor4 verändert. 4.5.2 Semantics Die Eingabe eines Shaderprogramms ist ein Datenstrom, der verarbeitet werden muss. Bei einem Vertexshader muss ein Strom von Vertices verarbeitet werden, im Fragmentshader ein Strom von Fragmenten. Sowohl Vertices als auch die Fragmente besitzen gewisse Eigenschaften, auf die zur Verarbeitung zugegriffen werden kann. So besitzt beispielsweise jedes Fragment die Information, an welche Stelle im Framebuffer es geschrieben wird. Daneben kann es über Farbinformationen und über mehrere Texturkoordinaten verfügen. Im Fragmentshader können diese Informationen verwendet werden, um beispielsweise verschiedene Texturen an diesen Stellen auszulesen und den endgültigen Wert des Fragments zu berechnen. Um diese Informationen im Shaderprogramm zu erhalten werden sogenannte Semantics verwendet. Durch sie können verschiedenen Eingabevariablen bestimmte Informationen zugeordnet werden. Daneben muss auch für den Ausgabestrom angegeben werden, um welchen Datentyp es sich handelt. Im Fragmentshader sind dies eigentlich immer Farbinformationen, allerdings muss auch dies angegeben werden. Eine Tabelle mit den verschiedenen Semantics befindet sich in Anhang C.5. 48 4. GPGPU 5. Andere GPU-Implementierungen Neben den Versuchen schnelle und leistungsfähige Featuredetektoren auf der CPU zu entwickeln (siehe Kapitel 2), wurden einige Anstrengungen unternommen solche Algorithmen auf der Grafikkarte zu implementieren. Seit der GeForce 3 besitzen Grafikkarten freiprogrammierbare Shader, wobei einige Funktionalitäten erst auf späteren Grafikkarten vorhanden sind. Durch die programmierbaren Shader lassen sich Grafikkarten als sehr leistungsfähige Recheneinheiten verwenden, die inzwischen nicht nur für Grafikberechnungen sondern für jegliche Art von rechenintensiven Aufgaben Verwendung finden. Kapitel 4 beschreibt die Besonderheit der Programmierung auf der Grafikkarte und die Entwicklung des sogenannten General-Purpose Computing on GPU (kurz GPGPU). In diesem Kapitel werden die bereits in Kapitel 2.3.2 erwähnten SIFT-Varianten für die GPU ausführlich vorgestellt. 5.1 OpenVIDIA Das OpenVIDIA-Projekt1 beinhaltet verschiedene Computer-Vision-Algorithmen, die alle auf der Grafikkarte laufen. In [Fung 05a] und [Fung 05b] stellt James Fung, in [Fung 05b] gemeinsam mit Steve Mann und Chris Aimone, unter anderem die GPU-Version eines Canny-Kantendetektors, ein Programm zum Erstellen von Panoramabildern und einen Händetracker vor. Alle diese Programme laufen auf der GPU und sind Bestandteil des OpenVIDIA-Projekts. Desweiteren wird die GPU-Implementierung eines Programms zur Berechnung von Feature-Vektoren für zuvor gefundene Keypoints präsentiert. Diese Feature-Vektoren sind den SIFT-Deskriptoren sehr ähnlich. Allerdings werden hier weder die Orientierung noch die Größe der Keypoints betrachtet. Dadurch sind die Feature-Vektoren nicht invariant gegenüber vielen Veränderungen des Bildes. 1 http://openvidia.sourceforge.net 50 5. Andere GPU-Implementierungen Abbildung 5.1: Auf diesem Rechner mit sechs PCI GeForce FX 5200 Grafikkarten und einer AGP Grafikkarte laufen die GPU-Programme des OpenVIDIA-Projekts Zum Teil lassen sich die dort präsentierten GPU-Programme auch parallel auf mehreren Grafikkarten ausführen. Auf Abbildung 5.1 ist ein in diesem Projekt verwendeter Rechner mit sechs PCI GeForce FX 5200 Grafikkarten und einer AGP Grafikkarte zu sehen. Abbildung 5.2 zeigt ein Bild, auf dem verschiedene Keypoints detektiert und nummeriert wurden. Für diese Keypoints wurden Feature-Vektoren berechnet. Dadurch können diese Punkte getrackt werden. Die Keypoints werden hier mit einem HarrisDetektor ermittelt. Für die Feature-Vektoren werden die Gradienten in einer 16×16 -Nachbarschaft jedes Keypoints betrachtet. Dafür wird diese Nachbarschaft in sechzehn Unterregionen mit je 4 × 4 Pixeln unterteilt. Für jede dieser Unterregionen wird ein Histogramm der Gradientenrichtungen, bestehend aus acht Bins, berechnet. Dabei wird jeder der BinEinträge mit der entsprechenden Gradientenmagnitude gewichtet. Außerdem werden die Einträge abhängig von ihrem Abstand zum Keypoint gaussgewichtet. (Vgl. auch den SIFT-Deskriptor in Kapitel 3.7). Für jeden Keypoint werden 16 Histogramme mit je 8 Bins berechnet, also 128 Werte. Diese Histogramme sollen auf der GPU berechnet werden. Die Schwierigkeit besteht darin, dass bei der Berechnung auf der GPU normalerweise für einen gerenderten Punkt nur ein RGBA-Ausgabewert berechnet werden kann. Deshalb muss hier ein Trick angewendet werden. Für jedes der 16 Histogramme eines Keypoints wird ein Vertex gerendert. So könnten vier Werte pro Histogramm berechnet und in eine RGBA-Textur geschrieben werden. Da der Feature-Vektor aber aus acht Bins pro Histogramm besteht, werden die Daten zusätzlich gepackt. Statt einem 32 Bit float-Wert werden je zwei 16 Bit half -Werte in einen Farbkanal geschrieben. 5.2. Diplomarbeit von Sebastian Heymann 51 Abbildung 5.2: Keypoints im OpenVIDIA-Projekt 5.2 Diplomarbeit von Sebastian Heymann In seiner Diplomarbeit [Heymann 05a] hat Sebastian Heymann den SIFT-Algorithmus auf der GPU implementiert und mit einer eigenen Referenzimplementierung auf der CPU verglichen. Er hat außerdem untersucht, inwiefern sich das Auslagern einzelner rechenintensiver Schritte auf die GPU hinsichtlich der benötigten Rechenzeit auswirkt. Um das Potential der Grafikkarte voll auszunutzen, arbeitet Sebastian Heymanns Version mit gepackten Bildern. Dabei werden jeweils vier benachbarte Pixel des zu untersuchenden Eingabebildes in einen RGBA-Pixel gepackt. Dadurch können in jedem Berechnungsschritt vier Werte gleichzeitig bearbeitet werden. Außerdem ist damit für vier Werte nur ein Texturzugriff notwendig. In Kapitel 6.2 ist diese Packmethode als lokales Packen noch einmal ausführlich beschrieben. 5.2.1 Packen der Daten Durch das Packen kann ein Performancegewinn bis zum Faktor vier erreicht werden. Allerdings müssen dafür die Daten in gepackter Form vorliegen. Für wenig rechenintensive Schritte lohnt sich daher der zum Packen und späteren Entpacken notwendige Aufwand nicht. Die Berechnung der DoG-Pyramide ist allerdings aufwendig genug, um eine Berechnung auf gepackten Daten zu rechtfertigen. Das Packen der Daten ist relativ einfach und wird auf der GPU vorgenommen. Dafür werden die Werte von jeweils vier Eingangspixeln in die vier Farbkanäle eines einzigen Pixels geschrieben. Diese Werte befinden sich dabei im ersten Kanal des Eingabebildes. Deshalb kann dieses Programm auch zum späteren Verkleinern gaussgeglätteter Bilder verwendet werden, um dann die nächste Oktave der Gausspyramide zu berechnen. Das spätere Entpacken der Bilder ist ungleich schwieriger. 52 5. Andere GPU-Implementierungen Abbildung 5.3: Zunächst wird das Graubild in eine RGBA-Textur gepackt, anschließend werden horizontale und vertikale Gaussfaltungen auf das Bild angewendet und Differenzbilder berechnet. [Heymann 05a] Berechnungen, in denen auf benachbarte Pixel zugegriffen wird, werden durch die Verwendung gepackter Bilder komplizierter, da die Lage der Nachbarpixel immer abhängig vom gerade betrachteten Farbkanal ermittelt werden muss. Eine solche Berechnung ist die Gaussfaltung. Die zum Aufbau der Gausspyramide verwendeten zweidimensionalen Gaussfaltungen lassen sich jeweils in eine vertikale und eine horizontale eindimensionale Faltung aufteilen. Auf Abbildung 5.3 ist der Aufbau der Gausspyramide und der DoG-Pyramide zu sehen. Abbildung 5.4: Die Gaussfaltung wird auf gepackten Bildern durchgeführt. Hier ist die horizontale Faltung zu sehen, aufgeteilt in gerade und ungerade Bereiche. [Heymann 05b] Das Falten von gepackten Bildern erläutert Sebastian Heymann am Beispiel der horizontalen Faltung. Hier können die gleichen Gaussgewichte jeweils auf die untereinander liegenden Kanäle R und B bzw. G und A angewendet werden. Außerdem wird die Faltung auf allen vier Kanälen gleichzeitig durchgeführt. 5.2. Diplomarbeit von Sebastian Heymann 53 Die Faltung lässt sich in gerade und ungerade Bereiche aufteilen. Abbildung 5.4 zeigt diese Aufteilung. Dadurch, dass die Bilder gepackt vorliegen, werden weniger Texturzugriffe benötigt als bei ungepackten Bildern. Für Gaussmasken der Größe k sind statt k Zugriffen nur k+1 Zugriffe notwendig. 2 Um die Faltungen weiter zu beschleunigen nutzt Sebastian Heymann die auf der GPU vorhandenen Möglichkeiten zur linearen Interpolation zwischen verschiedenen Pixeln aus. Außerdem verwendet er den Vertex Shader um linear veränderliche Werte bei der Gaussfaltung vorzuberechnen. Aus den gaussgeglätteten Bildern werden durch ein weiteres Fragmentprogramm die Differenzbilder berechnet. Auch diese Bilder liegen gepackt vor. 5.2.2 Keypointsuche auf der CPU In einem ersten Versuch entpackt Heymann die DoG-Pyramide auf der GPU und liest die entpackten Daten zurück auf die CPU. Dort sucht er dann nach Keypoints. Dies führt schon zu einer Beschleunigung des SIFT-Algorithmus gegenüber einer reinen CPU-Berechnung. Allerdings benötigt das Rücklesen der DoG-Pyramide relativ viel Zeit. Deshalb wird in einem zweiten Versuch auch die Suche nach Keypoints auf der GPU realisiert. 5.2.3 Keypointsuche auf der GPU Auf der GPU werden die Keypoints in der gepackten DoG-Pyramide gesucht. Dieser Teil des Algorithmus ist auf der GPU nicht unbedingt schneller als auf der CPU. Da aber so weniger Daten zurückgelesen werden müssen, lohnt sich die Berechnung auf der GPU auch hier. Allerdings müssen einige Anpassungen des Algorithmus vorgenommen werden, da Konditionalverzweigungen, wie sie bei der Suche nach Keypoints auftreten, auf der GPU eher problematisch sind. So wird zunächst untersucht, ob überhaupt einer der vier in einem Pixel gepackten Punkte einen notwendigen Schwellwert überschreitet. Dadurch können etwa die Hälfte der Pixel frühzeitig verworfen werden. Anschließend wird untersucht, ob ein Punkt lokales Extremum ist. Nur wenn auch dies der Fall ist, muss auf Pixel anderer Stufen der DoG-Pyramide zugegriffen werden, um zu überprüfen, ob tatsächlich ein globales Extremum vorliegt. Abbildung 5.5 zeigt die Keypointsuche, wie sie im Original-SIFT vorgenommen wird, (vgl. Kapitel 3.4) und die für die GPU optimierte Keypointsuche. Als Rückgabe der Keypointsuche dienen zwei gepackte Texturen. In der einen Textur ist die Größe jedes Keypoints gespeichert, in der anderen seine Orientierung. Die Positionen der Keypoints sind durch die Positionen der Markierungen in beiden Texturen kodiert. Diese beiden Texturen werden auf der GPU entpackt und dann zurück auf die CPU geschrieben. Dort muss eine der beiden Texturen nach markierten Stellen durchsucht werden. 54 5. Andere GPU-Implementierungen Abbildung 5.5: Links ist der klassische Ablauf der Keypointsuche, rechts ist der Ablauf der für die GPU optimierten Suche dargestellt. [Heymann 05b] 5.2.4 Berechnung der Gradienten Für die Berechnung der Deskriptoren müssen die Gradienten im entsprechenden Gaussbild in der Umgebung eines jeden Keypoints bekannt sein. Auf der GPU bietet es sich an, in einem Fragmentprogramm die Gradientenrichtung θ und die Gradientenmagnitude m eines jeden Punktes zu berechnen. Da die Eingabe gepackt vorliegt, unterscheidet sich die Berechnung für jeden der vier Farbkanäle. Abbildung 5.6 zeigt die Berechnung für den R-Kanal. Die Rückgabe ist wieder eine gepackte Struktur. Um sowohl Magnitude als auch Richtung jedes Punktes zurückgeben zu können, werden zwei Ausgabetexturen benötigt. Dies ist mit Hilfe von MRT (siehe Kapitel 4.4.8) möglich. Um bei der Deskriptorenberechnung ohne größere Koordinatenumrechnung auf diese Daten zugreifen zu können, werden sie durch ein weiteres Fragmentprogramm entpackt. Dabei wird in einen Farbkanal die Gradientenrichtung, in einen zweiten die Magnitude geschrieben. Abbildung 5.7 zeigt diesen Vorgang. 5.2.5 Deskriptorenberechnung Zur Berechnung eines Feature-Vektors wird für jeden Keypoint ein Gitter von sechzehn Vertices gerendert. Jeder Vertex wird auf der CPU erzeugt und mit gewissen Attributen wie Texturkoordinaten, Größe und Richtung des Gradientenbereichs versehen. Für jeden dieser Vertices wird eines der sechzehn Histogramme des Deskriptors berechnet. Dabei wird auf die entpackten Gradientendaten zugegriffen. Für jeden der sechzehn Vertices müssen acht Werte berechnet und zurückgegeben werden. Da eine Rückgabetextur nur über vier Rückgabewerte pro gerendertem Pixel 5.3. GPU-based Video Feature Tracking von Sudipta N. Sinha 55 Abbildung 5.6: Hier ist die Berechnung der Deskriptoren zu sehen. Dafür werden einzelne Vertices gerendert und in zwei Texturen geschrieben. [Heymann 05b] verfügt, wird hier wieder MRT angewendet. Beide Texturen werden auf die CPU zurückgelesen. Dort werden dann die Deskriptoren für jeden Punkt zusammengefügt. In Abbildung 5.8 ist die Berechnung der Deskriptoren auf der GPU zu sehen. Sebastian Heymann hat in seiner Arbeit gezeigt, dass sich der SIFT-Algorithmus durch Auslagern einiger Komponenten auf die GPU etwas und durch vollständige Berechnung auf der GPU stark beschleunigen lässt. 5.3 GPU-based Video Feature Tracking von Sudipta N. Sinha Sudipta N. Sinha, Jan-Michael Frahm, Marc Pollefeys und Yakup Genc beschreiben in [Sinha 06a] und [Sinha 06b] ihre GPU-Implementierungen eines KLT Feature Trackers und des SIFT-Algorithmus. Anders als in der Arbeit von Sebastian Heymann wird bei ihrer SIFT-Version ein ungepacktes Bild auf die Grafikkarte geladen. Die verschiedenen Gaussglättungen werden von unterschiedlichen Fragment-Programmen vorgenommen. In jedes dieser Fragment-Programme ist die verwendete Gaussmaske fest eincodiert. Neben der Berechnung des gaussgeglätteten Bildes erfolgt auch noch die Berechnung eines Differenzbildes und der Gradienten, bestehend aus Magnitude und Richtung. Die ermittelten Daten werden in eine RGBA-Textur geschrieben, wobei das Differenzbild im A-Kanal steht. So wird eine Pyramide mit allen benötigten Informationen erzeugt. In dieser Pyramide wird nach Extremstellen in den Differenzbild-Kanälen gesucht. Dies erfolgt mittels Blending-Operationen. Der Vergleich mit den benachbarten Stufen der Pyramide wird von einen Depth-Test geleistet. So erhält man potentielle Keypoints, die noch weiteren Tests unterworfen werden müssen. Damit ein Punkt 56 5. Andere GPU-Implementierungen Abbildung 5.7: Hier ist das Entpacken der Gradientenrichtungen und Gradientenmagnituden zu sehen. In den R-Kanal der Ausgabetextur wird die Richtung, in den G-Kanal die Magnitude geschrieben.[Heymann 05a] Abbildung 5.8: Berechnung der Deskriptoren [Heymann 05b] Keypoint sein kann, muss außerdem auf dem entsprechenden DoG-Bild ein bestimmter Grenzwert erreicht sein. Die Überprüfung erfolgt mittels eines Alpha-Tests, der Punkte mit zu kleinem Wert verwirft. Außerdem werden zu kantenartige Punkte verworfen. Das Ergebnis ist eine binäre Textur von der Größe des Bildes, auf dem an jeder Position markiert ist, ob sich dort ein Keypoint befindet oder nicht. Diese Textur wird mit zusätzlichen Kompressionen in eine RBGA-Textur gepackt. Dadurch verkleinert sich die zu übertragende Datenmenge um den Faktor 32. Die gepackten Daten werden zurück auf die CPU gelesen. Dort werden sie entpackt und decodiert. Da das Zurücklesen der kompletten Gradientenpyramide zu zeitaufwendig ist, müssen auch die Teile der Orientierungsberechnung auf der GPU durchgeführt werden. Dafür wird in einem weiteren Fragmentshader für jeden Keypoint ein gaussgewichtetes Gradientenhistogramm seiner Umgebung erstellt. Dieses Histogramm wird auf die CPU übertragen. Auf der CPU wird nach Maxima im Histogramm gesucht, um so die Orientierung des Keypoints zu erhalten. 5.4. Realtime SIFT von Cameron Upright 57 Abbildung 5.9: Datenfluss in Sudipta Sinhas GPU-SIFT Implementierung [Sinha 06a] Im letzten Schritt werden die SIFT-Deskriptoren für jeden Keypoint berechnet. Ein Deskriptor besteht aus einzelnen gewichteten Gradientenhistogrammen der Umgebung eines Keypoints. Da eine effektive Berechnung auf der GPU schwierig ist, werden auch hier einzelne Schritte der Berechnung auf der CPU ausgeführt. Auf der GPU werden die Gradientenmagnituden in jeder Keypointumgebung mit einer Gaussmaske überblendet. So enthält man eine Region mit den richtigen Gewichtungen für die einzelnen Histogrammeinträge. Auf der CPU werden aus dieser gewichteten Region und den entsprechenden Gradientenrichtungen die Histogramme erstellt. Dieser Programmablauf ist in Abbildung 5.9 zu sehen. Die GPU-Implementierung ist laut [Sinha 06a] um den Faktor acht bis zehn schneller als vergleichbare CPU-Implementierungen. 5.4 Realtime SIFT von Cameron Upright In [Upright 06] stellt Cameron Upright seine GPU-Version des SIFT vor. Im Gegensatz zum Original-SIFT, bei dem üblicherweise auf drei Differenzbildern einer Oktave nach Keypoints gesucht wird, werden hier nur auf zwei Bildern Keypoints detektiert. Dies hat zur Folge, dass nur fünf gaussgeglättete Bilder pro Oktave benötigt werden. Diese Bilder werden in zwei RGBA-Texturen abgespeichert. 58 5. Andere GPU-Implementierungen Aus diesen fünf Bildern werden vier Differenzbilder gebildet. Jedes dieser Differenzbilder lässt sich in einem Farbkanal einer einzigen RGBA-Textur unterbringen. Dadurch können in einem Fragmentshader, der als Eingangstextur eben diese Textur bekommt, alle potentiellen Keypoints ermittelt werden. Ein Keypoint hat auch hier, verglichen mit seinen acht Nachbarpixeln auf der selben Stufe und den achtzehn Nachbarpixeln auf den Stufen darüber und darunter, einen kleineren oder größeren Wert als alle diese Pixel. Da nur auf den beiden mittleren Stufen nach Peaks gesucht wird, kann sich an jeder Position höchstens ein lokales Extremum, auch Peak genannt, befinden. Falls sich irgendwo ein Peak befindet, wird der A-Kanal an dieser Stelle auf eins gesetzt. Im B-Kanal wird die Stufe, auf der sich der Peak befindet, gespeichert und in R und G die x- und y-Position des Keypoints. Dies ist notwendig, da nicht die komplette Textur zurückgelesen wird. Stattdessen wird ein von Daniel Horn entwickeltes Verfahren [Horn 05] zur Kompression eingesetzt, so dass nur Pixel, die Keypoints enthalten, zurückgelesen werden müssen. In einem einzelnen Pass wird eine subpixelgenaue Lokalisierung der Keypoints vorgenommen und Keypoints an Kanten werden verworfen. Dies geschieht, indem der A-Kanal gegebenenfalls auf null gesetzt wird. Auf eine Orientierung der Keypoints verzichtet Cameron Upright mit der Begründung, dass dies für Robotikanwendungen nicht notwendig sei. Seiner Meinung nach können größere Rotationen nur durch ein Umfallen des Roboters auftreten. Durch diese Einschränkung lassen sich die SIFT-Deskriptoren auch relativ leicht berechnen. Dies erfolgt wieder in einem einzelnen Pass. Dabei werden genau wie beim Original-SIFT für jeden Keypoint 16 Histogramme mit jeweils 8 Bins erstellt. Für jedes dieser Histogramme wird ein Pixel gerendert. In die RGBA-Kanäle eines Ausgabepixels werden durch das Packen als Datentype half die Werte von 8 Bins geschrieben. Diese Implementierung erreicht laut Cameron Upright für Bilder der Größe 640×480 eine Geschwindigkeit von mehr als 13 Frames pro Sekunde. 6. Realisierung 6.1 Programm-Ablauf Der Programmfluss der GPU-Implementierung unterscheidet sich deutlich von der LibSIFT-Implementierung. Grund hierfür sind hauptsächlich die diversen Einschränkungen, die bei der Programmierung auf der GPU bestehen (siehe Kapitel 4). Außerdem wird der Datentransfer von der GPU auf die CPU, der relativ langsam ist, möglichst gering gehalten. So werden bei der GPU-Implementierung die Gausspyramide und die DoG-Pyramide nicht in einem Schritt komplett aufgebaut, sondern nur so weit wie sie benötigt werden, um weitere Keypoints zu detektieren und für diese Punkte Orientierung und Deskriptoren zu berechnen. Dies hat zusätzlich noch den Vorteil, dass die Berechnung zwischendrin abgebrochen werden kann und es trotzdem schon verwertbare Ergebnisse gibt. Diese Ergebnisse sind bereits berechnete Keypoints inklusive der dazugehörigen Deskriptoren. Abbildung 6.2 zeigt den veränderten Ablauf des SIFT-Algorithmus auf der GPU. Hier werden folgende Schritte dargestellt: 1. Die Gausspyramide und die DoG-Pyramide werden so weit aufgebaut, dass die ersten Keypoints gefunden werden können. Dafür sind drei DoG-Stufen und damit auch vier Gauss-Stufen notwendig. Für die Keypoints werden mit Hilfe einer berechneten Gradiententextur Orientierung und Deskriptoren ermittelt. 2. Eine zusätzliche Stufe der Gausspyramide wird gebaut. Mit dieser Stufe lässt sich eine weitere Stufe der DoG-Pyramide ermitteln. So kann auf der nächsten Stufe nach Keypoints gesucht werden. Auch für diese Keypoints werden Orientierung und Deskriptoren berechnet. 3. Analog zu 2. 4. Nach dem Verkleinern eines Gaussbildes wird daraus der erste Teil der Gausspyramide der nächsten Oktave aufgebaut. Anschließend werden die ersten drei 60 6. Realisierung Stufen der nächsten Oktave der DoG-Pyramide erstellt, um nach Keypoints suchen zu können. Anschließend erfolgt wieder die Berechnung der Orientierung und der Deskriptoren. 5. Keypoints der nächsten Stufe dieser Oktave detektieren 6. Analog zu 5. 7. Nächste Oktave . . . Initialisieren Bild laden und auf GPU packen Vorberechnungen für 1. Oktave Oktave vorbereiten Keypoints finden und Deskriptoren berechnen Texturen verkleinern Keypoints finden und Deskriptoren berechnen Keypoints finden und Deskriptoren berechnen weitere Oktaven ? weitere Bilder ? Abbildung 6.1: Schritte des GPUSIFT auf der CPU In Abbildung 6.1 ist dieser Abblauf noch einmal schematisch zu sehen. Dabei entsprechen die hier aufgeführten Teile Methoden, die vom Hauptprogramm aus aufgerufen werden. Nach einer notwendigen Initialisierung wird das zu bearbeitende Bild geladen und auf die GPU übertragen. Bevor die ersten Berechnungen stattfinden können, müssen noch einige Vorbereitungen durchgeführt werden. Einige dieser Vorbereitungen sind nur bei der ersten Oktave notwendig, andere sind immer erforderlich, wenn eine neue Oktave erreicht wurde. So werden beispielsweise für jede Oktave die ersten Gaussglättungen und Differenzberechnungen vorgenommen. Anschließend wird dreimal hintereinander auf verschiedenen Stufen der DoG-Pyramide nach Keypoints gesucht. Für diese Punkte werden jeweils direkt die Deskriptoren ermittelt. Dazu werden jedesmal die nächste Stufe der Gausspyramide und die nächste Stufe der DoG-Pyramide aufgebaut. 6.1. Programm-Ablauf 61 Falls die letzte Oktave noch nicht erreicht wurde, wird eine der Gausstexturen verkleinert. Anschließend werden wieder die Vorbereitungen für die nächste Oktave vorgenommen. In den nächsten Abschnitten sind diese auf der CPU laufenden Schritte noch einmal genau erläutert. 6.1.1 Initialisierung Zunächst findet eine Überprüfung statt, ob auf dem aktuellen Rechner die notwendigen Hardware-Voraussetzungen gegeben sind. Damit das Programm fehlerfrei arbeiten kann, muss die vorhandene Grafikkarte gewisse Anforderungen erfüllen. Die Überprüfung kann mittels der glew-Bibliothek erfolgen. Falls die notwendigen Anforderungen nicht erfüllt sind, bricht das Programm sofort ab. Anschließend werden die später verwendeten Texturen angelegt. Dafür muss bereits die Bildgröße bekannt sein. Wegen der verwendeten Packmethode (siehe Abschnitt 6.2) müssen Höhe und Breite des Bildes gerade sein. Um auch nach mehrmaligem Verkleinern auf gepackten Daten arbeiten zu können, müssen Höhe und Breite entsprechend große Zweierpotenzen enthalten. Außerdem wird mit der Größe der Framebuffer initialisiert. Desweiteren müssen einige Befehle ausgeführt werden, um die Cg-Programme verwenden zu können. Diese Programme werden geladen, kompiliert und auf die GPU übertragen. Die dafür notwendigen Befehle sind im Anhang C aufgeführt. 6.1.2 Laden der Bilder Das Laden der zu bearbeitenden Bilder erfolgt mittels OpenCV-Funktionen. Mit der entsprechenden OpenCV-Funktion wird das Bild direkt als Graustufenbild geladen. Anschließend wird es, wie in Abschnitt 6.2 erläutert, gepackt. Das gepackte Bild wird mittels OpenGL-Methoden als Textur auf die Grafikkarte transferiert. 6.1.3 Erste Oktave Bevor die eigentlichen Berechnungen beginnen, wird das Eingangsbild erst noch einer ersten Gaussglättung unterworfen. Dies geschieht in der Methode FirstOctave. Dabei wird auf die Gaussglättungsprogramme auf der GPU zurückgegriffen. 6.1.4 Oktave vorbereiten Damit bei der späteren Suche nach Keypoints inklusive Berechnung ihrer Deskriptoren immer die selben Schritte durchgeführt werden können, müssen einige Vorbereitungen vorgenommen werden. So werden die ersten vier Stufen der Gausspyramide gebaut. Die Gaussfaltungen finden iterativ statt. Die horizontalen und vertikalen Glättungen werden nacheinander vorgenommen. Die Glättungen selbst werden von den Cg-Programmen gauss x.cg und gauss y.cg durchgeführt. Die genaue Arbeitsweise dieser Programme ist in Abschnitt 6.3.1 erläutert. 62 6. Realisierung GPUSIFT 1. 2. Eingangsbild Gaussbilder Differenzbilder Gradienten Keypoints Orientierung Deskriptoren 4. 6. 5. Nächste Oktave ... ... ... Abbildung 6.2: Ablauf auf der GPU 3. 6.2. Packen der Bilddaten 6.1.5 63 Keypoints finden und Deskriptoren berechnen In der Methode FindPeaks werden relativ viele Schritte ausgeführt. So werden die nächsten Keypoints gefunden. Dafür werden immer die nächste Stufe der Gausspyramide und die nächste Stufe der DoG-Pyramide aufgebaut. Die neueste Stufe der DoG-Pyramide und ihre beiden Vorgängerstufen werden verwendet um nach Keypoints zu suchen. Die CPU-Methode FindAndLocalizePeaks liefert den Vektor lokalized_peaks mit gefundenen Keypoints zurück. Dabei werden auch hier die meisten Berechnungen auf der GPU ausgeführt (vgl. Abschnitt 6.4). Um die Orientierung der gefundenen Keypoints berechnen zu können, werden die Gradienten der entsprechenden Gaussstufe berechnet und in einer Textur gespeichert (siehe Abschnitt 6.5). Anschließend wird die Methode CalcPeakOrientation aufgerufen um für jeden Keypoint dieser Stufe die Orientierung zu berechnen. Dabei kann es vorkommen, dass der Vektor lokalized_peaks um neue Keypoints erweitert wird (Siehe Kapitel 6.6). Zuletzt wird noch für alle Keypoints der aktuellen Stufe ein Deskriptor berechnet. Dies geschieht in der Methode CalcDescriptor. Dieser Vorgang ist in Abschitt 6.7 beschrieben. 6.1.6 Texturen verkleinern Um die nächste Oktave zu berechnen, muss eine der Gausstexturen verkleinert werden. Dazu wird die Methode NextOctave aufgerufen. Diese Methode verkleinert das Bild auf der vierten Stufe der aktuelle Oktave in der Gausspyramide. Dafür wird das Cg-Programm resize.cg aufgerufen. Die genaue Arbeitsweise dieses Programms ist in Abschnitt 6.8 zu finden. 6.2 Packen der Bilddaten Der SIFT-Algorithmus arbeitet ausschließlich auf Graustufenbildern. Diese Bilder belegen nur einen Farbkanal. Moderne Grafikkarten können aber auf Vierfarbkanälen (RGBA) gleichzeitig arbeiten (siehe Kapitel 6.7.4). Falls also nur ein Farbkanal verwendet würde, ginge wertvolle Rechenleistung verloren. Deshalb ist es gerade für besonders rechenintensive Aufgaben nicht sinnvoll drei Kanäle ungenutzt zu lassen. So können auch Einkanal-Graustufenbilder in ein entsprechend nur ein Viertel so großes RGBA-Bild gepackt werden. Dabei gibt es zwei verschiedene Packmethoden [Woolley 05]. So kann entweder in jeden der vier Farbkanäle ein Bildsektor gepackt werden oder es können jeweils vier benachbarte Graustufen-Pixel in einen RBGAPixel geschrieben werden. Im Folgenden werden beide Methoden erläutert. Packen der Sektoren Bei dieser Methode wird das Bild in vier gleichgroße Sektoren aufgeteilt. Jeder dieser Sektoren wird in einen der vier Farbkanäle geschrieben. Dadurch kann das Bild theoretisch auf ein Viertel der ursprünglichen Größe gebracht werden. Sind allerdings Operationen, die auf benachbarte Pixel zugreifen müssen, notwendig, muss ein zusätzlicher Overhead mit abgespeichert werden. Der Grund hierfür ist, dass die Shaderprogramme für alle Fragmente die gleiche Operation durchführen, also beispielsweise alle umliegenden Pixel in einer Eingangstextur lesen. 64 6. Realisierung Abbildung 6.3: Packen der Sektoren [Woolley 05] Abbildung 6.4 zeigt den Overhead, der notwendig ist, wenn auf benachbarte Pixel zugegriffen werden muss. Nur so kann dann bei dieser Packmethode gewährleistet werden, dass die Pixel in der Bildmitte richtig berechnet werden. Bei den im SIFT verwendeten Gaussglättungen muss auf benachbarte Pixel zugegriffen werden. Um nicht in der Mitte des Bildes falsch geglättete Stellen zu bekommen, müsste hier ein entsprechend großer Overhead eingeplant werden. Alle Pixel, die jemals in eine Berechnung einfließen, müssten dafür in einem vergrößerten Rand der Textur gespeichert werden. Dieser Rand wäre aufgrund der vielen Gaussglättungen sehr groß. Da die Bilder aus unteren Oktaven aus mehrfach gaussgeglätteten Bildern der höherliegenden Oktaven berechnet werden und diese Bilder dann weiter geglättet werden, müsste der Overhead so groß werden, dass sich der zusätzliche Rechenaufwand zum Packen nicht lohnt. Deshalb ist diese Methode hier nicht einsetzbar. 6.2. Packen der Bilddaten 65 Bereich der in G und A gepackt wird Bereich der in R und B gepackt wird Bereich der in R und G gepackt wird Bereich der in B und A gepackt wird Bereich der in R,G,B und A gepackt wird Abbildung 6.4: Overhead beim Packen der Sektoren Lokales Packen von benachbarten Pixeln Stattdessen werden jeweils vier benachbarte Pixel in ein RBGA-Pixel gepackt. Dadurch gibt es tatsächlich eine Verkleinerung des Bildes auf ein Viertel. Allerdings werden so die Algorithmen komplizierter und damit auch häufig etwas komplexer. Abbildung 6.5: Lokales Packen [Woolley 05] So müssen häufig Koordinaten umgerechnet werden, abhängig davon, ob auf einen Wert in einem R-, in einem G-, in einem B- oder in einem A-Kanal zugegriffen wird. Häufig muss auf die direkten Nachbarn eines Bildpunktes zugegriffen werden. Wo diese Nachbarn liegen, ist davon abhängig, in welchem Farbkanal der Punkt selbst steht. Nachbarschaftsberechnung Für die Berechnung der direkten Nachbarn wurden die Methoden DirectRNeighbors, DirectGNeighbors, DirectBNeighbors und DirectANeighbors entwickelt. Diese Methoden geben die vier direkten Nachbarn zurück, die nicht nur diagonal mit dem Punkt verbunden sind. Sei I(x, y) ein Wert an den ungepackten Koordinaten (x, y). Dann liefern diese Methoden folgenden Vektor: 66 6. Realisierung DirectXNeighbors(x, y) = I(x, y − 1) I(x − 1, y) I(x + 1, y) I(x, y + 1) (6.1) X steht dabei für R, G, B oder A und gibt den Farbkanal an, in dem der Punkt gespeichert ist, dessen Nachbarn berechnet werden sollen. Abbildung6.6 zeigt noch einmal die Berechnung der direkten Nachbarn eines Punktes. DirectRNeighbors x y z w DirectGNeighbors x y z w DirectBNeighbors x y z w DirectANeighbors x y z w Abbildung 6.6: Berechnung der direkten Nachbarn eines Punktes Analog dazu wurden die Methoden XNeighborhood (wobei X auch hier für R, G, B oder A steht) für die komplette Nachbarschaft geschrieben. Diese Methoden liefern eine 3 × 3-Matrix mit dem aktuellen Punkt und allen seinen Nachbarpunkten: I(x − 1, y − 1) I(x, y − 1) I(x + 1, y − 1) I(x − 1, y) I(x, y) I(x + 1, y) XNeighborhood(x, y) = I(x − 1, y + 1) I(x, y + 1) I(x + 1, y + 1) (6.2) In Abbildung 6.7 ist die Berechnung dieser Nachbarschaft zu sehen. Hier werden für jeden der vier in einem Pixel gespeicherten Punkte die Nachbarpunkte berechnet und in eine Matrix geschrieben. Diese Berechnung bedeutet aber einen zusätzlichen Rechenaufwand, der sich nicht immer lohnt. Deshalb werden auch nur der sehr rechenintensive Bau der Gausspyramide und die Berechnung der DoG-Pyramide auf gepackten Bildern durchgeführt. Dadurch müssen aber dann auch die Keypoints in den gepackten Bildern der DoGPyramide gesucht werden. Die veränderten Algorithmen werden in Abschnitt 6.3.1, 6.3.2 und 6.4 näher erläutert. 6.3. Aufbau der Pyramidenteile RNeighborhood 67 GNeighborhood BNeighborhood ANeighborhood Abbildung 6.7: Berechnung der Nachbarschaft eines Punktes Das Ermitteln der Orientierung und die Deskriptorenberechnung, die beide nur für einzelne Keypoints und nicht für alle Punkte eines Bildes durchgeführt werden müssen, erfolgen daher beide auf ungepackten Daten. Dies vereinfacht diese Algorithmen enorm. Die relativ kleine Anzahl an Keypoints verglichen mit der Anzahl der Pixel würde ein Arbeiten auf gepackten Daten nicht rechtfertigen. Außerdem wäre eine Berechnung auf gepackten Daten hier langsamer, da viele Operationen zur Ermittlung der korrekten Koordinaten notwendig wären. Das Entpacken der Daten findet bei der Berechnung der Gradienten statt. 6.3 6.3.1 Aufbau der Pyramidenteile Gaussfaltung Wie bereits in Kapitel 3.4.1 erwähnt, lässt sich die Gaussglättung in eine Faltung in x- und eine anschließende Faltung in y-Richtung aufteilen. Durch diese Aufteilung wird relativ viel Rechenzeit gespart. Für die Glättungen in jede der beiden Richtungen wird ein Fragmentshaderprogramm aufgerufen. Dieses Programm bekommt als Eingangswerte eine Textur mit den zu glättenden Werten und eine Textur, die die Gaussmaske enthält. Die zu glättenden Werte sind allerdings in einer RGBA-Textur gepackt. Um die Glättung dennoch leicht und schnell berechnen zu können, muss die entsprechende Gaussmaske angepasst werden. Exemplarisch wird hier die Gaussfaltung in horizontaler Richtung erläutert. Die Glättung in vertikaler Richtung erfolgt analog. x2 − 2 1 2σ Seien I das Bild, das geglättet werden soll, und g(x) = 2πσ die dafür verwen2e dete, eindimensionale Gaussfunktion. Das Ergebnis wird in das Bild J geschrieben. Die zum Glätten verwendete Gaussmaske habe die Größe 2k + 1. Dann gilt für den horizontal geglätteten Wert an (x, y): J(x, y) = 2k X i=−2k g(i)I(x + i, y) 68 6. Realisierung Die Werte der Funktion lassen sich als Gewichte wi in einer Maske abspeichern. Daher gilt: J(x, y) = 2k X wi I(x + i, y) i=−2k Dies lässt sich auch schreiben als J(x, y) = k X (w2i I(x + 2i, y) + w2i+1 I(x + 2i + 1, y)) i=−k mit w2k+1 = 0. Nun liegen die Daten aber in gepackter Form vor. Das Ergebnis der Glättung soll auch in eine gepackte Textur geschrieben werden. Sei (x̃, ỹ) die Texturkoordinate im ˜ an der sich die ungepackte Position (x, y) befindet. Sei hier o.B.d.A. gepackten Bild I, die Position (x, y) im Farbkanal R gespeichert. Dann gilt für den geglätteten Wert: ˜ ỹ).r = J(x̃, k X ˜ + i, ỹ).r + w2i+1 · I(x̃ ˜ + i, ỹ).g) (w2i · I(x̃ i=−k Auch die Gaussmaske befindet sich in einer Vier-Kanal-Textur ω geschrieben. Zur besseren Unterscheidbarkeit sind hier die vier Farbkanäle, in denen die Gewichte gespeichert sind, mit x, y, z und w bezeichnet. Dabei wird für die Glättung im R-Kanal nur auf die Kanäle x und y zugegriffen. In den anderen beiden Kanälen stehen Werte, die bei den Glättungen der anderen Kanäle Verwendung finden. Der genaue Aufbau der Gaussmaske wird im folgenden Abschnitt erläutert. ˜ ỹ).r = J(x̃, k X ˜ + i, ỹ).r + ω(k + i).y · I(x̃ ˜ + i, ỹ).g) (ω(k + i).x · I(x̃ i=−k Für die horizontale Glättung des B-Kanals werden die gleichen Gewichtungen der Nachbarpixel verwendet. Statt auf die Kanäle R und G wird hier auf die Kanäle B und A zugegriffen. ˜ ỹ).b = J(x̃, k X ˜ + i, ỹ).b + ω(k + i).y · I(x̃ ˜ + i, ỹ).a) (ω(k + i).x · I(x̃ i=−k Die in einem Nachbarpixel gepackten Punkte haben horizontal einen anderen Abstand zu einem in G oder A gespeicherten Punkt als zu Punkten in R oder B. Deshalb wird auf andere in der Gaussmaske gespeicherte Gewichtungen zurückgegriffen. Diese Gewichte sind in z und w gespeichert. Für G gilt also: ˜ ỹ).g = J(x̃, k X i=−k ˜ + i, w̃).r + ω(k + i).y · I(x̃ ˜ + i, ỹ).g) (ω(k + i).z · I(x̃ 6.3. Aufbau der Pyramidenteile 69 Analog gilt für A: ˜ ỹ).a = J(x̃, k X ˜ + i, ỹ).b + ω(k + i).w · I(x̃ ˜ + i, ỹ).a) (ω(k + i).z · I(x̃ i=−k Alle vier Kanäle lassen sich in dem Fragmentprogramm gleichzeitig parallel berechnen. So gilt für den Pixel mit allen vier geglätteten Werten folgendes: k X ˜ ỹ).rgba = J(x̃, ˜ + i, ỹ).rbrb + ω(k + i).yyww · I(x̃ ˜ + i, ỹ).gaga) (ω(k + i).xxzz · I(x̃ i=−k Analog gilt für die anschließend durchgeführte vertikale Faltung: k X K̃(x̃, ỹ).rgba = ˜ + i, ỹ).rgrg + ω(k + i).yyww · I(x̃ ˜ + i, ỹ).baba) (ω(k + i).xxzz · I(x̃ i=−k Hier werden jeweils für den R- und G-Kanal bzw. auf B und A die selben Gaussgewichte verwendet. Erzeugen der Gaussmaske Die Werte in der Gausstextur werden auf der CPU berechnet. Dabei sind folgende Dinge zu beachten: 0 w w w w w w w 0 0 0 0 w w w w w w w 0 x z 1 y w 2 1 x z 3 2 y w 4 3 x z 5 4 y w 6 5 x z 7 6 y w 7 x y z w Abbildung 6.8: Gaussmaske für Maskengröße 7 Bei der horizontalen Faltung unterscheidet sich der Abstand eines in R gepackten Punktes gegenüber einem in G des gleichen Pixels gepackten Punktes zu einem anderen zur Glättung verwendeten Punkt genau um eins. Deshalb müssen unterschiedliche Gaussgewichtungen verwendet werden. Dabei sind aber die Gewichte für B die gleichen wie für R und die für A die gleichen wie für G. Analog unterscheidet sich der Abstand zu anderen Punkten bei der verikalen Faltung zwischen R und B bzw. zwischen G und A jeweils um eins. Um bei der Berechnung während des Zugriffs auf einen benachbarten Pixel auch nur auf einen Pixel der Gausstextur zugreifen zu müssen, sollen alle beim Zugriff auf einen Pixel der Gausstextur verwendeten Gewichtungen in einem Pixel der Gaussmaskentextur stehen. 70 6. Realisierung Dabei sehen die Gewichtungen in der Gausstextur folgendermaßen aus: pixeli .x pixeli .y pixeli .z pixeli .w = = = = w2i w2i−1 pixeli−1 .y pixeli .x Abbildung 6.8 zeigt eine so berechnete Gaussmaske der Größe 7. Der x- und der y-Kanal wird jeweils berechnet. In den Kanälen z und w stehen einfach die um eins verschobenen Werte. Gaussglättung im Fragmentshader 0 w w w w w w w 0 1 x y 2 x 3 y 4 x 5 y 6 x 7 y x 0 y R G R G R G R G R G 0 w w w w w w w 0 1 x y 2 x 3 y 4 x 5 y 6 x 7 y x 0 y B A B A B A B A B A R B Abbildung 6.9: Horizontale Gaussglättung eines R- und eines B-Kanals. Dafür werden die Kanäle x und y der Gaussmaske verwendet. w w 1 0 z 1 0 w w w w w w w 0 w 1 z 2 w 3 z 4 w 5 z 6 w 7 z w R G R G R G R G R G G 0 z 0 w w w w w w w 0 w 1 z 2 w 3 z 4 w 5 z 6 w 7 z w B A B A B A B A B A A Abbildung 6.10: Horizontale Gaussglättung eines G- und eines A-Kanals. Dafür werden die Kanäle z und w der Gaussmaske verwendet. Die horizontale Glättung für alle vier Kanäle eines Pixels ist in den Abbildungen 6.9 und 6.10 zu sehen. 6.3.2 Differenzbilder Für die Bildung des Differenzbildes wird das Shaderprogramm build diff.cg mit zwei benachbarten Texturen der Gausspyramide aufgerufen. Die Werte beider Texturen 6.4. Finden der Keypoints 71 werden voneinander abgezogen. Da das Shaderprogramm die Subtraktion parallel auf jedem Kanal ausführt, spielt es keine Rolle, dass hier mit gepackten Daten gearbeitet wird. Die so berechneten Differenzbilder sind so ebenfalls gepackt. Sei I˜n das gepackte, mit σn geglättete Bild und I˜n+1 das gepackte, mit σn+1 geglättete Bild. Dann gilt für das gepackte Differenzbild D̃n folgendes: D̃n (x, y).rgba = I˜n (x, y).rgba − I˜n+1 (x, y).rgba (6.3) Abbildung 6.11 zeigt noch einmal die Differenzbildung bei gepackten Bildern. R1 G1 R2 G2 R1 − R2 G1 − G2 B1 A1 B2 A2 B1 − B2 A1 − A2 Abbildung 6.11: Differenz gepackter Bilder 6.4 Finden der Keypoints Zum Finden der Keypoints wird das Cg-Programm find peaks.cg aufgerufen. Anders als bei der Implementierung auf der CPU werden hier neben dem Suchen nach potentiellen Keypoints direkt schon die Filterung und die Subpixellokalisierung vorgenommen. Das Cg-Programm erhält als Eingabe drei benachbarte Stufen der DoG-Pyramide, die als gepackte Texturen (siehe 6.2) vorliegen. Als Ausgabe dient eine Textur, die die x- und y-Position der gefundenen Keypoints mit Subpixelgenauigkeit enthält. Außerdem wird in diese Textur noch genau die Zwischenstufe in der Pyramide und damit die genaue Größe der Keypoints geschrieben. Beim Suchen nach Keypoints werden im Fragmentprogramm immer die vier Punkte, die in einem Pixel gepackt sind, untersucht. Sollte einer dieser Punkte ein Keypoint sein, wird die gleiche Stelle in der Ausgabetexur markiert. Außerdem werden die genauen Koordinaten des Keypoints in die Ausgabetextur geschrieben. Da ausschließlich lokale Extremstellen als Keypoints infrage kommen, kann höchstens einer von vier benachbarten, in einem RGBA-Pixel gespeicherten Punkten ein Keypoint sein. Deshalb ist diese Art der Ausgabe möglich. Probleme bereiten hier die gepackten Daten. Da jeder der Punkte in den vier Kanälen unterschiedliche Nachbarpunkte besitzt, müssen viele der folgenden Schritte für jeden Kanal einzeln durchgeführt werden. Deshalb wird versucht RGBA-Pixel, in denen keiner der vier Punkte ein Keypoint ist, möglichst früh zu verwerfen. Im folgenden wird erläutert wie der Bereich, in dem Keypoints gefunden werden, von vorne herein eingegrenzt werden kann. Anschließend werden die durchgeführten Grenzwerttests erläutert. Dabei wird ein früher Grenzwerttest für alle Punkte eines Fragment und mehrere Tests für die einzelnen Farbkanäle vorgenommen. 72 6. Realisierung Textur gerenderter Bereich Keypoint Abbildung 6.12: Bereich in dem nach Keypoints gesucht wird 6.4.1 Bereich in dem nach Keypoints gesucht wird Da die Umgebung, die zur späteren Deskriptorberechnung benötigt wird, bei Punkten in der Nähe des Bildrandes nicht mehr vollständig im Bild liegt, kann für diese Punkte kein aussagekräftiger Deskriptor berechnet werden. Deshalb wird für diese Punkte erst gar nicht überprüft, ob sie Keypoints sind. Dies geschieht hier durch das Rendern eines entsprechend kleineren Rechtecks. Im ungünstigsten Fall ist der Punkt und damit auch seine Umgebung um π4 rotiert. √ Dann muss dieser Punkt einen Abstand von mindestens 8· 2 ≈ 12 vom Rand haben. Da die Keypoints hier in gepackten Bildern gesucht werden, müssen nur Pixel mit einem Abstand von mindestens sechs Pixeln vom Rand untersucht werden. Abbildung 6.12 zeigt eine Textur auf der nach Keypoints gesucht wird. Der um 90 Grad orientierte Keypoint am rechten Rand kann gerade noch gefunden werden, da sein Deskriptor noch vollständig im Bild liegt. Nur Punkte im rotumrandeten Bereich können Keypoints werden. Deshalb wird nur dieser Bereich gerendert und auf die CPU zurückgelesen. 6.4.2 Test für komplettes Fragment Früher Grenzwerttest Zuerst erfolgt die Überprüfung, ob die Punkte eines Fragments einen bestimmten Grenzwert überschreiten. 6.4. Finden der Keypoints 73 Abbildung 6.13: Hier wird getestet, ob mindestens einer der vier Werte in einem Pixel größer als ein gewisser Grenzwert T ist. Für jeden gepackten Pixel wird also überprüft: Pixel.r oder Pixel.g oder Pixel.b oder Pixel.a > > > > threshold threshold threshold threshold Sollte dies für keinen der vier Punkte gelten, kann das Fragment frühzeitig verworfen werden. Andernfalls werden weitere Tests durchgeführt. Dabei wird für jeden einzelnen Kanal überprüft, ob er Maximum und ob er Minimum ist. Dies geschieht auch, wenn schon in einem anderen Kanal ein Keypoint gefunden wurde. Grund hierfür ist die problematische Behandlung von Konditionalverzweigungen auf der GPU. Abbildung 6.13 zeigt diesen Test. 6.4.3 Tests in einzelnen Farbkanälen Ablauf der weiteren Tests Die folgenden Tests werden für jedes noch nicht verworfene Fragment durchgeführt. Dabei werden mehrere Tests für jeden Farbkanal durchgeführt. Sollte einer dieser Tests scheitern, kann im entsprechenden Kanal kein Keypoint liegen. • R ist ein Maximum seines Pixels Damit er als Keypoint gilt, muss er noch folgende Tests bestehen: 1. R ist größer als der Grenzwert 2. R ist größer als die Nachbarn seiner Ebene 3. R ist größer als die Nachbarn auf den benachbarten Ebenen 4. R ist keine Kante Wenn R alle diese Tests besteht, wird die Subpixelposition des Punktes berechnet. Außerdem wird die Position im ungepackten Bild ermittelt. Anschließend werden in jedem Fall noch die anderen Kanäle untersucht und auf Minimum überprüft. • Analog hierzu werden diese Tests auch noch für G, B und A vorgenommen. Außerdem wird noch überprüft, ob in einem dieser Kanäle ein Minimum liegt. Diese Überprüfungen werden noch einmal detailliert für einen in einem beliebigen Kanal X gespeicherten Punkt als Maximum beschrieben. 74 6. Realisierung X ist Maximum des Pixels In diesem ersten Schritt wird für jeden der vier Punkte festgestellt, ob er ein Maximum dieser vier Fragmentpunkte ist. Hierfür muss nur auf Werte des gleichen Fragments zugegriffen werden. Abbildung 6.14: Hier wird getestet, ob im R-Kanal ein maximaler Wert steht. Nur wenn dies der Fall ist, werden für diesen Kanal weitere Tests vorgenommen. Pixel.x ≥ max{Pixel.r, Pixel.g, Pixel.b, Pixel.a} Nur wenn dieser Punkt ein Maximum ist, werden für ihn weitere Tests durchgeführt. Falls mehrere Punkte eines Fragments den gleichen Wert besitzen, werden sie in einem späteren Test noch verworfen. In Abbildung 6.14 wird dies für den Kanal R skizziert. Grenzwertüberprüfung Auch der einzelne Punkt darf den Grenzwert nicht unterschreiten, um Keypoint werden zu können. Deshalb wird für jeden Punkt, der noch als Keypoint in Frage kommt, überprüft, ob auch er das Kriterium Pixel.x > threshold erfüllt. Abbildung 6.15: Hier wird überprüft, ob auch tatsächlich der R-Kanal größer als ein bestimmter Grenzwert T ist. Dies ist in Abbildung 6.15 für den Kanal R angedeutet. X ist lokales Maximum Außerdem wird für diesen Punkt getestet, ob er größer als alle seine acht Nachbarn ist. Hier werden u.a. Punkte verworfen, falls mehrere Maxima existieren. Pixel.x > max(XNeighborhood(Pixel)\{Pixel.x}) (6.4) 6.4. Finden der Keypoints 75 Abbildung 6.16: Es wird überprüft, ob R größer als seine Nachbarn auf der gleichen Pyramidenstufe ist. Wenn nicht, kann dieser Punkt kein Keypoint sein. Zur Überprüfung wird die in Kapitel 6.2 vorgestellte Funktion XNeighborhood verwendet. Hier wird der Punkt Pixel.x zusätzlich mit den Nachbarpunkten verglichen, die in andere Fragments gepackt sind. Wenn der Punkt auch diesen Test besteht, wird er mit seinen Nachbarn auf den angrenzenden Stufen verglichen. In der Abbildung 6.16 ist dies wieder für den Pixel.r dargestellt. X ist globales Maximum Der Punkt muss außerdem einen größeren Wert besitzen als die benachbarten achtzehn Punkte auf der darüber liegenden und der darunter liegenden Stufe der DoGPyramide. Bei diesen Nachbarschaftsvergleichen werden wieder einige der in Abschnitt 6.2 erwähnten Funktionen zum Ermitteln der Nachbarpunkte verwendet. Es muss hier gelten: Pixel.x > max{XNeighborhood(above)} (6.5) Pixel.x > max{XNeighborhood(below)} (6.6) und Kantenfilterung Die Kantenfilterung auf der GPU unterscheidet sich kaum von der Kantenfilterung auf der CPU (siehe 3.5). Aus den Nachbarpunkten der gleichen Stufe, die mit der Funktion XNeighborhood ermittelt werden, wird die zweidimensionale Hesse-Matrix H berechnet. Anschließend wird der Quotient des Quadrats der Spur spur(H)2 und 2 der Determinante det(H) dieser Matrix gebildet und mit dem Wert (r+1) verglichen. r Dieser Wert wird bereits auf der CPU aus dem Parameter r berechnet und an das Fragmentprogramm übergeben, da er so nicht für jeden Pixel neu berechnet werden muss. Nur wenn auch dieser Test erfolgreich ist, wird für den Kanal x die subpixelgenaue Position berechnet. 76 6. Realisierung Berechnung der Subpixelkorrektur Wie bei der Implementierung auf der CPU werden auch hier zur subpixelgenauen Positionierung die dreidimensionale Hesse-Matrix und ein Vektor mit Ableitungen berechnet. Dafür finden auch hier wieder Nachbarschaftsmethoden Verwendung. So entsteht das Gleichungssystem, das in Kapitel 3.5 beschrieben ist. Anders als bei der CPU-Implementierung sind auf der GPU die Iterationsschritte nicht möglich, da Schleifen mit variablen Abbruchbedingungen hier nicht machbar sind. Stattdessen wird die erste Lösung des Gleichungssystems direkt verwendet. Da nur bei relativ wenigen Keypoints die Subpixellokalisierung mehrere Iterationsschritte benötigt, entstehen dadurch keine nachweisbaren Qualitätseinbußen. Das Ergebnis ist ein Korrekturvektor in alle drei Richtungen. Entpacken der Keypointposition von X Von den gefundenen Keypoints sind bisher die Texturkoordinate des Pixels und der Farbkanal, in dem der Keypoint kodiert wurde, bekannt. Hieraus soll nun die ungepackte Position berechnet werden. Sei coord die Texturkoordinate, an der ein Keypoint gefunden wurde. Dann gilt für die tatsächliche Position des Keypoints pos: pos = 2 · coord + subpixelposition + offsetX (6.7) wobei offsetX abhängig vom Farbkanal ist. Für die Offsets gilt: offsetR offsetG offsetB offsetA = = = = (−1, −1) (0, −1) (−1, 0) (0, 0) Analog zu den Maximumtests wird auch für jeden Punkt getestet, ob er ein Minimum ist und somit als Keypoint infrage kommen könnte. Rücklesen auf die CPU Wird irgendwo ein Keypoint gefunden, wird in die Rückgabetextur an dieser Stelle in die ersten beiden Kanäle die subpixelgenaue x- und y-Position des Keypoints geschrieben. Im dritten Kanal steht die genaue Stufe. Der vierte Kanal wird mit einer Eins markiert, um auf der CPU schneller nach den Keypoints suchen zu können. Diese Rückgabetextur wird auf die CPU zurückgelesen. Dort werden die Keypoints ausgelesen und mit genauer Position und Größe in einen Vektor geschrieben. Verbesserungsmöglichkeiten Als mögliche Verbesserung könnten die Daten vor dem Zurücklesen mit der von Daniel Horn beschriebenen Methode [Horn 05] so gepackt werden, dass nur Pixel, die Informationen über einen Keypoint enthalten, auf die CPU übertragen werden. 6.5. Gradientengrößen und Richtungen 6.5 77 Gradientengrößen und Richtungen Zur Berechnung des Gradienten wird das Cg-Programm mag and dir.cg mit einer gepackten Textur der Gausspyramide aufgerufen. Als Ausgabe hat dieses Programm eine Textur mit ungepackten Daten. Diese Ausgabetextur enthält im R-Kanal die Gradientengröße an der entsprechenden Stelle und im G-Kanal die Gradientenorientierung. Anhand der Position des jeweiligen Ausgabepixels wird berechnet, welcher Wert der Eingabetextur verwendet wird. Ausgabetextur m θ Abbildung 6.17: Hier ist die Berechnung des Gradienten für den R-Kanal zu sehen. Dafür werden die Größe m und die Richtung θ mit Hilfe der direkten Nachbarn ermittelt und in die ersten beiden Kanäle einer Ausgabetextur geschrieben. Anders als in der Arbeit von Heymann (siehe Kapitel 5.2.4) wird das Ergebnis hier nicht erst in zwei gepackte Texturen geschrieben, die später wieder entpackt werden müssen, sondern direkt in eine ungepackte Textur. Der Grund für das Entpacken der Daten hier ist, dass es noch relativ einfach ist. Bei der Berechnung der Orientierung und der Deskriptoren eines jeden Keypoints muss auf diese Daten wieder zugegriffen werden. Würde dafür auf gepackte Daten zugegriffen, müssten die Positionen der Keypoints erst wieder umgerechnet werden. Zusätzlich wären viele weitere Verzweigungen abhängig vom aktuellen Farbkanal notwendig. In Abbildung 6.17 wird gezeigt, wie der Gradient für einen Punkt im R-Kanal berechnet und ungepackt in die zwei ersten Kanäle einer Textur geschrieben wird. Der 78 6. Realisierung Inhalt der ersten beiden Kanäle einer Gradiententextur ist in Abbildung 6.18 zu sehen. Links stehen die im ersten Kanal gespeicherten Gradientengrößen und rechts die im zweiten Kanal gespeicherten Orientierungen. Abbildung 6.18: Erster und zweiter Kanal einer Gradiententextur 6.6 Orientierung Für jeden der gefundenen Keypoints muss seine Orientierung berechnet werden. Dafür wird auf der CPU die Methode CalcPeakOrientation aufgerufen. Bei jedem Aufruf werden die Orientierungen aller auf einer Pyramidenstufe gefundenen Keypoints berechnet. Das Programm erhält als Eingabe neben der Keypointliste das ungepackte Gradientenbild der entsprechenden Stufe und die Information, auf welcher Stufe sich diese Keypoints befinden. Für jeden Keypoint wird ein Vertex gerendert. Zum Rendern wird das Cg-Programm histogramm.cg verwendet. Dieses Programm berechnet ein Orientierungshistogramm, das dann auf die CPU zurückgelesen wird. Daraus lässt sich dann die genaue Orientierung eines Keypoints ermitteln. 6.6.1 Erzeugen der Gaussmaskentextur Da die Gradientenrichtungen in der Umgebung eines Keypoints gaussgewichtet, abhängig von der Entfernung, in das Histogramm einfließen, müssen die Gaussgewichte berechnet werden. Die Gewichtung ist dabei von der Entfernung der Stelle zum Keypoint abhängig. Die Gaussgewichte sind für alle Keypoints einer Stufe gleich. Deshalb werden sie schon auf der CPU vorberechnet und in eine zweidimensionale Textur geschrieben. Um nicht unnötig viele Umrechnungsschritte machen zu müssen und da die Gaussmasken relativ klein sind, wird hier nur der erste Farbkanal der Textur verwendet. Die Gausstextur kann dann dem Shaderprogramm übergeben werden. 6.6.2 Rendern der Orientierungshistogramme Zur Berechnung der Orientierung wird pro Keypoint genau ein Vertex gerendert. Der zu rendernde Vertex wird mit Hilfe von OpenGL-Befehlen erzeugt. Er erhält als Texturkoordinaten die Position des Keypoints in der Gradiententextur. Die Vertexkoordinaten werden im Framebuffer der Reihe nach angeordnet. 6.6. Orientierung 79 Attachment0 Attachment1 Attachment2 Attachment3 bin0 bin1 bin4 bin5 bin16 bin17 bin20 bin21 bin2 bin3 bin6 bin7 bin18 bin19 bin22 bin23 bin8 bin9 bin12 bin13 bin24 bin25 bin28 bin29 bin10 bin11 bin14 bin15 bin26 bin27 bin30 bin31 Abbildung 6.19: Die Rückgabe der 32 Bins Da auf der GPU lediglich gather -Zugriffe aber kein scatter möglich ist, kann pro Vertex nur ein Ausgabepixel geschrieben werden. Dieses Ausgabepixel hat vier Farbkanäle, was für die Speicherung eines kompletten Histogrammes viel zu wenig ist. Eine Möglichkeit, die Anzahl der Rückgabewerte pro Vertex und damit pro Keypoint zu erhöhen, sind die sogenannten Multiple Render Targets (siehe Kapitel 4.4.8). Damit kann ein Fragmentprogramm in bis zu vier Ausgabetexturen gleichzeitig schreiben. Dadurch erhöht sich die Anzahl der Rückgabewerte auf sechzehn, was aber, verglichen mit den im SIFT-Algorithmus vorgesehenen 36 Bins, immer noch zu wenig ist. Durch das Packen der Histogrammdaten lässt sich die Anzahl noch einmal verdoppeln. Dabei werden statt eines float-Wertes zwei Werte vom Typ half in einen Farbkanal geschrieben. So kann die Anzahl der auf der GPU berechneten Bins auf insgesamt 32 erhöht werden. Abbildung 6.19 zeigt wie die 32 Bins zurückgegeben werden. Jeweils zwei Bins werden in einem Farbkanal gespeichert. Insgesamt gibt es sechzehn Farbkanäle, jeweils vier verteilt auf vier verschiedene Texturen. 6.6.3 Berechnung im Shaderprogramm Zur Berechnung des Histogramms wird in der Umgebung eines Keypoints jeder Pixel der Gradiententextur betrachtet. Im ersten Kanal dieser Textur steht die Gradientengröße. Diese wird mit einer Gaussgewichtung, abhängig von der relativen Position des Pixels zum Keypoint, multipliziert. So erhält man die Gewichtung mit der dieser Pixel in das Histogramm einfließt. Aus dem zweiten Kanal des Gradientenpixels wird die Richtung ausgelesen. Damit kann das betroffene der 32 Bins ermittelt werden. Zu dessen Wert wird das Produkt aus Gaussgewichtung und Gradientengröße addiert. 80 6. Realisierung Der Quellcode dieses Shaderprogramms steht als Beispiel für die Verwendung von Multiple Render Targets auszugsweise in Anhang F.2. Abbildung 6.20: Die Histogramme werden in vier Texturen geschrieben. In die grau eingefärbten Pixel wurden der Reihe nach Histogrammdaten geschrieben. Nur der rot eingerahmte Bereich der vier Texturen wird auf die CPU zurückgelesen. 6.6.4 Rücklesen der Histogramme Nach der Berechnung stehen in jeder der vier berechneten Texturen acht Bins pro Pixel. Das Histogramm eines Keypoints ist auf vier Texturen verteilt. Die Histogramme verschiedener Punkte sind der Reihe nach auf den Ausgabetexturen angeordnet. Nachdem alle Histogramme berechnet wurden, wird aus jeder Textur das minimale Rechteck, das alle zur Ausgabe verwendeten Pixel enthält, auf die CPU zurückgeschrieben. Dort werden die Histogrammdaten in eine geeignetere Struktur geschrieben und weiterverarbeitet. Abbildung 6.20 zeigt die vier Texturen, in die die Histogrammdaten geschrieben werden. Jedes kleine Quadrat steht dabei für einen Vierkanalpixel. In jedes graue Pixel wurden Bins der Orientierungshistogramme geschrieben. Der rot umrandete Bereich umfasst das kleinste Rechteck, das alle beschriebenen Pixel enthält. Dieser Bereich wird zur Weiterverarbeitung auf die CPU zurückübertragen. 6.6.5 Berechnungen auf der CPU Aus den vier gerenderten Texturen werden die Daten in jeweils ein Array zurückgelesen. Diese Arrays enthalten pro Eintrag zwei gepackte half -Werte. Bevor weitere 6.7. Deskriptoren 81 Berechnungen vorgenommen werden können, müssen die Daten entpackt und in eine geeignete Form gebracht werden. Da die entpackten Histogrammdaten nur für die Berechnung der Orientierung benötigt werden, werden die folgenden Schritte für jeden Pixel einzeln durchgeführt. Dadurch kann Speicherplatz gespart werden. Zunächst werden die auf vier Arrays verteilten, gepackten Histogrammdaten in ein ungepacktes Array geschrieben. Anschließend werden exakt die selben Schritte wie bei der Originalversion des SIFT durchgeführt, also Glättung der Daten und Suchen nach dem globalen Maximum und nach genügend großen lokalen Maxima. Dabei werden gegebenenfalls weitere Keypoints angelegt. Dieses Vorgehen unterscheidet sich nicht von den Berechnungen beim OriginalSIFT. Siehe daher auch Kapitel 3.6. Der Hauptunterschied zwischen dem OriginalSIFT auf der CPU und der Implementierung auf der GPU besteht hierbei in der Anzahl der verwendeten Bins. Beim Original sind es 36, hier dagegen nur 32. 6.7 Deskriptoren Für die Berechnung der Deskriptoren wird auf der CPU das Programm CalcDescriptor aufgerufen. Es erhält als Eingabe eine Liste mit den Keypoints einer Pyramidenstufe und die Textur mit den Gradienten dieser Stufe. Für jeden der Keypoints sollen sechzehn Histogramme berechnet werden. Deshalb wird eine Ausgabetextur angelegt. Sie ist sechzehn Pixel breit. Ihre Höhe ist gleich der Anzahl der Keypoints. In jeder Zeile der Ausgabedatei soll nach der Berechnung der Deskriptor eines Keypoints stehen. Die Berechnung der Deskriptoren wird auf der GPU vom Fragmentprogramm descriptors.cg vorgenommen. Dafür wird für jedes einzelne Histogramm ein Vertex gerendert, also sechzehn Vertice pro Histogramm. Jedem Vertex werden verschiedene Informationen mitgegeben, wie die Orientierung des Keypoints, die Koordinaten des Keypoints in der Gradiententextur und die Koordinaten in der Gaussmaskentextur. Für jeden gerenderten Vertex wird ein Histogramm berechnet. Dieses Histogramm wird in einen RGBA-Pixel der Ausgabetextur geschrieben. Durch das Packen der Histogrammdaten als half lassen sich so acht Bins pro Histogramm berechnen und in die Textur schreiben. Die Ausgabetextur wird nach der Berechnung auf die CPU zurückübertragen. Dort werden die Daten entpackt und weiterverarbeitet. Die nun vorgenommenen Schritte zur Glättung und Normalisierung unterscheiden sich nicht wesentlich von den Schritten bei der Originalimplementierung des SIFT. Die eben genannten Schritte werden in den folgenden Abschnitten ausführlicher erläutert. 6.7.1 Ablauf Folgender Programmablauf wird dazu im Programm CalcDescriptor ausgeführt: 1. Erzeugen der Ausgabetextur 82 6. Realisierung 2. Für jeden Keypoint (a) Berechnen der Schrittweite (b) Für jedes Histogramm: i. Berechnen der Texturkoordinaten des ersten Punktes der Region im Gradientenbild ii. Berechnen der Gaussmaskenkoordinaten iii. Rendern eines Vertex inkl. Gausskoordinaten 3. Zurücklesen der Histogrammdaten aller Keypoints 4. Für jeden Keypoint (a) Entpacken der 16 Histogramme (b) Normieren des Feature-Vektors 6.7.2 Erzeugen der Ausgabetextur Die Ausgabetextur soll nach der Berechnung die Histogrammdaten aller Keypoints der aktuell betrachteten Stufe enthalten. Deshalb wird eine Textur mit genau der richtigen Größe erstellt. Diese Textur ist genau sechzehn Pixel breit. In jedem der Pixel ist ein Histogramm gespeichert. Damit kann der gesamte, auf der GPU berechnete Feature-Vektor eines Keypoints in einer Zeile der Textur stehen. Die Höhe der Textur ist durch die Anzahl der Keypoints bestimmt. Abbildung 6.21 zeigt die Ausgabetextur für die Deskriptorhistogramme. In jedes Pixel wird ein Histogramm mit gepackten Binwerten geschrieben. Ausgabetextur Anzahl der Keypoints Anzahl der Histogramme Abbildung 6.21: Textur, in die alle Deskriptorhistogramme der Keypoints einer Stufe geschrieben werden. In jeden Pixel wird ein Histogramm mit acht Bins geschrieben. Dafür werden die Daten als half gepackt in die vier Farbkanäle geschrieben. 6.7. Deskriptoren 6.7.3 83 Gaussmaskentextur Anders als bei der Berechnung auf der CPU wird hier nicht für jeden Keypoint ein individuell berechnetes σ zum Glätten der Histogrammwerte verwendet. Dafür müssten zuviele Berechnungen auf der Grafikkarte durchgeführt werden. Stattdessen wird eine Gaussmaske pro Oktavenstufe auf der CPU vorberechnet. Diese Maske wird auf die Grafikkarte übertragen und dort im ersten Farbkanal einer Textur gespeichert. Alle auf einer Stufe gefundenen Keypoints verwenden damit dieselbe Gaussmaske zur Berechnung der Deskriptoren. 6.7.4 Rendern der Histogramme Zur Berechnung eines Histogramms wird ein Vertex mit OpenGL erzeugt und gerendert. In der Ausgabetextur sollen alle Histogramme der Reihe nach angeordnet werden. Die Histogramme eines Keypoints stehen dabei in einer Zeile. Daraus ergeben sich die Vertexkoordinaten jedes Vertex. Außerdem werden weitere, für die Berechnung notwendige Daten dem Cg-Programm in Form von Texturkoordinaten übergeben. Aktuelle Grafikkarten ermöglichen es jedem Vertex mehrere verschiedene Texturkoordinatensätze zu übergeben. Dies wird hier genutzt, um die Orientierung des Keypoints, die tatsächliche Position des ersten Punktes, der für die Berechnung des aktuellen Histogramms benötigt wird, die Schrittweite zum nächsten Punkt und die benötigte Position in der Gaussmaske dem Shader zu übergeben. Tabelle 6.1 zeigt die verschiedenen Texturkoordinatensätze und welche Daten in ihnen enthalten sind. Texturkoordinaten TEXCOORD0 TEXCOORD1 TEXCOORD2 TEXCOORD3 Verwendung Orientierung des Keypoints Position des ersten Punktes in der Eingabetextur Schrittweiten um zu den nächsten Punkten zu gelangen erste Position in der Gaussmaske Tabelle 6.1: Texturkoordinatensätze und Verwendung 6.7.5 Das Cg-Programm Im Cg-Programm descriptors.cg wird ein Histogramm berechnet. Dazu erhält es in TEXCOORD1 die erste Position auf der Gradiententextur. An dieser Stelle wird die Gradientenorientierung und die Gradientengröße ausgelesen. Die Gradientengröße wird mit einem Gaussgewicht, welches an der mittels TEXCOORD3 übergebenen Position in der Gausstextur liegt, multipliziert. Dies bestimmt die erste Erhöhung der Bins. Um das Bin zu ermitteln, wird von der ausgelesenen Gradientenorientierung die über TEXCOORD0 übergebene Keypoint-Orientierung abgezogen. Anschließend wird die aktuelle Position in der Gradiententextur und in der Gausstextur verändert. An den neuen Stellen wird dann wieder Gradient und Gaussgewichtung ausgelesen. Um die nächste Stelle in der Gradientenmaske zu erreichen, werden die durch TEXCOORD2 erhaltenen Schrittweiten verwendet. Die Positionen in der 84 6. Realisierung Gaussmaske sind der Reihe nach angeordnet und können so einfach bestimmt werden. So werden nacheinander alle sechzehn Stellen in der Gradiententextur und die passenden Stellen in der Gausstextur ausgelesen, um das Histogramm zu berechnen. Nach der Berechnung des kompletten Histogramms werden die acht Bins als half in die vier Farbkanäle der Ausgabetextur gepackt und zurück auf die CPU gelesen. Ablauf Dabei werden folgende Schritte durchgeführt: 1. Berechnen der nächsten Position auf der Gradiententextur 2. Berechnen der nächsten Gaussmaskenposition 3. Berechnen des Gesamteinflusses auf zwei benachbarte Bins 4. Ermitteln der betroffenen Bins 5. Berechnen der Erhöhung für jedes der beiden Bins und Erhöhen 6. Solange noch weitere Positionen einfließen, weiter mit 1 7. Packen der 32 Bins als half in vier RGBA-Pixel Interpolation zwischen den Bins Im Fragmentprogramm wird für jede Stelle, die ins Histogramm mit einfließen soll, ihr Gesamteinfluss als Produkt der Magnitude an dieser Stelle und eines Gaussgewichts berechnet. Anschließend wird ermittelt, welche zwei benachbarten Bins durch diesen Wert verändert werden. Dafür wird die Gradientenorientierung an dieser Stelle ausgelesen. Von ihr wird die Keypoint-Orientierung abgezogen. Durch den Wert dieser Differenz lassen sich die beiden Bins bestimmen. Dies geschieht wie in Kapitel 3.7.2 beschrieben. 6.7.6 Die übergebenen Parameter In Abbildung 6.22 sind die Eingabeparameter des Cg-Programms zu sehen. Hier werden allerdings nur vier Histogramme berechnet. Links ist die Lage der Histogramme auf der Gradiententextur zu sehen. Dem Cg-Programm wird für jedes einzelne Histogramm die Position der ersten Stelle angegeben, hier angedeutet durch rote Punkte. Außerdem werden die Schrittweiten zum Erreichen der nächsten für das Histogramm relevanten Stelle übergeben. Diese Schrittweiten sind hier durch blaue Pfeile dargestellt. Daneben erhält das Programm für jedes Histogramm die Position auf der Gaussmaskentextur. Diese ist rechts dargestellt. Die Schrittweiten, um den nächsten Punkt auf der Gaussmaske zu erreichen, sind immer eins. Deshalb muss diese Schrittweite nicht dem Programm übergeben werden. Alle diese Parameter werden auf der CPU vorberechnet, um sie dann dem Fragmentprogramm zu übergeben. 6.8. Nächste Oktave berechnen 85 Gaussmaskentextur Gradiententextur Schrittweite in horizontaler und vertikaler Richtung Gauss−Startpositionen Keypoint Orientierung Startpositionen auf der Gradiententextur Abbildung 6.22: Die an das Cg-Programm übergebenen Parameter 6.7.7 Berechnungen auf der CPU Der auf der GPU berechnete, noch unnormierte Feature-Vektor wird gepackt auf die CPU übertragen. Nach dem Entpacken muss er noch normiert werden. Dafür wird exakt das gleiche, in Kapitel 3.7.3 beschriebene Verfahren wie beim OriginalSIFT verwendet. Dadurch erhält jeder Keypoint seinen Deskriptor. 6.7.8 Einschränkungen gegenüber der CPU-Implementierung In dem hier auf der GPU realisierten SIFT-Algorithmus haben alle Samples nur Einfluss auf ihr eigenes Histogramm. Im Originalalgorithmus dagegen verändern Samples am Rand einer Subregion auch das Histogramm der benachbarten Subregion. Dieser Einfluss ist dabei schwächer als der Einfluss auf das eigene Histogramm. Dies könnte auch auf der GPU erreicht werden, indem nicht nur Samples an den sechzehn Positionen in der eigenen Subregion betrachtet würden. Stattdessen müssten an Stellen um die Subregion herum Samples genommen werden. Jedes Sample würde dann mit einem zusätzlichen Wert gewichtet werden. Die Gewichte könnten dem Shaderprogramm als Textur übergeben werden. Dadurch würde die Berechnung der Deskriptoren allerdings etwas langsamer werden. 6.8 Nächste Oktave berechnen Für die nächste Oktave dient das vorletzte gaussverzerrte Bild der aktuellen Oktave als Eingabebild. Dieses in einer RGBA-Textur gepackte Bild muss auf ein Viertel seiner aktuellen Größe verkleinert werden. Dies geschieht mit dem Fragmentprogramm resize.cg. Das Fragmentprogramm verwendet den R-Kanal jedes Eingangspixels und schreibt dessen Wert abhängig von seiner Position in einen der vier Farbkanäle der 86 6. Realisierung verkleinerten Ausgangstextur. Das Ausgangsbild liegt somit auch in gepacktem Zustand vor. Mit diesem verkleinerten Bild werden die gleichen weiteren Schritte vorgenommen wie auf den vorherigen Oktaven. Abbildung 6.23 zeigt den Vorgang des Verkleinerns. R1 G1 R2 G2 Resize B1 A1 B2 A2 R1 R2 R3 G3 R4 G4 R3 R4 B3 A3 B4 A4 Abbildung 6.23: Verkleinern der Textur 7. Auswertung In diesem Kapitel werden zwei wesentliche Anforderungen an den SIFT-Algorithmus auf der GPU untersucht. Dies ist zum einen die Qualität der berechneten Resultate. Nur wenn die Deskriptoren des GPUSIFT ähnlich gut sind wie die seines Gegenstücks auf der CPU, hat er Chancen sinnvoll eingesetzt zu werden. Zum anderen ist eine der wesentlichen Anforderungen an den SIFT auf der GPU schnell zu sein. Deshalb wird neben der Qualität auch die Geschwindigkeit untersucht. Dabei wird neben einem Vergleich mit anderen SIFT-Implementierungen auch eine Analyse der einzelnen Programmkomponenten durchgeführt, um so Verbesserungspotential aufzuzeigen. 7.1 Bewertung einzelner Zwischenergebnisse In diesem Abschnitt werden die einzelnen Komponenten betrachtet. Um die Korrektheit dieser Komponenten sicherzustellen, wurden ihre Resultate, soweit dies möglich war, mit den Ergebnissen des LibSIFT verglichen. 7.1.1 Gaussbilder Die Gaussglättung auf der GPU unterscheidet sich speziell an den Rändern von der Gaussglättung auf der CPU. Während dort eine spezielle Behandlung der Randpixel möglich ist, muss hier für die Randpixel die gleiche Gaussmaske verwendet werden wie bei allen anderen Pixeln. Die Textur ist dazu so eingestellt, dass für Zugriffe auf Pixelpositionen außerhalb der Textur der nächstgelegene Randpixel verwendet wird. Dadurch unterscheiden sich die auf der GPU berechneten Werte etwas von den Werten auf der CPU. Außerdem gibt es durch die geringere Rechengenauigkeit auf der GPU kleinere Unterschiede. Auf der GPU wird der Datentyp float für die Berechnungen verwendet, während auf der CPU mit doppelter Genauigkeit in double gerechnet werden kann. Dennoch sind diese unvermeidlichen Abweichungen nicht so gravierend, dass dadurch die Ergebnisse unbrauchbar wären. 88 7. Auswertung 7.1.2 Differenzbilder Bei der Berechnung der Differenzbilder werden die auf der GPU berechneten, gaussgeglätteten Bilder verwendet. Diese Bilder entsprechen nicht hundertprozentig den auf der CPU berechneten Gaussbildern. Allerdings werden durch die Differenzbildung Unterschiede zum Teil wieder ausgeglichen. Hier sind signifikante Unterschiede höchstens in den sowieso eher unwichtigen Eckpixeln festzustellen. In den getesteten Szenarien lag die Abweichung zwischen den anderen auf der GPU und auf der CPU berechneten Pixeln der Differenzbilder unter 0,01. Die Werte der Pixel liegen hier zwischen -1 und 1. 7.1.3 Position der Keypoint Bei der Entscheidung, ob ein Punkt als Keypoint erkannt wird oder nicht, können winzige Unterschiede ausschlaggebend sein. Deshalb werden beim GPUSIFT nicht immer exakt die selben Keypoints gefunden wie beim LibSIFT. Einige wenige Punkte werden nur im GPUSIFT als Keypoints erkannt, andere dagegen nur im LibSIFT. Allerdings lagen die Unterschiede in den Testbildern bei weniger als fünf Prozent. Dagegen liefert die Subpixellokalisierung auf der GPU teilweise deutlich andere Werte als die auf der CPU. Meistens ist zwar die Tendenz der Subpixelkorrektur richtig, ihr Wert aber nicht. Das Problem bei der Berechnung auf der GPU besteht darin, dass dort lediglich ein Lokalisierungsschritt vorgenommen wird (siehe Kapitel 6.4). 7.1.4 Orientierungen Zur Überprüfung, ob die Keypoint-Orientierungen vom GPUSIFT korrekt berechnet werden, wurden auf einem Testbild Keypoints detektiert und deren Orientierung ermittelt. Anschließend wurde dieses Bild gedreht. Auch auf dem gedrehten Bild wurden Keypoints und Orientierungen mit Hilfe des GPUSIFT berechnet. Diese Keypoints wurden in Positionen des Originalbildes umgerechnet. Außerdem wurde von der Keypoint-Orientierung die Drehung abgezogen. Um Interpolationsfehler auszuschließen und um die gleichen Keypoints zu erhalten, wurden allerdings nur Drehungen um Vielfache von 90 Grad vorgenommen. Die so ermittelten Werte wurden mit den Werten des nicht gedrehten Testbildes verglichen. Abbildung 7.1 zeigt die Unterschiede zwischen den im nicht gedrehten Testbild ermittelten Orientierungen und den in einem um 180 Grad gedrehten Bild ermittelten und anschließend umgerechneten Orientierungen. Dabei hat sich gezeigt, dass sich diese Werte erst ab der dritten Nachkommastelle unterscheiden. 7.2 Qualität des Deskriptors Letztendlich sind für die Bewertung der Qualität die Deskriptoren entscheidend. In ihrer Arbeit [Mikolajczyk 05] haben Krystian Mikolajczyk und Cordelia Schmid gezeigt, dass der SIFT den meisten anderen Deskriptoren1 qualitativ weit überlegen ist. Daher wird hier die GPU-Version nur mit zwei SIFT-Implementierungen auf der CPU, dem LibSIFT und Lowes eigener Implementierung, verglichen. Für die Qualität eines Deskriptors ist hauptsächlich entscheidend, welche Keypoints gematcht werden. Dabei will man möglichst viele Matches, aber auf gar keinen Fall fälschlicherweise einander zugeordnete Keypoints. 1 etwas besser ist nur der viel langsamere GLOH (siehe 2.2.5) 7.2. Qualität des Deskriptors 89 Abbildung 7.1: Rechenungenauigkeiten bei der Orientierungsberechnung 7.2.1 Matchen der Keypoints Zum Matchen der Keypoints zweier Bilder werden deren Deskriptoren miteinander verglichen. Dabei wird für jeden Keypoint des einen Bildes der euklidische Abstand seines Deskriptors zu den Deskriptoren aller Keypoints des anderen Bildes berechnet. ˆ ein Keypoint auf Bild 2. Seien außerdem Sei Kp ein Keypoint auf Bild 1 und Kp ˆ Dann gilt d = (d1 , . . . , d128 ) und dˆ = (dˆ1 , . . . , dˆ128 ) die Deskriptoren von Kp und Kp. ˆ für den Deskriptoren-Abstand δ zwischen Kp und Kp: v u 128 uX ˆ δ(Kp, Kp) = t (dk − dˆk )2 (7.1) k=1 Für jeden Keypoint Kp aus Bild 1 wird auf Bild 2 der Keypoint mit dem geringsten ˆ Deskriptor-Abstand ermittelt. Sei dies für Kp der Keypoint Kp. ˆ höchstens 60 Prozent des AbFalls der Deskriptor-Abstand zwischen Kp und Kp standes jedes anderen Keypoints des zweiten Bildes zu Kp beträgt, gelten Kp und ˆ als gematcht. Kp ˆ ein beliebiger anderer Keypoint aus dem Bild 2. Dann muss für Kp ˆ ˜ 6= Kp Sei Kp gelten, um gematcht zu werden: ˆ < 60% · δ(Kp, Kp) ˜ δ(Kp, Kp) (7.2) Falls die Keypoints in zwei Bildern tatsächlich den selben Punkt beschreiben, ist ihr Abstand sehr gering. Dadurch wird der eine dieser Punkte wahrscheinlich als Keypoint mit dem geringsten Abstand zum anderen ermittelt. Die Bedingung, bezüglich des Deskriporenabstands viel näher als der zweitnächste Keypoint zu sein, 90 7. Auswertung stellt sicher, dass keine Punkte aufeinander gematcht werden, die sich nur zufällig am nächsten sind. Die 60%, verglichen mit dem zweitnächsten Punkt, sind einer starren Grenze überlegen. 7.2.2 Testszenarien Zur Überprüfung der Deskriptorqualität werden Keypoints mit dem GPUSIFT und Lowes SIFT-Implementierung detektiert. Für sie werden mit der jeweiligen SIFTVariante Deskriptoren berechnet. Dies geschieht immer auf zwei unterschiedlichen Bildern der selben Szene. Die Deskriptoren werden gematcht und es wird überprüft, ob die Matches korrekt sind. Der GPUSIFT verwendet dabei zur Keypointsuche lediglich die erste Oktave der Pyramide, da die höheren Oktaven noch nicht ausreichend verifiziert sind. Bilddrehung In diesem Testszenario wurde überprüft, wie stabil die vom GPUSIFT berechneten Deskriptoren gegenüber Bilddrehungen sind. Dafür wurden in zwei unterschiedlich gedrehten Bildern der gleichen Szene mit dem GPUSIFT Keypoints detektiert und diese mit Hilfe der GPUSIFT-Deskriptoren gematcht. Um diese Ergebnisse vergleichen zu können wurde dieser Vorgang mit Lowes SIFT wiederholt. Tabelle7.1 zeigt die Unterschiede zwischen den beiden SIFT-Versionen. Lowes SIFT detektiert viel mehr Keypoints und kann daher auch mehr Matches finden. Alle gefundenen Matches sind bei beiden Versionen auch korrekt. Abbildung 7.2 zeigt die Matches zwischen zwei im Winkel von 180 Grad zu einander orientierten Bildern. Die Keypoints und die dazugehörigen Deskriptoren wurden mit dem GPUSIFT ermittelt. Abbildung 7.2: Gematchte Keypoints bei einer Bilddrehung von 180 Grad Größenänderungen In diesem Szenario wurden zwei unterschiedlich große Bilder der gleichen Szene betrachtet. Das eine Bild hat 87,5 % der Größe des anderen. Größere Unterschiede sind nicht sinnvoll, da die Größenänderung noch durch die unterschiedlich starken Gaussglättungen abgedeckt werden soll. 7.3. Laufzeit 91 Deskriptor Keypoints auf Bild 1 Keypoints auf Bild 2 Matches falsche Matches GPUSIFT 150 150 135 0 Lowes SIFT 566 577 544 0 Tabelle 7.1: Matches bei Bilddrehungen von 0 und 180 Grad In Tabelle 7.2 sind die Ergebnisse dieses Szenarios zu sehen. Abbildung 7.3 zeigt zwei Bilder mit verschiedener Größe. Hier wurden die mit dem GPUSIFT berechneten Deskriptoren zum Matchen verwendet. Abbildung 7.3: Größenänderungen Bewertung Die Anzahl der gefundenen Keypoints ist beim GPUSIFT deutlich geringer als bei Lowes SIFT, was zum einen an der Einstellung einiger Parameter liegt. Zum anderen wurde beim GPUSIFT nur die erste Oktave zur Keypoint-Ermittlung verwendet, wodurch naturgemäß weniger Keypoints gefunden werden. Daher konnten bei Lowes SIFT-Variante auch deutlich mehr Matches gefunden werden. Trotzdem eignen sich die Keypoints und Deskriptoren, die vom GPUSIFT gefunden werden, hervorragend zum Matchen. 7.3 Laufzeit In diesem Abschnitt wird die Laufzeit des GPUSIFT untersucht. Dafür wird zum einen seine Laufzeit mit den Laufzeiten des LibSIFT und der SIFT-Implementierung 92 7. Auswertung Deskriptor Keypoints auf Bild 1 Keypoints auf Bild 2 Matches falsche Matches GPUSIFT 901 1140 281 0 Lowes SIFT 3103 3371 2007 2 Tabelle 7.2: Matches bei Größenveränderung von Lowe verglichen. Zum anderen wird die Laufzeit der einzelnen Komponenten des GPUSIFT ermittelt, um so Ansatzpunkte für Verbesserungen zu bekommen. 7.3.1 Vergleich mit anderen Implementierungen In diesem Abschnitt findet ein Vergleich des GPUSIFT mit Lowes SIFT und mit dem LibSIFT statt. Dazu wird zunächst der Testablauf geschildert und erläutert, wie die Laufzeit gemessen wird. Anschließend werden die verschiedenen Testkandidaten kurz vorgestellt. Schließlich werden die Ergebnisse präsentiert und kommentiert. Testablauf Alle der Kandidaten müssen ein Testbild im pgm-Format laden. Für dieses Bild müssen Keypoints und Deskriptoren berechnet werden. Die Ergebnisse werden in eine Testdatei geschrieben. Für diesen Vorgang wird die Zeit gemessen. Dieser Ablauf wurde gewählt, um den Vergleich mit Lowes SIFT-Implementierung möglich zu machen, die nur binär verfügbar ist und diese Eingaben und Ausgaben vorsieht. Allerdings können durch unterschiedlich effiziente Methoden zum Schreiben der Keypoints und Deskriptoren die Ergebnisse etwas verfälscht sein. Die Zeitmessung Die Zeitmessung wird von einem kleinen Perl-Programm vorgenommen. Dieses Programm ruft jeden der zu testenden SIFT-Algorithmen mehrfach hintereinander mit unterschiedlichen Testbildern als Eingabe auf. Dabei wird die Rechenzeit der SIFTImplementierung gestoppt. Zur Zeitmessung wird dabei das Packet Time::HiRes verwendet. Die vier Testkandidaten Für insgesamt vier verschiedene Versionen, deren Besonderheiten im folgenden kurz beschrieben sind, werden die Laufzeiten berechnet. • Der LibSIFT ist in C# geschrieben. Er kann mit Hilfe von mono aufgerufen werden. Der Quellcode des LibSIFT wurde so angepasst, dass er dem GPUSIFT möglichst ähnlich ist. So wurden beispielsweise für die Orientierungshistogramme 32 statt 36 Bins verwendet. Ansonsten ist es im wesentlichen der in Kapitel 3 beschriebene Algorithmus. • Die SIFT-Version von Lowe war zum Zeitpunkt des Erstellens dieser Arbeit nur als binäre Datei verfügbar. Sie benötigt ein pgm-Bild als Eingabe. Für dieses Bild werden die Keypoints und deren Deskriptoren berechnet, die dann in eine Textdatei geschrieben werden. Bei einigen der Parameter ist nicht klar, wie sie eingestellt sind. 7.3. Laufzeit 93 • Der GPUSIFT ist ausführlich in Kapitel 6 beschrieben. • Beim GPUSIFT ist ein relativ großer Rechenaufwand zur Initialisierung notwendig. Wenn dieser Algorithmus auf eine Bildsequenz angewendet wird, ist dies aber ein einmaliger Aufwand. Deshalb wurde die zur Initialisierung benötigte Zeit gestoppt und von der tatsächlichen Laufzeit abgezogen. Dieser Fall ist mit GPUSIFT* bezeichnet. Die Testbilder Da die SIFT-Implementierung von Lowe nur pgm-Bilder als Eingabe akzeptiert, sind die Testbilder in diesem Format. Zum Testen wurden zum einen zehn Bilder der Größe 640 × 480 und zum anderen zehn auf die Größe 320 × 240 verkleinerte Bilder verwendet. Ergebnisse Abbildung 7.4 und Tabelle 7.3 zeigen die Laufzeiten der einzelnen Versionen. Dabei ist Lowes SIFT bei kleinen Bildern mit durchschnittlich 0,67 Sekunden pro Bild am schnellsten. Bei Bildern der Größe 640×480 ist dagegen der GPUSIFT ohne Initialisierung mit 1,73 der schnellste. Lowes SIFT braucht dafür 2,46 Sekunden, der LibSIFT 2,60 Sekunden. Abbildung 7.4: Vergleich der Laufzeiten verschiedener SIFT-Implementierungen Bewertung Bei Bildern der Größe 640×480 ist der GPUSIFT ohne Initialisierung am schnellsten. Allerdings ist er nicht so signifikant schneller, dass sich ein Einsatz unbedingt 94 7. Auswertung Bildgröße 320×240 640×480 LibSIFT 2,40 2,60 Lowes SIFT GPUSIFT 0,67 2,28 2,46 2,52 GPUSIFT* 1,44 1,73 Tabelle 7.3: Laufzeiten pro Bild verschiedener SIFT-Implementierungen in Sekunden lohnt, zumal der LibSIFT durch eine C-Portierung möglicherweise noch beschleunigt werden könnte. Daher sind am GPUSIFT noch einige Laufzeit-Optimierungen notwendig. Die guten Ergebnisse von Lowes SIFT insbesondere bei kleinen Bildern könnten durch eine bessere Routine zum Laden von pgm-Bildern zustande kommen. 7.3.2 Laufzeit einzelner Komponenten Insbesondere zum Optimieren der Geschwindigkeit ist die Kenntnis der Laufzeit einzelner Komponenten notwendig. Deshalb wird in diesem Abschnitt untersucht, welche Komponente des Algorithmus wieviel Rechenzeit verbraucht. Dabei ist dies bei GPU-Programmen nicht so einfach wie bei reinen CPU-Programmen, wo man einfach einen bereits verfügbaren Profiler mitlaufen lassen kann, der die Anzahl der Aufrufe bestimmter Befehle und ihre Ausführungsdauer mitprotokolliert. Daher wurde eine eigene Klasse zum Profilen geschrieben. Der Profiler Das Profilen des SIFT wird von der Klasse tProfiler übernommen. Mit Hilfe dieser Klasse lassen sich die Laufzeit und Häufigkeit der Aufrufe einzelner Komponenten messen. Allerdings ist es damit nicht möglich, die Dauer einzelner Befehle innerhalb eines Cg-Programms zu ermitteln. Stattdessen wird der SIFT-Algorithmus in acht Bereiche oder Komponenten aufgeteilt, deren Zeit gestoppt wird. Dafür wird für jeden dieser Bereiche eine Instanz von tProfiler angelegt. Bei jedem Aufruf einer dieser Komponenten kann mit der Methode Start die Zeitmessung gestartet werden. Beim Beenden dieser Komponente wird mit der Methode Stop die Messung angehalten. Zur eigentlichen Messung wird die Zeitfunktion gettimeofday aus sys/time.h verwendet, die auf die Millionstelsekunde genaue Messungen macht. Allerdings sollte man dabei beachten, dass die Ergebnisse dieser Art der Messung stark von äußeren Einflüssen wie anderen, parallel laufenden Prozessen abhängen. Außerdem kann das Messen selbst die Messung leicht beeinflussen. Dennoch kann sie als Orientierung dienen, an welchen Stellen noch Optimierungspotential vorhanden ist. Die Komponenten Zur Laufzeitmessung wird der Algorithmus in acht verschiedene Komponenten eingeteilt. Die Zeit, die zur Initialisierung und zum Laden eines Bildes in den Speicher der CPU notwendig ist, wird dabei nicht berücksichtigt. Der Grund hierfür ist, dass die Initialisierung bei der Untersuchung vieler Bilder, zum Beispiel eines Videostroms, nur einmal vorgenommen werden muss. Diese Bilder befinden sich dabei häufig schon im Speicher und müssen nicht erst noch mit OpenCV-Methoden in den Speicher geladen werden. Es wurden folgende Komponenten untersucht: 7.3. Laufzeit 95 Abbildung 7.5: Laufzeitanteil einzelner Komponenten auf der ersten Oktave • Gaussglättungen • Berechnung der Differenzbilder • Gradientenberechnung • Finden der Keypoints • Ermitteln der Keypoint-Orientierung • Berechnung der Deskriptoren • Das Laden des Bildes aus dem Speicher der CPU auf die GPU • Die Vorbereitung der nächsten Oktave Außerdem wird noch die Gesamtlaufzeit aller Komponenten zusammen gemessen und so ermittelt, wieviel Zeit für einzelne Befehle, die zwischen den Aufrufen der gemessenen Komponenten stattfinden, benötigt wird. Ergebnisse Für die Untersuchung wurde ein Bild der Größe 640 × 480 verwendet. In einem ersten Szenario wurden die Keypoints nur auf der ersten Oktave detektiert. Für diese Keypoints wurden zuerst die Orientierung und dann die Deskriptoren berechnet. Durch die Einschränkung auf eine Oktave fällt das Verkleinern des Bildes weg. Außerdem müssen viele der Komponenten seltener ausgeführt werden. In einem zweiten Szenario wurde nach und nach die komplette Pyramide aufgebaut und zum Finden der Keypoints verwendet. Dadurch müssen Operationen wie die Gaussglättung häufiger ausgeführt werden. Allerdings müssen bei den zusätzlichen Aufrufen nur kleinere Texturen verarbeitet werden. 96 7. Auswertung In Abbildung 7.5 ist der Anteil der einzelnen Komponenten an der Gesamtlaufzeit zu sehen. Dabei zeigt sich, dass die drei Komponenten Finden der Keypoints, Ermitteln der Keypoint-Orientierung und Berechnung der Deskriptoren zusammen über 87% der gesamten Rechenzeit benötigen. Das Fragmentprogramm zum Finden der Keypoints hat relativ viele Verzweigungen, um für jeden der vier in einem Fragment gepackten Punkte ermitteln zu können, ob er ein Keypoint ist (siehe dazu Kapitel 6.4). Komponente Gaussglättungen Differenzbilder Gradienten Keypoints Orientierungen Deskriptoren Bild laden Nächste Oktave Rest Gesamt Aufrufe 6 5 3 3 3 3 1 0 1 1 Laufzeit Anteil 0,0425 Sek. 2,06% 0,0194 Sek. 0,94% 0,1818 Sek. 8,82% 0,6643 Sek. 32,22% 0,7262 Sek. 35,22% 0,4191 Sek. 20,33% 0,0065 Sek. 0,31% 0,00 0,0018 Sek. 0,09 2,0616 Sek. 100% Tabelle 7.4: Laufzeiten einzelner Komponenten auf der ersten Oktave In den beiden anderen Komponenten werden immer nur einzelne Punkte gerendert. Für diese Punkte, die das Shaderprogramm als einzelne Fragmente erreichen, müssen Histogramme berechnet werden. Dafür müssen einige Zugriffe auf Nachbarfragmente erfolgen, um Samples zu berechnen. Für jedes Sample müssen mehrere konditionale Abfragen überprüft werden, um zu ermitteln, welche Bins beeinflusst werden (siehe Kapitel 6.7.4 und Kapitel 6.7.4). Insbesondere die konditionalen Verzweigungen sind auf der Grafikkarte sehr teuere Operationen. Tabelle 7.4 zeigt noch einmal die Laufzeitanteile der einzelnen Komponenten. Daneben enthält sie die Zeit, die durch das Ausführen der Komponenten insgesamt verbraucht wird. Außerdem ist angegeben, wie oft eine Komponente aufgerufen wird. So werden beispielsweise für die Berechnung der ersten Okatve sechs Gaussglättungen durchgeführt. Dies dauert zusammen etwas mehr als vier Hundertstelsekunden, was einen Anteil an der Gesamtlaufzeit von etwa zwei Prozent ausmacht. Die komplette Ausführung des Algorithmus auf der ersten Oktave dauert etwa zwei Sekunden. Abbildung 7.6 zeigt die Verteilung der Laufzeitanteile bei Verwendung der kompletten Gausspyramide mit drei Oktaven. Dabei sind zusätzliche Rechenschritte zum Verkleinern eines Gaussbildes notwendig. Diese fallen mit einem Anteil von weniger als einem Prozent aber nicht weiter ins Gewicht. Die Verteilung der Anteile ist hier im Wesentlichen die gleiche wie auf der ersten Oktave. Auch hier verbrauchen wieder die Komponenten Finden der Keypoints, Ermitteln der Keypoint-Orientierung und Berechnung der Deskriptoren und mit Abstrichen die Gradientenberechnung die meiste Rechenzeit. Diese vier Komponenten benötigen zusammen über 95% der Laufzeit. 7.3. Laufzeit 97 Abbildung 7.6: Laufzeitanteil einzelner Komponenten auf allen Oktaven Tabelle 7.5 zeigt dies noch einmal detailliert. Die Berechnung des Algorithmus auf der kompletten Pyramide ist mit 2,24 Sekunden gegenüber 2,06 Sekunden für die erste Oktave nicht wesentlich teurer. Komponente Gaussglättungen Differenzbilder Gradienten Keypoints Orientierungen Deskriptoren Bild laden Nächste Oktave Rest Gesamt Aufrufe Laufzeit Anteil 16 0,0428 Sek. 1,90% 15 0,0216 Sek. 0,96% 9 0,1891 Sek. 8,42% 9 0,6405 Sek. 28,52% 9 0,8846 Sek. 39,39% 9 0,4486 Sek. 19,97% 1 0,0065 Sek. 0,29% 2 0,0095 Sek. 0,42% 1 0,0124 Sek. 0,55% 1 2,2461 100% Tabelle 7.5: Laufzeiten einzelner Komponenten auf allen Oktaven Bewertung Diese Untersuchung hat klar gezeigt, welche Komponenten am meisten Laufzeit verbrauchen. Deshalb muss versucht werden, diese Komponenten zu beschleunigen. Außerdem sollte getestet werden, ob sich diese Komponenten möglicherweise schneller auf der CPU berechnen lassen. 98 7. Auswertung 8. Zusammenfassung und Ausblick 8.1 Zusammenfassung In dieser Arbeit wurde eine Version des SIFT-Algorithmus, die auf der Grafikkarte läuft, entwickelt. Dabei wurde ein lauffähiges Programm geschrieben, das Keypoints detektiert und Deskriptoren für diese Keypoints berechnet. Dieses Programm hat sich allerdings als zu langsam erwiesen, um tatsächlich gewinnbringend eingesetzt zu werden. Bei der Entwicklung des Programms wurden neue Konzepte entworfen und erprobt. So konnte unter anderem aufgezeigt werden, wie der SIFT-Algorithmus prinzipiell auf der GPU umgesetzt werden kann und an welchen Stellen Optimierungspotential vorhanden ist. Auf diese Erkenntnisse kann bei künftigen Entwicklungen aufgebaut werden. 8.2 Ausblick Für einen sinnvollen Einsatz des GPUSIFT müssen einige Verbesserungen vorgenommen werden. So gibt es zum einen noch einige Möglichkeiten die Qualität der gefundenen Keypoints und ihrer Deskriptoren zu verbessern. Zum anderen muss die Geschwindigkeit deutlich erhöht werden. Zur Qualitätsverbesserung der Deskriptoren können, wie in Kapitel 6.7.8 angedacht, auch Samples der Nachbarregionen eines Histogramms verwendet werden. Daneben sollte die Subpixellokalisierung und die Detektion von Keypoints auf höheren Oktaven genauer untersucht werden. Um die neu entwickelte Version des SIFT-Algorithmus gewinnbringend einsetzen zu können, ist allerdings die Erhöhung seiner Geschwindigkeit viel wichtiger als eine Verbesserung seiner jetzt schon verwendbaren Ergebnisse. Dazu ist die LaufzeitOptimierung einzelner Komponenten notwendig. Insbesondere die Gradientenberechnung, das Finden von Keypoints sowie die Berechnung ihrer Orientierungen und ihrer Deskriptoren muss massiv beschleunigt werden. Dafür sollten die folgenden Ansätze überprüft werden. 100 8. Zusammenfassung und Ausblick So sollte getestet werden, ob es nicht besser ist, auf gepackte Daten bei der Berechnung der Gausspyramide und der DoG-Pyramide zu verzichten. Durch das Packen der Daten können diese Schritte zwar sehr schnell berechnet werden. Allerdings muss man bei der Gradientenberechnung und dem momentan sehr teuren Finden von Keypoints dafür bezahlen, da dort zusätzliche Operationen zum Entpacken notwendig werden. Deshalb könnte die Verwendung ungepackter Daten vorteilhafter sein. Ein vielversprechender Ansatz, der ungepackte Daten benötigt, ist die Verwendung von Blending-Operationen zum Finden der Keypoints. Das Zurücklesen der Keypoints kann möglicherweise durch ein von Daniel Horn entwickeltes Kompressionsverfahren [Horn 05] beschleunigt werden. Der Einsatz dieses Verfahrens wird aber sicherlich die Geschwindigkeitsprobleme nicht alleine lösen können. Erfolgsversprechender erscheint das neue Tutorial1 zum schnellen Übertragen von Daten auf die GPU und von der GPU zur CPU, das Dominik Göddeke auf seiner Seite anbietet. So kann durch die Verwendung der dort vorgestellten Techniken der Datentransfer und damit der gesamte Algorithmus beschleunigt werden. Der schnellere Datentransfer könnte möglicherweise die Option eröffnen, nur einzelne Teile des Algorithmus auf der GPU zu berechnen, während andere Teile auf der CPU laufen. So könnte der Aufbau von Gausspyramide und DoG-Pyramide auf der Grafikkarte stattfinden, während Komponenten mit vielen konditionalen Verzweigungen wie die Ermittlung der Keypoint-Orientierung oder das Berechnen der Deskriptoren auf der CPU berechnet werden. 1 http://www.mathematik.uni-dortmund.de/~goeddeke/gpgpu/tutorial3.html A. Verwendete Hardware A.1 Grafikkarte GeForce 7600 GT/PCI/SSE2 Abbildung A.1: Die GeForce 7600 GT Name Erscheinungsdatum Schnittstelle Chiptakt Speicher Speichertakt Bandbreite 1 GeForce 7600 GT März 2006 PCI-Express 560 MHz 256 MB 700 MHz 22,4 GB/s Tabelle A.1: GPU des verwendeten Rechners 1 http://pc.watch.impress.co.jp 102 A.2 A. Verwendete Hardware Rechner processor vendor id cpu family model model name stepping cpu MHz cache size processor vendor id cpu family model model name stepping cpu MHz cache size 0 GenuineIntel 15 6 Intel(R) Pentium(R) D CPU 3.40GHz 4 3392.498 2048 KB 1 GenuineIntel 15 6 Intel(R) Pentium(R) D CPU 3.40GHz 4 3392.498 2048 KB Tabelle A.2: CPU des verwendeten Rechners B. Verschiedene Profile Verschiedene Grafikkarten verfügen über unterschiedliche Funktionalitäten. Befehle, die auf einer neueren Grafikkarte lauffähig sind, können möglicherweise nicht auf älteren Karten ausgeführt werden. Außerdem unterscheiden sich die Programme für den Vertexshader von Fragmentprogrammen. Um dieser Tatsache Rechnung zu tragen, gibt es in Cg verschiedene Profile. Je nach Profil sind manche Anweisungen gültig und andere nicht. Beim Laden eines CgProgramms auf die GPU muss immer ein Profil angegeben werden. Tabelle B.1 zeigt eine Liste von Vertex- und Fragment-Profilen. Profilname vp20 vp30 vp40 fp20 fp30 fp40 Name in C++ CG PROFILE VP20 CG PROFILE VP30 CG PROFILE VP40 CG PROFILE FP20 CG PROFILE FP30 CG PROFILE FP40 Bemerkung Vertexprofil Vertexprofil Vertexprofil Fragmentprofil, sehr eingeschränkt Fragmentprofil, noch ohne MRT Fragmentprofil, in dieser Arbeit verwendet Tabelle B.1: Verschiedene Vertex- und Fragmentprofile Statt ein konkretes Profile beim Laden eines Fragmentprogramms anzugeben, ist es auch möglich das beste der verfügbaren Profile zu verwenden. Um dieses zu ermitteln, gibt es folgenden Befehl: CGprofile fragment_profile = cgGLGetLatestProfile(CG_GL_FRAGMENT); Allerdings kann es dann zu Fehlern kommen, falls dieses Profil nicht ausreicht, um das Cg-Programm auszuführen. So werden nicht nachvollziehbare Ergebnisse geliefert. Stattdessen sollte man das niedrigste Profil, mit dem die eigenen Cg-Programme noch laufen, verwenden und vor der Ausführung testen, ob dieses Profil auf der vorhandenen Hardware verfügbar ist. 104 B. Verschiedene Profile C. Cg C.1 Anlegen eines Cg-Kontextes Alle Cg-Programme existieren in einem sogenannten CgContext. Dieser Kontext muss eigentlich nur einmal angelegt werden. Dies geschieht mit: CGcontext cg_context = cgCreateContext(); C.2 Erzeugen und Laden der Cg-Programme Die Cg-Programme können in einzelnen Textdateien gespeichert werde. Um sie ausführen zu können, müssen aus den Textdateien auf der GPU ausführbare Programme erzeugt werden. Diese können dann auf die GPU geladen werden. Beim Erzeugen der Programme muss das verwendete Profil angegeben werden. Mit folgendem Code können diese Schritte durchgeführt werden: cgGLSetOptimalOptions(fragment_profile); CGprogram fragment_program = cgCreateProgramFromFile ( cg_context, CG_SOURCE, file_name, fragment_profile, entry_function, NULL ); cgGLLoadProgram (fragment_program); C.3 Cg-Beispielcode Folgendes Fragmentshaderprogramm wird verwendet, um ein Differenzbild auf der GPU zu berechnen: struct G_Out { float4 color: COLOR; }; G_Out main( in float2 coords : TEXCOORD0, 106 C. Cg uniform samplerRECT Image1, uniform samplerRECT Image2 ) { G_Out return_statement; return_statement.color = return return_statement; } texRECT(Image2,coords) - texRECT(Image1, coords); Die zwei Bilder der Gausspyramide werden dem Programm als Texturen Image1 und Image2 übergeben. Die aktuelle Position, an der die Differenz gebildet wird, befindet sich in TEXCOORD0. Mit texRECT lässt sich der RGBA-Wert einer Textur an einer bestimmten Stelle ermitteln. C.4 Ausführung des Cg-Programms Um ein Cg-Programm auszuführen, werden mit OpenGL erzeugte Objekte, meist ein den kompletten Framebuffer bedeckendes Rechteck, gerendert (siehe Anhang D). Folgende Befehle müssen aber vorher noch ausgeführt werden: Binden der Ausgabetextur an ein Attachment glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT3_EXT, textarget, diff_texture, 0); Binden des Cg-Programms cgGLBindProgram(cg_fragment_build_diff); cgGLSetTextureParameter( cgGetNamedParameter(cg_fragment_build_diff, "Image1"), src_texture1 ); cgGLEnableTextureParameter( cgGetNamedParameter(cg_fragment_build_diff, "Image1") ); cgGLSetTextureParameter( cgGetNamedParameter(cg_fragment_build_diff, "Image2"), src_texture2 ); cgGLEnableTextureParameter( cgGetNamedParameter(cg_fragment_build_diff, "Image2") ); Festlegen des Ausgabe-Attachments glDrawBuffer(GL_COLOR_ATTACHMENT3_EXT); C.5. Semantics C.5 107 Semantics Folgende Semantics stehen in Cg zur Verfügung: Semantic POSITION WINPOS TEXCOORD0 TEXCOORD1 TEXCOORD2 .. . Typ float4 float2 float2 float2 float2 .. . Aufgabe Position eines Vertex Position eines Fragments im Framebuffer erste Texturkoordinate eines Fragments oder Vertex zweite Texturkoordinate eines Fragments oder Vertex dritte Texturkoordinate eines Fragments oder Vertex .. . COLOR COLOR0 COLOR1 COLOR2 COLOR3 float4 float4 float4 float4 float4 RGBA-Farbwert eines Vertex oder Fragments RGBA-Farbwert eines Fragments in COLOR ATTACHMENT0 RGBA-Farbwert eines Fragments in COLOR ATTACHMENT1 RGBA-Farbwert eines Fragments in COLOR ATTACHMENT2 RGBA-Farbwert eines Fragments in COLOR ATTACHMENT3 Tabelle C.1: Verschiedene Semantics 108 C. Cg D. OpenGL-Befehle D.1 Start Folgende Befehle sind zu Beginn notwendig, um OpenGL zu verwenden: glutInit (&argc, argv); glutCreateWindow(""); glewInit(); D.2 Rendern von Rechtecken Um Berechnungen auf der Grafikkarte durchzuführen, müssen Objekte gerendert werden. Üblicherweise werden Rechtecke von der Größe des Framebuffers gerendert. Die Texturen, die die Eingangsdaten enthalten, haben dabei auch oftmals genau diese Größe. Mit folgendem Befehl wird ein solches Rechteck gerendert: glPolygonMode(GL_FRONT,GL_FILL); glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0); glTexCoord2f(tex_width, 0.0); glVertex2f(tex_width, 0.0); glTexCoord2f(tex_width, tex_height); glVertex2f(tex_width, tex_height); glTexCoord2f(0.0, tex_height); glVertex2f(0.0, tex_height); glEnd(); 110 D. OpenGL-Befehle Dafür werden die Eckpunkte angegeben. Zusätzlich erhalten diese Punkte Texturkoordinaten, um auf Eingangstexturen zugreifen zu können. In diesem Beispiel unterscheiden sich die Texturkoordinaten lediglich hinsichtlich ihrer Genauigkeit von den Vertexkoordinten. Die Texturkoordinaten sind genauer. Dabei ist noch zu beachten, dass die erste Texturkoordinate nicht (0,0) sondern (0.5,0.5) ist. Dementsprechend liegt das letzte Pixel rechts unten an (tex_width - 0.5, tex_height - 0.5). D.3 Rendern einzelner Punkte Um einzelne kleinere Berechnungen durchzuführen, können auch einzelne Punkte gerendert werden. Dies wird in dieser Arbeit verwendet um Orientierungen und Deskriptoren für einzelne Keypoints zu berechnen. Dafür werden Vertices erzeugt. Die Vertexkoordinaten geben dabei die Ausgabestelle im Framebuffer an. Daneben können jedem Vertex mehrere Texturkoordinaten übergeben werden. Im folgenden Beispiel werden einige einzelne Punkte gerendert. Jedem dieser Punkte werden in den Semantics TEXCOORD0 und TEXCOORD1 Daten übergeben. glBegin(GL_POINTS); for (int point_nr = 0; point_nr < number_of_points; point_nr++) { glMultiTexCoord2f(GL_TEXTURE0, value1, value2); glMultiTexCoord1f(GL_TEXTURE1, value3); ... glVertex2f(output_x, output_y); } glEnd(); E. Übertragen und Rücklesen der Daten E.1 CPU auf GPU Die Daten, die als Textur auf die GPU gebracht werden sollen, liegen hier als Array float* data vor. Dann kann mit diesen Daten durch folgende Befehle eine Textur bereits angelegte Textur gefüllt werde: glBindTexture(textarget,start_texture); glTexSubImage2D(textarget,0,0,0, tex_width,tex_height, GL_RGBA,GL_FLOAT, data); Wenn eine RGBA-Textur gefüllt werden soll, werden vier benachbarte Werte aus dem Array in einen Pixel geschrieben. E.2 GPU auf CPU Zum Zurücklesen auf die CPU wird zunächst das Attachment, aus dem gelesen werden soll, angegeben. Anschließend können die Daten aus diesem Attachment in ein Array geschrieben werden. glReadBuffer(attachment); glReadPixels(0, 0, tex_width, tex_height ,GL_RGBA,GL_FLOAT, data) 112 E. Übertragen und Rücklesen der Daten F. Multiple Render Targets In diesem Kapitel wird am Beispiel der Berechnung des Orientierungshistogramms auf der GPU die Verwendung von MRTs erläutert. F.1 F.1.1 Aufruf in OpenGL Binden der Ausgabetexturen Zunächst werden hier vier Ausgabetexturen an die vier verfügbaren Attachments gebunden. In diese Attachments und damit in die entsprechenden Texturen soll später das Ergebnis geschrieben werden. glFramebufferTexture2DEXT( glFramebufferTexture2DEXT( glFramebufferTexture2DEXT( glFramebufferTexture2DEXT( F.1.2 GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textarget, hist_textures[0], 0 ); GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, textarget, hist_textures[1], 0 ); GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT, textarget, hist_textures[2], 0 ); GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT3_EXT, textarget, hist_textures[3], 0 ); Festlegen der Attachments Nach dem Binden des Cg-Programms und dem Eingeben der Eingangsdaten wird festgelegt, in welches Attachment geschrieben wird. GLenum buffers[] = {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT, GL_COLOR_ATTACHMENT3_EXT}; glDrawBuffers(4, buffers); 114 F. Multiple Render Targets F.2 Cg-Programm struct float4 float4 float4 float4 }; G_Out { bins0: COLOR0; bins1: COLOR1; bins2: COLOR2; bins3: COLOR3; G_Out main( uniform samplerRECT image, uniform samplerRECT gauss_mask, float2 winPos: TEXCOORD0 ) { G_Out output; float dir; float weight; float pi = 3.14159265; half4 bin0 = half4(0,0,0,0); ... half4 bin7 = half4(0,0,0,0); int radius = 9; for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) { weight = texRECT(gauss_mask, float2(radius + x, radius + y)).x ; weight = weight * texRECT(image, winPos + float2(x, y)).x ; dir = pi + texRECT(image, winPos + float2(x, y)).y ; if ( dir < pi/16) { bin0.x += weight; } if ( pi/16 <= dir && dir < 2 * pi/16) { bin0.y += weight; } ... if ( 32 * pi/16 <= dir) { bin0.x += weight; } } } F.3. Rücklesen output.bins0 = float4( output.bins1 = float4( output.bins2 = float4( output.bins3 = float4( 115 pack_2half(bin0.xy), pack_2half(bin1.xy), pack_2half(bin2.xy), pack_2half(bin3.xy), pack_2half(bin4.xy), pack_2half(bin5.xy), pack_2half(bin6.xy), pack_2half(bin7.xy), pack_2half(bin0.zw), pack_2half(bin1.zw) ); pack_2half(bin2.zw), pack_2half(bin3.zw) ); pack_2half(bin4.zw), pack_2half(bin5.zw) ); pack_2half(bin6.zw), pack_2half(bin7.zw) ); return output; } F.3 Rücklesen glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); glReadPixels(0, 0 , width, winpos_y + 1, GL_RGBA, GL_FLOAT, hist0); glReadBuffer(GL_COLOR_ATTACHMENT1_EXT); glReadPixels(0, 0 , width, winpos_y + 1, GL_RGBA, GL_FLOAT, hist1); glReadBuffer(GL_COLOR_ATTACHMENT2_EXT); glReadPixels(0, 0 , width, winpos_y + 1, GL_RGBA, GL_FLOAT, hist2); glReadBuffer(GL_COLOR_ATTACHMENT3_EXT); glReadPixels(0, 0 , width, winpos_y + 1, GL_RGBA, GL_FLOAT, hist3); Hier wird von jeder der vier Ausgabetexturen das kleinste Rechtecke, das alle HistogrammInformationen enthält, zurückgelesen. 116 F. Multiple Render Targets Abbildungsverzeichnis 1.1 Gematchte Keypoints . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.1 Aufteilung der Keypointumgebung beim GLOH und beim SIFT. . . . 15 3.1 Bildpyramide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.2 Ablauf des SIFT-Algorithmus auf der CPU . . . . . . . . . . . . . . . 19 3.3 Gausspyramide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.4 Gaussglättung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.5 Verschiedene eindimensionale Gaussfunktionen . . . . . . . . . . . . . 22 3.6 Differenzbild aus zwei Gaussbildern . . . . . . . . . . . . . . . . . . . 23 3.7 Keypointsuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.8 Vergleich mit Nachbarn [Lowe 04] . . . . . . . . . . . . . . . . . . . . 25 3.9 Zweidimensionale DoG-Funktion . . . . . . . . . . . . . . . . . . . . . 26 3.10 Verschiedene eindimensionale DoG-Funktionen . . . . . . . . . . . . . 27 3.11 Berechnung der Gradientengröße und Richtung jedes Pixels. . . . . . 31 3.12 Berechnung der Orientierung eines jeden Keypoints . . . . . . . . . . 32 3.13 Ungeglättetes Histogramm zur Orientierungsberechnung . . . . . . . 33 3.14 Geglättetes Histogramm zur Orientierungsberechnung . . . . . . . . . 33 3.15 Genaue Orientierung eines Keypoints . . . . . . . . . . . . . . . . . . 34 3.16 Berechnung der Deskriptoren . . . . . . . . . . . . . . . . . . . . . . 35 3.17 Drehung der Keypoint-Umgebung . . . . . . . . . . . . . . . . . . . . 36 3.18 Deskriptorenberechnung [Heymann 05a] . . . . . . . . . . . . . . . . . 38 4.1 Die Grafikkarte als Stream Modell[Owens 05] . . . . . . . . . . . . . . 40 4.2 Architektur der GeForce 6 [Kilgariff 05] . . . . . . . . . . . . . . . . . 41 4.3 Ablauf bei Berechnungen auf der GPU . . . . . . . . . . . . . . . . . 42 4.4 Zugriffsarten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.5 Multiple Render Targets . . . . . . . . . . . . . . . . . . . . . . . . . 46 118 Abbildungsverzeichnis 5.1 Rechner des OpenVIDIA-Projekts . . . . . . . . . . . . . . . . . . . . 50 5.2 Keypoints im OpenVIDIA-Projekt . . . . . . . . . . . . . . . . . . . 51 5.3 Ablauf bei Heymanns SIFT [Heymann 05a] . . . . . . . . . . . . . . . 52 5.4 Gaussfaltung auf gepackten Bildern [Heymann 05b] . . . . . . . . . . 52 5.5 Keypointsuche auf CPU und GPU [Heymann 05b] . . . . . . . . . . . 54 5.6 Deskriptorenberechnung [Heymann 05b] . . . . . . . . . . . . . . . . 55 5.7 Entpacken der Gradiententexturen [Heymann 05a] . . . . . . . . . . . 56 5.8 Berechnung der Deskriptoren [Heymann 05b] . . . . . . . . . . . . . . 56 5.9 Datenfluss in Sudipta Sinhas GPU-SIFT Implementierung [Sinha 06a] 57 6.1 Schritte des GPUSIFT auf der CPU . . . . . . . . . . . . . . . . . . . 60 6.2 Ablauf auf der GPU . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 6.3 Packen der Sektoren [Woolley 05] . . . . . . . . . . . . . . . . . . . . 64 6.4 Overhead beim Packen der Sektoren . . . . . . . . . . . . . . . . . . . 65 6.5 Lokales Packen [Woolley 05] . . . . . . . . . . . . . . . . . . . . . . . 65 6.6 Berechnung der direkten Nachbarn eines Punktes . . . . . . . . . . . 66 6.7 Berechnung der Nachbarschaft eines Punktes . . . . . . . . . . . . . . 67 6.8 Gaussmaske für Maskengröße 7 . . . . . . . . . . . . . . . . . . . . . 69 6.9 Horizontale Gaussglättung für R und B . . . . . . . . . . . . . . . . . 70 6.10 Horizontale Gaussglättung für G und A . . . . . . . . . . . . . . . . . 70 6.11 Differenz gepackter Bilder . . . . . . . . . . . . . . . . . . . . . . . . 71 6.12 Bereich in dem nach Keypoints gesucht wird . . . . . . . . . . . . . . 72 6.13 Früher Grenzwerttest . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 6.14 Test auf Pixel-Maximum . . . . . . . . . . . . . . . . . . . . . . . . . 74 6.15 Zweiter Grenzwerttest . . . . . . . . . . . . . . . . . . . . . . . . . . 74 6.16 Test auf lokales Maximum . . . . . . . . . . . . . . . . . . . . . . . . 75 6.17 Gradientenberechnung . . . . . . . . . . . . . . . . . . . . . . . . . . 77 6.18 Erster und zweiter Kanal einer Gradiententextur . . . . . . . . . . . . 78 6.19 Die Rückgabe der 32 Bins . . . . . . . . . . . . . . . . . . . . . . . . 79 6.20 Ausgabetexturen für Orientierungshistogramme . . . . . . . . . . . . 80 6.21 Ausgabetextur für Deskriptorhistogramme . . . . . . . . . . . . . . . 82 6.22 Die an das Cg-Programm übergebenen Parameter . . . . . . . . . . . 85 6.23 Verkleinern der Textur . . . . . . . . . . . . . . . . . . . . . . . . . . 86 7.1 Rechenungenauigkeiten bei der Orientierungsberechnung . . . . . . . 89 7.2 Gematchte Keypoints bei einer Bilddrehung von 180 Grad . . . . . . 90 Abbildungsverzeichnis 119 7.3 Größenänderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 7.4 Vergleich der Laufzeiten verschiedener SIFT-Implementierungen . . . 93 7.5 Laufzeitanteil einzelner Komponenten auf der ersten Oktave . . . . . 95 7.6 Laufzeitanteil einzelner Komponenten auf allen Oktaven . . . . . . . 97 A.1 Die GeForce 7600 GT 1 . . . . . . . . . . . . . . . . . . . . . . . . . . 101 120 Abbildungsverzeichnis Literaturverzeichnis [Bay 06] Herbert Bay, Tinne Tuytelaars, Luc Van Gool. SURF: Speeded Up Robust Features. In Proceedings of the ninth European Conference on Computer Vision, 2006. [Chati 06] Harding Djakou Chati. Implementation of an Embedded Hardware/Software Feature Detector-System on FPGA. Diplomarbeit, Technische Universität Kaiserslautern, 2006. [Fernando 03] Randima Fernando, Kilgard Merk J. The Cg Tutorial. NVIDIA, 2003. [Fung 05a] James Fung. Computer Vision on the GPU. In Matt Pharr (Hrsg.), GPU Gems 2, Kap. 40, S. 649–665. Addison Wesley, March 2005. [Fung 05b] James Fung, Steve Mann, Chris Aimone. OpenVIDIA: Parallel GPU Computer Vision. In Proceedings of the ACM Multimedia 2005, S. 849–852, Singapore, November 2005. [Grabner 06] Michael Grabner, Helmut Grabner, Horst Bischof. Fast Approximated SIFT. In Proc. ACCV 2006, Hyderabad, India, S. 918–927. Springer, January 2006. [Harris 05] Mark Harris. Mapping Computational Concepts to CPUs. In Matt Pharr (Hrsg.), GPU Gems 2, Kap. 31, S. 493–508. Addison Wesley, March 2005. [Heymann 05a] Sebastian Heymann. Implementierung und Evaluierung von Video Feature Tracking auf moderner Grafikhardware. Diplomarbeit, Bauhaus Universität Weimar, August 2005. [Heymann 05b] Sebastian Heymann. Präsentation: Implementierung und Evaluierung von Video Feature Tracking auf moderner Grafikhardware, August 2005. [Horn 05] Daniel Horn. Stream Reduction Operations for GPGPU Applications. In Matt Pharr (Hrsg.), GPU Gems 2, Kap. 36, S. 573–589. Addison Wesley, March 2005. [Ke 04] Y. Ke, R. Sukthankar. PCA-SIFT: A more distinctive representation for local image descriptors. In Proc. of the IEEE Conf. on Computer Vision and Pattern Recognition (CVPR), 2004. 122 Literaturverzeichnis [Kilgariff 05] Emmett Kilgariff, Randima Fernando. The GeForce 6 Series GPU Architecture. In Matt Pharr (Hrsg.), GPU Gems 2, Kap. 30, S. 471–491. Addison Wesley, March 2005. [Ledwich 06] Luke Ledwich, Stefan Williams. Reduced SIFT Features For Image Retrieval and Indoor Localisation, 2006. [Lepetit 04] Vincent Lepetit, Julien Pilet, Pascal Fua. Point Matching as a Classification Problem for Fast and Robust Object Pose Estimation. In Proceedings of the 2004 IEEE Computer Society Conference on Computer Vision and Pattern Recognition CVPR 2004, S. 244–250, June-July 2004. [Lowe 04] D. Lowe. Distinctive Image Features from Scale Invariant Keypoints. International Journal of Computer Vision, 60(2):91–110, 2004. [Lowe 99] D. Lowe. Object recognition from local scale-invariant features. In International Conference on Computer Vision, S. 1150–1157, September 1999. [Mikolajczyk 05] Krystian Mikolajczyk, Cordelia Schmid. A performance evaluation of local descriptors. IEEE Transactions on Pattern Analysis & Machine Intelligence, 27(10):1615–1630, 2005. [Owens 05] John Owens. Streaming Architectures and Technology Trends. In Matt Pharr (Hrsg.), GPU Gems 2, Kap. 29, S. 457–470. Addison Wesley, March 2005. [Se 04] Stephen Se, Ho-Kong Ng, Piotr Jasiobedzki, Tai-Jing Moyung. Vision based Modeling and Localization for Planetary Exploration Rovers. In Proceedings of International Astronautical Congress, Vancouver, Canada, 2004. [Sinha 06a] Sudipta N. Sinha, Jan-Michael Frahm, Marc Polleefeys, Yakup Genc. GPU-based Video Feature Tracking and Matching. Technischer Bericht, Department of Computer Sience, University of North Carolina at Chapel Hill, May 2006. [Sinha 06b] Sudipta N. Sinha, Jan-Michael Frahm, Marc Polleefeys, Yakup Genc. GPU-based Video Feature Tracking and Matching. EDGE06, 2006. [Upright 06] Cameron Upright. Realtime SIFT Implementation on Graphics Hardware, 2006. [Woolley 05] Cliff Woolley. GPU Program Optimization. In Matt Pharr (Hrsg.), GPU Gems 2, Kap. 35, S. 557–571. Addison Wesley, March 2005. [Zimmermann 06] Fabian Zimmermann. Erstellen eines Frameworks zur Detektion von InterestPoints, Projektarbeit, Mai 2006.