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.