Image Processing on GPU

Transcription

Image Processing on GPU
STUDENTEN
Clemens Blumer
Dominik Oriet
BETREUER
Marcus Hudritsch
AUFTRAGGEBER
Prof. Dr. Thomas Vetter
Departement Informatik
Universität Basel
Experte
Pascal Paysan
Diplomarbeit DA_11_0506
Basel, Januar 2006
Diplomarbeit Image Processing on GPU
Vorwort
Im Rahmen unserer Diplomarbeit beschäftigten wir uns während 10 Wochen intensiv mit der GPUProgrammierung. Dabei durchlebten wir verschiedenste Höhen und Tiefen. Erfreulicherweise
überwiegten rückblickend gesehen die Höhen klar.
Ein grosser Dank geht an unseren Betreuer Marcus Hudrtisch, der uns viele Freiheiten liess, aber uns
trotzdem dabei half, das Ziel der Arbeit nicht zu vergessen. Auch versuchte er uns jeweils in die
richtige Bahn zu lenken und schenkte unserer Diplomarbeit grosses Interesse und viel Zeit, obwohl er
noch viele andere Mitstudenten betreuen musste.
Auch unserem Auftraggeber, Thomas Vetter und seiner Gruppe sind wir dankbar. Sie ermöglichten
uns mit Ihrem Auftrag eine interessante Diplomarbeit und boten uns nebst einer guten
Arbeitsumgebung und technischer Ausrüstung auch stets fachliche Unterstützung.
Last but not least möchten wir auch all denjenigen Personen danken, die uns in sonst irgendeiner Form
unterstützten und hier nicht aufgeführt sind.
Basel, Januar 2006
Seite 2/42
Diplomarbeit Image Processing on GPU
Abstract
Wir wollen aufzeigen, dass es grundsätzlich möglich ist, Bildverarbeitung auf der GPU zu betreiben.
Die Stärken und Schwächen dieser Methodik machen wir ersichtlich, indem wir einen aufwändigen
Algorithmus auf der Grafikhardware implementieren. Dieser Algorithmus, der den Optischen Fluss
zweier Bilder berechnen kann, wird an der Uni Basel für gewisse Projekte gebraucht und wird durch
unsere Implementierung drastisch beschleunigt.
Damit unser erworbenes Wissen nicht verloren geht, beschreiben wir detailliert, wie auf der
Grafikkarte Bildverarbeitung betrieben werden kann. Ausserdem zeigen wir alle herausgefundenen
Tricks auf, um Fragmentprogramme möglichst optimal zu schreiben.
Basel, Januar 2006
Seite 3/42
Diplomarbeit Image Processing on GPU
Inhaltsverzeichnis
Inhaltsverzeichnis.................................................................................................................................... 4
1 Ausgangslage und Ziele .................................................................................................................. 5
2 Konzept ........................................................................................................................................... 7
3 Bildverarbeitung: Optischer Fluss................................................................................................... 8
3.1
Optischer Fluss und Umkehrung............................................................................................. 8
3.2
Warp ........................................................................................................................................ 9
3.3
Gradienten, Eigenwerte und Eigenvektoren.......................................................................... 10
3.4
Farbbilder .............................................................................................................................. 13
4 Grundlagen (GP)GPU-Programmierung ....................................................................................... 14
4.1
Programmfluss mit der CPU ................................................................................................. 14
4.1.1
Laden der Textur ........................................................................................................... 14
4.1.2
Fragment Programm laden ............................................................................................ 15
4.1.3
Setzen des Viewports und der orthogonalen Projektion................................................ 15
4.1.4
Übergeben der Uniform Variablen................................................................................ 16
4.1.5
Quad zeichnen ............................................................................................................... 16
4.1.6
Abspeichern der Textur ................................................................................................. 16
4.2
Fragmentprogramm ............................................................................................................... 17
4.2.1
Simpler Vergleich von GPU und CPU Programmen .................................................... 17
4.2.2
Binomial-Filter Beispiel ................................................................................................ 17
4.2.2.1 Binomialfilter auf der CPU ....................................................................................... 18
4.2.2.2 Binomialfilter auf der GPU ....................................................................................... 18
4.2.2.3 Die Unterschiede ....................................................................................................... 18
4.2.3
Tipps & Tricks............................................................................................................... 19
4.2.3.1 Möglichst einfacher Programmfluss.......................................................................... 19
4.2.3.2 Möglichst viel vorberechnen ..................................................................................... 19
4.2.3.3 Framebuffer Objekte ................................................................................................. 19
4.2.3.4 Textur-Zugriffe.......................................................................................................... 19
4.2.4
Readback von der Textur oder des Framebuffers.......................................................... 20
4.2.4.1 Zeitmessungen am CannyEdge ................................................................................. 20
4.2.5
Render-to-Texture ......................................................................................................... 21
4.2.5.1 Beispiel mit zwei Texturen und einem Framebuffer Object ..................................... 22
5 Canny Edge auf der GPU .............................................................................................................. 24
6 Optischer Fluss auf der GPU......................................................................................................... 27
6.1
Ablauf des Optischen Flusses auf der GPU .......................................................................... 27
6.1.1
Die Klasse OpticalFlowGPU......................................................................................... 27
6.2
Benchmark ............................................................................................................................ 29
6.2.1
Flussfeld ........................................................................................................................ 29
6.2.2
Bildinitialisierung.......................................................................................................... 29
6.2.3
Morphing....................................................................................................................... 29
7 Demo-Applikation......................................................................................................................... 30
7.1
Technischer Aufbau .............................................................................................................. 31
7.1.1
Kurze Beschreibung des Aufbaus ................................................................................. 31
7.2
Funktionsumfang................................................................................................................... 32
8 Ausblick ........................................................................................................................................ 35
8.1
Einsatz bei Projekten von Thomas Vetter ............................................................................. 35
8.2
Image Processing on GPU..................................................................................................... 35
9 Fazit............................................................................................................................................... 37
A Screenshots.................................................................................................................................... 38
Quellenverzeichnis ................................................................................................................................ 41
Anhang: ................................................................................................................................................. 42
Basel, Januar 2006
Seite 4/42
Diplomarbeit Image Processing on GPU
1
Ausgangslage und Ziele
Thomas Vetter hat einige Projekte am laufen, welche uns als Ausgangslage dienen. Es soll nicht etwas
Neues für diese entwickelt werden. Vielmehr wollen wir herausfinden, ob es Sinn macht, für einzelne
Teilbereiche (Bildverarbeitung) die GPU als Recheneinheit zu verwenden.
Ein spezieller Aspekt unserer Arbeit gilt dabei dem Optischen Fluss. Dieser ist relativ interessant für
Optimierungen, da er ziemlich rechenintensiv ist. Grundsätzlich ist es aber von Interesse zu wissen,
wie die aktuelle Situation bezüglich der Bildverarbeitung auf der GPU aussieht.
Das Ziel der Arbeit ist es, die Möglichkeiten der Bildverarbeitung auf der GPU darzulegen und im
Speziellen eine Implementierung des Optischen Flusses auf der GPU umzusetzen. Von zentralem
Interesse ist dabei jeweils der Performance-Gewinn.
Zum besseren Verständnis dient ein kleiner Überblick, der aufzeigt, in welchem Bereich der Optische
Fluss angewendet werden soll:
Anhand einer Fotoaufnahme eines Gesichtes soll ein 3D-Modell des Kopfes entstehen. Dazu wird ein
iterativer Prozess durchlaufen, der sich wiederholt.
1. Anhand von vorhandenen Parametern wird ein 3D-Modell des Kopfes erstellt.
2. Danach soll der Unterschied zwischen dem generierten und dem realen, fotografierten Gesicht
durch den Optischen Fluss dargestellt werden.
3. Anhand des Flussfeldes kann eine Verbesserung der angewendeten Parameter vorgenommen
werden.
4. Danach wird wiederum ein 3D-Modell des Kopfes mit den neuen Parametern erstellt und der
Prozess wiederholt sich von vorne.
Start
Parameter-Set
Start
3D-Modell rendern
Optischer Fluss zw.
Foto und 3D-Modell
Ende
Foto und Modell
„identisch“?
Parameter anhand
vom
Flussfeld
optimieren
Bild 1: Skizze Prozessablauf
Basel, Januar 2006
Seite 5/42
Diplomarbeit Image Processing on GPU
Nebst dem Verständnis der Bildverarbeitung auf der GPU ist auch das detaillierte Verständnis über
den Optischen Fluss ein Ziel der Arbeit.
Als letztes sollen dann die auf der GPU geschriebenen Algorithmen mit den Implementierungen auf
der CPU verglichen werden.
Basel, Januar 2006
Seite 6/42
Diplomarbeit Image Processing on GPU
2
Konzept
Da der Ausgang der Arbeit offen und unsicher war, wurde das Konzept von Beginn an relativ flexibel
gestaltet.
Über das Thema Bildverarbeitung auf der GPU waren anfangs gar keine Kenntnisse vorhanden. Des
Weiteren hatten wir noch sehr wenig Erfahrung in der GPU-Programmierung, weshalb wir uns zuerst
darin einarbeiten wollten.
Trotz des offenen Ausgangs und den wenigen Vorkenntnissen wurde ein Konzept mit einigen
wenigen, aber wichtigen Eckpunkten erstellt.
a)
b)
c)
d)
e)
f)
g)
Einarbeitung in GPU-Programmierung und Tutorials
Aktuellen Stand der Entwicklung anhand von bestehenden OpenSource-Projekten abschätzen
Knowhow vertiefen anhand von kleinen Beispielen
Optischen Fluss verstehen
Optischen Fluss auf GPU implementieren
Demo-Applikation mit GPU- und CPU-Version
Dokumentation
Tätigkeit/
Woche
1
2
3
4
5
6
7
8
9
10
a)
b)
c)
d)
e)
f)
g)
Bild 2: Konzept Diplomarbeit
Basel, Januar 2006
Seite 7/42
Diplomarbeit Image Processing on GPU
3
Bildverarbeitung: Optischer Fluss
Laut Definition ist der Optische Fluss ein Vektorfeld zur Bewegungsangabe. Etwas detaillierter
gemäss Quelle[13]:
Mit dem Optischen Fluss wird ein Vektorfeld bezeichnet, das die 2D-Bewegungsrichtung und Geschwindigkeit für jeden Bildpunkt bzw. jedes Pixel einer Bildsequenz angibt. Das Konzept des
Optischen Flusses wird zur Bewegungskontrolle in der visuellen Repräsentation verwendet und
erfährt durch den automatisierten Datenfluss von digitalen Bildern einen starken Entwicklungsschub.
Unbewusst setzen auch Menschen und Tiere dieses Prinzip ein.
Quelle[13] Abschnitt „Anwendung bei der Biene und beim Menschen“:
Bienen und andere Tierarten nutzen den Optischen Fluss, um Hindernissen auszuweichen und
Abstände einfach schätzen zu können. Sie werten dazu offenbar die Bilder beider Facettenaugen aus
und fliegen dann in die Richtung des Auges mit geringerem Optischen Fluss. Das führt auf einfachste
Weise zu einer Flugbahn, die den größten Freiraum und die wenigsten Hindernisse vor sich hat.
Ein ähnlicher Vorgang ist die Basis unserer alltäglichen Erfahrung im Fussgänger- und im
Strassenverkehr: wir nehmen die Bewegung der anderen Verkehrsteilnehmer aus den Augenwinkeln
wahr und berücksichtigen sie "unbewusst" bei der eigenen Fortbewegung. Schert hingegen ein
Verkehrsteilnehmer aus diesem Fluss aus, ist unser Gesichtssinn sofort alarmiert und es wird teilweise
sogar ein schützender Reflex ausgelöst, wie ein Sprung oder Kopf einziehen.
Der Optische Fluss kommt somit ursprünglich aus der Natur und das Prinzip wurde für die
Bildverarbeitung übernommen.
Grundsätzlich wurde zum Verständnis des Optischen Flusses die Quelle [2] verwendet.
In diesem Kapitel soll der Optische Fluss aber nicht in allen Details erklärt werden. Es werden nur die
Punkte angesprochen, welche für das Verständnis wichtig und unserer Meinung nach oft schlecht
erklärt sind. Für das allgemeine Verständnis wird jedoch die Quelle als bekannt vorausgesetzt.
3.1 Optischer Fluss und Umkehrung
Das Flussfeld stellt für jeden Pixel in Bild 1 dar, wo sich dessen passender Pixel im Bild 2 befindet.
Man erhält also für jeden Pixel in Bild 1 ein Vektor.
Bild 3: Umkehrung Flussfeld
Basel, Januar 2006
Seite 8/42
Diplomarbeit Image Processing on GPU
Es wird angenommen, dass alle Pixel weiss sind, bis auf einer, welcher Rot ist. Dieser wird nun
verschoben. Der Verschiebungsvektor für den roten Pixel ist der blaue Pfeil. Dieser zeigt auf die
Position, wo im Bild 2 der rote Pixel neu zu finden ist. Der Vektor ist also korrekt.
Geht man nun aber hin und kehrt diesen Vektor bzw. das Flussfeld, erhält man den grünen Pfeil. Nun
wird ja oft angenommen, dass das umgekehrte Flussfeld die Veränderung von Bild 2 zu Bild 1
darstellt. Dies ist aber falsch. Es ist klar erkennbar, dass man nun an der Stelle des roten Pixels in Bild
2 nicht den grünen Pfeil hat. Dieser befindet sich an einem völlig falschen Ort. Für kleine
Bewegungen mag dieser Fehler nicht bemerkt werden, jedoch darf das Flussfeld nicht einfach
invertiert werden, um korrekte Ergebnisse zu erhalten. Will man den Fluss von Bild 2 zu Bild 1, muss
eine neue Berechnung getätigt werden.
3.2
Warp
Hier behandeln wir nur den Backward-Warp. Dieser ist deutlich einfacher und wird am häufigsten
eingesetzt. Für den Forward-Warp müsste man auch wissen, wohin jeder Pixel wandert und nicht wo
dessen passendes Gegenstück im 2. Bild ist.
Beim Warp wird ein Bild durch ein Verschiebungsvektorfeld in ein anderes übergeführt. Es sei ein
Verschiebungsvektorfeld (Dx,Dy) gegeben.
Für jeden Pixel wird folgender neue Wert O(x,y) aus dem alten Wert I(x,y) berechnet:
x' = x + Dx( x, y )
y ' = y + Dy ( x, y )
O( x, y ) = bilinear ( I ( x, y ), x' , y ' )
Dabei muss, da für x’ und y’ kaum Integer-Werte herauskommen, bilinear interpoliert werden:
δx = Dx − floor ( Dx)
δy = DY − floor ( Dy )
O ( x, y ) =
(1 − δx)(1 − δy ) * I ( floor ( x' ), floor ( y ' )) +
δx(1 − δy ) * I ( floor ( x' ) + 1, floor ( y ' )) +
(1 − δx)δy * I ( floor ( x' ), floor ( y ' ) + 1) +
δx + δy * I ( floor ( x' ) + 1, floor ( y ' ) + 1)
Bsp.:
(x,y) sei (0,0)
Dx = 1.3
Dy = 1.3
δ
x’ = 1
y’ = 1
δ
x = 0.3
y = 0.3
Bild 4: Bilineare Interpolation
Basel, Januar 2006
Seite 9/42
Diplomarbeit Image Processing on GPU
An der Position (0,0) findet eine bilineare Interpolation aus den markierten Bereichen statt, wobei
folgende Gewichtung gilt:
(1,1): 0.49
(2,1): 0.21
(2,1): 0.21
(2,2): 0.09
1.00
Hinweis:
Ein Warp darf nicht mit Morphing verwechselt werden. Beim Warpen wird nur die Geometrie
verändert. Beim Morphen hingegen verändert sich sowohl die Geometrie als auch die Textur, wobei
die Warp Funktion verwendet wird.
3.3
Gradienten, Eigenwerte und Eigenvektoren
Zur Bestimmung des Flusses werden die Gradienten gebildet. Dies geschieht bezüglich x- und yRichtung und dem zeitlichen Gradienten. Die Gradienten entsprechen den Ableitungen:
F2 ( x + 1, y ) − F2 ( x − 1, y )
2 .0
F ( x, y + 1) − F2 ( x, y − 1)
F y ( x, y ) = 2
2 .0
Ft ( x, y ) = F2 ( x, y ) − F1 ( x, y )
Fx ( x, y ) =
Die so genannte Optical Flow Constraint wird durch folgende Gleichung dargestellt und stellt die
Bedingungen für den Fluss dar:
v x Fx + v y Fy − Ft = 0
Um die Bewegung v = (vx, vy)T am Punkt (x,y) zu erhalten wird der Fehler minimiert:
E = ∑ (v x Fx + v y Fy − Ft ) 2
(Die Summe wird über ein Fenster R=wh gebildet (typ. 5x5).)
Dies geschieht durch Ableitung nach vx bzw. vy, welche Null sein sollen:
E
= ∑ (2 * (v x Fx + v y Fy − Ft ) * Fx ) = 0
δv x
→
v x ∑ ( Fx Fx ) + v y ∑ ( Fx Fy ) = ∑ ( Ft Fx )
E
= ∑ (2 * (v x Fx + v y Fy − Ft ) * Fy ) = 0
δv y
→
v x ∑ ( Fx Fy ) + v y ∑ ( Fy Fy ) = ∑ ( Ft Fx )
Oder in Matrix Schreibweise:
Basel, Januar 2006
Seite 10/42
Diplomarbeit Image Processing on GPU
∑ ( Fx Fx )

∑ ( Fx Fy )
∑ ( F F )
∑ ( F F )
x
y
y
y
∑ ( Fy Ft )
v x 
*   = 

∑ ( Fy Ft )
v y 
W *v = γ
→
Der Fluss kann durch Errechnen der Eigenwerte und Eigenvektoren ermittelt werden.
Gesucht sei der Eigenwert von:
a b 
A=

b c 
a − λ
A − λE = 
 b
b 
c − λ 
Eigenwert: det( A − λE ) = 0
det( A − λE ) = (a − λ )(c − λ ) − b 2
= λ2 − λ (a + c) + ac − b 2
= 0
2
Quad. Gleichung: x + px + q = 0
2
λ1, 2
=
→
a+c
a 2 + 2ac + c 2 − 4ac + 4b 2
±
2
4
x1, 2
p
 p
= ±   −q
2
2
=
(a − c) 2 + 4b 2
a+c
±
2
4
Da die Matrix symmetrisch ist, reduziert sich das Gleichungssystem für die Eigenvektoren auf eine
Gleichung. Eine Unbekannte kann frei gewählt werden (z.B. x2=1)
( A − λE ) x = 0
b   x1 
a − λ
=0
 b
c − λ   x 22 

Daraus folgen die 2 Gleichungen:
(a − λ ) x1 + bx 2 = 0
(1)
bx1 + (c − λ ) x 2 = 0
(2)
Behauptung: Gleichung (1) sei ein Vielfaches von Gleichung (2) und deshalb reduziert sich das
Gleichungssystem auf eine Gleichung mit 2 Unbekannten, wobei eine davon frei gewählt werden
kann:
Damit (1) ein Vielfaches von (2) ist, gilt:
a−λ
b
=
b
c−λ
(a − λ )(c − λ ) = b 2
→
ac − λ (a + c) + λ = b
2
2
→
λ − λ (a + c) + ac − b = 0
2
Basel, Januar 2006
2
Seite 11/42
Diplomarbeit Image Processing on GPU
Aufgelöst folgt für :
λ
λ1, 2
a+c
a 2 + 2ac + c 2 − 4ac + 4b 2
±
2
4
=
=
a+c
(a − c) 2 + 4b 2
±
2
4
q.e.d .
Dies entspricht den Eigenwerten. Somit ist die Bedingung erfüllt. Eine Gleichung kann also fallen
gelassen werden:
(a − λ ) x1 + bx 2 = 0
x1 = −
wobei gilt (frei gewählt): x2 = 1
b
b
=
a−λ λ −a
 b 
Φ 1 =  λ − a 


 1 
normieren!!
Des Weiteren gilt, dass die Eigenvektoren einer symmetrischen Matrix orthogonal aufeinander stehen.
Deshalb reicht die Berechnung eines Vektors und für den zweiten folgt:
1


b




Φ2 = −
 λ − a 


normieren!!
Die Berechnung der Eigenwerte erfolgt mit der Matrix W.
Des Weiteren gilt:
v = α max Φ max + α min Φ min
wobei:
α max =
α min =
Φ Tmax γ
λmax
Φ Tmin γ
λmin
Für v gilt:
v=0
wenn λmax < 1
v = α max Φ max
wenn λmax >> λmin
v = α max Φ max + α min Φ min
sonst
Für das grundsätzliche Verständnis über Eigenvektoren und Eigenwerte sei auf die allgemein
bekannten Mathematik Bücher hingewiesen.
Basel, Januar 2006
Seite 12/42
Diplomarbeit Image Processing on GPU
3.4
Farbbilder
Die Optical Flow Constraint (siehe oben) kann auch aufgelöst und die Summen über Kanäle und
Regionen gebildet werden:
∑ ( Fx Fx )

∑ ( Fx Fy )
∑ ( F F )
∑ ( F F )
x
y
y
y
∑ ( Fy Ft )
v x 
*   = 

∑ ( Fy Ft )
v y 
→
W *v = γ
Daraus folgt:
v = W −1 * γ
a b
A=

b c 
1  d − b
ad − bc − c a 
A −1 =
→
Woraus folgt:
det(W ) = ∑ Fx Fx * ∑ Fy Fy − ∑ Fx Fy * ∑ Fx Fy
v=
 ∑ Fy Fy
1
*
det( w) − ∑ Fx Fy
− ∑ Fx Fy   ∑ Fx Ft 
*

∑ Fx Fx  ∑ Fy Ft 
Wobei nun auch noch die Kanäle aufsummiert werden.
entspricht
Kanäle
Nachbarn
∑
∑
∑
Summen über Nachbarschaft (z.B. 5x5) und Kanäle (rgb).
K = Kanäle; N = Nachbarn
vx =
vy =
∑ ∑ (F F )* ∑ ∑ (F F ) − ∑ ∑ (F F
∑ ∑ (F F ) * ∑ ∑ (F F ) − ∑ ∑ (F F
K
N
y
y
K
N
x
t
K
N
x
y
K
N
x
x
K
N
y
y
K
N
x
y
)* ∑ ∑ (F F )
)* ∑ ∑ (F F )
K
N
y
t
K
N
x
y
∑ ∑ (F F ) * ∑ ∑ (F F ) − ∑ ∑ (F F )* ∑ ∑ (F F )
∑ ∑ (F F ) * ∑ ∑ (F F ) − ∑ ∑ (F F )* ∑ ∑ (F F )
K
N
x
x
K
N
y
t
K
N
x
y
K
N
x
t
K
N
x
x
K
N
y
y
K
N
x
y
K
N
x
y
Sollte die Determinante Null sein, sind vx und vy auch 0.
Der Ansatz, die Summen über die Kanäle zu bilden, kommt daher, dass jeder Kanal für sich eine
korrekte Repräsentation der Szene darstellt. Es gibt auch Versuche, bei denen anstelle von
Farbbildern, welche nur wenig mehr Information als Graustufen-Bilder bieten, andere
Aufnahmemethoden verwendet werden. Unter anderem wurde die Kombination der Aufnahme von
klassischen Graustufen-Bildern und Infrarot-Bildern erwähnt. Je mehr Kanäle bzw. Aufnahmemedien
vorhanden sind, umso grösser ist dadurch auch der Informationsgehalt.
Basel, Januar 2006
Seite 13/42
Diplomarbeit Image Processing on GPU
4
Grundlagen (GP)GPU-Programmierung
Normalerweise wird die GPU dazu gebraucht, 3D Spiele beschleunigen zu können. Somit wird die
CPU entlastet und kann sich auf andere Aufgaben konzentrieren.
In unserem Fall möchten wir die GPU dazu benützen, rechenintensive Bildverarbeitung auf einer
handelsüblichen Consumer-Grafikkarte betreiben zu können.
Für unsere Zwecke benützen wir lediglich Fragmentprogramme. Vertexprogramme werden in
unserem Fall nicht benötigt, da wir nur Bilddaten von Texturen manipulieren wollen.
Heutige Grafikprozessoren besitzen je nach Modell bis zu 24 Pixelpipelines (z.B. nVidia GeForce
7800 GTX). Dies bedeutet, dass ein Programm parallelisierbar sein muss, um die Leistung des
Prozessors vollständig ausnutzen zu können. Filteroperationen, wie sie bei der Bildverarbeitung
zahlreich vorkommen, sind dazu sehr häufig geeignet. Ein Beispiel des Canny Edge Filters wird im
zweiten Unterkapitel gezeigt.
Zu beachten ist, dass nicht alles von der GPU berechnet wird. Es werden lediglich die
rechenintensiven Filteroperationen darauf gerechnet. Die CPU dient dazu, den Programmfluss zu
steuern.
Wie kann man nun ein einfaches Bildverarbeitungsprogramm von der GPU berechnen lassen? Das
wollen wir hier in den nächsten beiden Unterkapiteln zeigen. Voraussetzung für das Verständnis ist
allerdings eine gewisse Vorkenntnis über die Grafikkartenprogrammierung. Es wird hier keine
Einführung in die Shaderprogrammierung gemacht, da dieses Thema in der Literatur[12] bestens
erklärt wird.
4.1
Programmfluss mit der CPU
Folgende Vorgehensweise wird empfohlen, um ein Bildverarbeitungsprogramm laufen zu lassen.
1.
2.
3.
4.
5.
6.
4.1.1
Laden der Textur
Fragment Programm laden
Setzen des Viewports und der orthogonalen Projektion
Übergeben der Uniform Variablen
Quad zeichnen
Abspeichern der Textur
Laden der Textur
Es gibt eine Bibliothek namens DevIL, mit welcher Bilder ziemlich einfach in OpenGL eingebunden
werden können. Dies kann mit folgenden Befehlen erreicht werden:
ilInit();
ilLoadImage("BildnameIN.bmp");
if (ilGetInteger(IL_IMAGE_FORMAT)!=IL_BGRA)
ilConvertImage(IL_BGRA, IL_UNSIGNED_BYTE);
Danach folgen die gewohnten OpenGL Befehle, um das Bild als Textur anzulegen:
glGenTextures(1, texName);
glBindTexture(GL_TEXTURE_2D, texName);
Basel, Januar 2006
Seite 14/42
Diplomarbeit Image Processing on GPU
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
width,
height,
0,
GL_BGRA,
GL_UNSIGNED_BYTE, ilGetData());
Wichtig in diesem Zusammenhang ist, dass beim Parameter GL_TEXTURE_WRAP_S/T der Wert
GL_CLAMP zugewiesen ist. Dies hat zur Folge, dass die Textur an den Kanten wiederholt wird.
Wenn zum Beispiel ausserhalb der Textur gelesen wird, bekommt man die entsprechende Farbe des
wiederholten Randes zurück. Das kann nützlich sein, wenn das Randproblem bei einer Filterung durch
Wiederholen des Randes gelöst werden soll.
Mit ilGetData() werden die Bilddaten des zuvor geladenen Bildes gelesen.
Die Wahl des internen Farbformates hat einen direkten Zusammenhang mit der Geschwindigkeit. Von
nVidia[8] wird empfohlen, das GL_BGRA Format zu verwenden, um eine möglichst hohe
Datentransferrate zu erreichen.
4.1.2
Fragment Programm laden
Mit den folgenden Codezeilen lässt sich ein Fragmentprogramm laden.
progObj = glCreateProgramObjectARB();
fragObj = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glShaderSourceARB(fragObj, 1, (const GLcharARB **)&fragSrc, NULL);
glCompileShaderARB(fragObj);
glAttachObjectARB(progObj, fragObj);
glLinkProgramARB(progObj);
In der Methode glShaderSourceARB enthält fragSrc die Daten der Fragment Datei. Um Fehler beim
Linken sichtbar zu machen, genügen folgende Zeilen.
int len = 0;
glGetObjectParameterivARB(progObj, GL_OBJECT_INFO_LOG_LENGTH_ARB, & len);
if (len >1) {
GLcharARB *log = new GLcharARB[len];
glGetInfoLogARB(progObj, len, 0, log);
cerr << progName << " could not be linked:\n" << log << endl;
exit(1);
}
4.1.3
Setzen des Viewports und der orthogonalen Projektion
Als erstes muss die Grösse des Bildschirmausschnitts festgelegt werden.
glViewport(0, 0, width, height);
Als nächstes muss die orthografische Projektion eingestellt werden. Diese bewirkt, dass alle Objekte
in der Szene durch parallele Strahlen auf der Projektionsfläche abgebildet werden.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-1, 1, -1, 1);
Basel, Januar 2006
Seite 15/42
Diplomarbeit Image Processing on GPU
4.1.4
Übergeben der Uniform Variablen
Dem Shaderprogramm können verschiedene Werte übergeben werden, so zum Beispiel neben Integers
oder Floats auch Texturen, Arrays und Vektoren.
glUseProgramObjectARB(progObj);
glUniform1iARB(glGetUniformLocationARB(progObj, "imageName"), 0);
glUniform1ivARB(glGetUniformLocationARB(progObj, "intArrName"), 5, x);
glUniform1fARB(glGetUniformLocationARB(progObj, "floatValName"), y);
4.1.5
Quad zeichnen
Bis hierhin sind fast alle notwendigen Vorbereitungen getroffen. Um das Fragmentprogramm zu
starten, muss ein Quad gezeichnet werden.
glBegin(GL_QUADS);
{
glTexCoord2f(0, 0);
glTexCoord2f(1, 0);
glTexCoord2f(1, 1);
glTexCoord2f(0, 1);
}
glEnd();
glVertex3f(-1, -1, -0.5f);
glVertex3f( 1, -1, -0.5f);
glVertex3f( 1, 1, -0.5f);
glVertex3f(-1, 1, -0.5f);
Mit diesem Vorgehen wird erreicht, dass die Textur in einem Bereich von 0 bis 1 angesprochen
werden kann. Der Punkt (0/0) ist dabei in der linken unteren Ecke.
4.1.6
Abspeichern der Textur
Das gerenderte Bild liegt nun im Framebuffer vor, sofern kein Framebuffer Objekt (FBO) verwendet
wurde (siehe Kapitel 4.2.4). Um die Textur aus dem Buffer zu lesen, genügen folgende Zeilen.
glBindTexture(GL_TEXTURE_2D, outTex);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);
Hiermit wird der Inhalt des Framebuffers in die Textur outTex geschrieben.
Um die Textur abzuspeichern, können die folgenden DevIL Funktionen verwendet werden.
ilEnable(IL_FILE_OVERWRITE);
ilSaveImage("BildnameOUT.bmp");
Hinweis: Das Rendern in den Framebuffer funktioniert zwar gut, bringt aber einige Nachteile mit sich.
Zum einen muss darauf geachtet werden, dass die Farbqualität der Bildschirmeinstellung auf 32bit
gesetzt ist. Zum anderen können keine negativen Farbwerte ausgegeben werden. Ausserdem beträgt
die maximale Grösse der Ausgabe soviel wie im System eingestellt ist. Und zu guter Letzt muss nach
jeder Berechnung der Framebuffer in eine Textur zurückkopiert werden.
Aus all diesen Gründen wird empfohlen, direkt in ein FBO zu rendern anstatt in den Framebuffer.
Basel, Januar 2006
Seite 16/42
Diplomarbeit Image Processing on GPU
4.2
Fragmentprogramm
Mit Fragmentprogrammen kann die eigentliche Bildverarbeitungslogik implementiert werden. Damit
diese effizient ausgeführt werden können, sind einige Dinge zu beachten, die nachfolgend aufgelistet
werden.
4.2.1
Simpler Vergleich von GPU und CPU Programmen
CPU
GPU
for(int y=0; y<maxY ; ++y){
for(int x=0 ; x<maxX ; ++x){
// Berechnung pro Pixel
}
}
/* Erhöhung von jedem Kanal um 30.
Angenommen, das Bild liegt wie in einem Array vor (rgb rgb rgb
rgb)
*/
Der Fragment-Shader beinhaltet bereits die 2 forSchleifen und macht diese dadurch überflüssig.
glFragColor=texture2D(texName,
gl_TexCoord[0].st)+30;
outImg[(x+y*maxX)*3]=inImg[(x+y*maxX)*3]+30;
outImg[(x+y*maxX)*3+1]
=inImg[(x+y*maxX)*3+1]+30;
outImg[(x+y*maxX)*3+2]
=inImg[(x+y*maxX)*3+2]+30;
// Zugriff auf Nachbarpixel (rgb-Kanäle)
// z.b. x-1, y+1
nachbar[r]=inImg[(x-1+(y+1)*maxX)*3];
nachbar[g]=inImg[(x-1+(y+1)*maxX)*3+1];
nachbar[b]=inImg[(x-1+(y+1)*maxX)*3+2];
vec2 v=vec2(-1,1); //Verschiebung
vec3
nachbar=
texture2D(texName,
gl_TexCoord[0].st+v).rgb;
Es zeigt sich, dass der Shader-Code oft deutlich kürzer ist. Auch das Denken fällt einfacher, da man
nicht auf jeden Kanal einzeln zugreifen muss, sondern wirklich das ganze Texel bzw. Pixel zur
Verfügung hat. Jedoch muss man, sollte man die Kanäle „missbrauchen“ und z.B. 4 s/w Bilder in eine
rgba-Textur füllen, berücksichtigen, dass man die richtigen Kanäle nimmt und kein Durcheinander
veranstaltet.
Es ist klar erkennbar, dass Pixeloperationen sehr einfach möglich sind. Die Schwäche kommt
allerdings bei Operationen, wie z.B. dem durchschnittlichen Helligkeitswert eines s/w Bildes, zum
Vorschein. Das ist normalerweise eine sehr einfache Funktion, welche alles aufsummiert und durch
die Anzahl Pixel dividiert. Jedoch werden genau solche Operationen auf der GPU sehr komplex und
dadurch auch langsam. Man arbeitet pro Pixel und hat keine Ausgabevariabel zu Verfügung. Die
einzige Ausgabe erfolgt über den Framebuffer bzw. Texturen.
Auch eine scheinbar einfache Aufgabe wie z.B. ein Bild auf der GPU horizontal zu spiegeln, ist ohne
weiteres machbar, die Performance dürfte aber durch die sehr weiten Texturzugriffe deutlich sinken.
4.2.2
Binomial-Filter Beispiel
Im Gegensatz zur klassischen Bildverarbeitung auf der CPU ist die Denkweise eines
Fragmentprogramms ganz anders. Dies soll hier anhand eines einfachen Beispiels gezeigt werden.
Basel, Januar 2006
Seite 17/42
Diplomarbeit Image Processing on GPU
Zu beachten ist, dass das Randproblem bei diesem Beispiel vernachlässigt wurde und es alles andere
als optimiert ist. Des Weiteren funktioniert das CPU Beispiel nur für Graustufenbilder, während
dessen das GPU Beispiel auch für Farbbilder funktioniert.
4.2.2.1
Binomialfilter auf der CPU
for (y=0; y<height; ++y) {
for (x=0; x<width; ++x) {
sum
sum
sum
sum
sum
sum
sum
sum
sum
=
+=
+=
+=
+=
+=
+=
+=
+=
inImg[POS(x-1,
inImg[POS(x ,
inImg[POS(x+1,
inImg[POS(x-1,
inImg[POS(x ,
inImg[POS(x+1,
inImg[POS(x-1,
inImg[POS(x ,
inImg[POS(x+1,
y-1)];
y-1)] *
y-1)];
y )] *
y )] *
y )] *
y+1)];
y+1)] *
y+1)];
2;
2;
4;
2;
2;
outImg[POS(x,y)] = sum/16;
}
}
Dieser Programmausschnitt sollte soweit selbsterklärend sein.
4.2.2.2
Binomialfilter auf der GPU
uniform sampler2D inImg;
void main(void) {
vec4 sum;
sum = texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
sum += texture2D(inImg,
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
gl_TexCoord[0].xy
+
+
+
+
+
+
+
+
+
vec2(-step,
vec2(0
,
vec2(step ,
vec2(-step,
vec2(0
,
vec2(step ,
vec2(-step,
vec2(0
,
vec2(step ,
-step));
-step)) *
-step));
0
)) *
0
)) *
0
)) *
step ));
step )) *
step ));
2.0;
2.0;
4.0;
2.0;
2.0;
gl_FragColor = sum/16.0;
}
Mit uniform sampler2D inImg wird das Bild in Form einer Textur dem Fragment Programm
übergeben. Mit texture2D wird auf die Textur zugegriffen. Gl_Coord[0].xy stellt die aktuelle Position
dar, in der sich das Programm im Moment befindet. step ist die Distanz von Pixel zu Nachbarpixel.
Gl_FragColor repräsentiert den Ausgabepixel.
4.2.2.3
Die Unterschiede
Das Auffälligste ist, dass im Shaderprogramm keine for-Anweisung zu finden ist. Der Grund dafür ist,
dass beim CPU-Programm Zeile für Zeile abgearbeitet wird. Beim GPU-Programm ist das anders.
Dort bestimmt die GPU, welcher Ausgabepixel gerade berechnet wird. Aus diesem Grund wird
Basel, Januar 2006
Seite 18/42
Diplomarbeit Image Processing on GPU
Gl_Coord[0].xy benötigt, damit das Fragmentprogramm weiss, an welcher Position es sich gerade
befindet.
Ein weiterer Unterschied ist der, dass das Shaderprogramm für jeden Pixelwert einen 4 dimensionalen
Vektor zur Verfügung hat, welcher die RGBA Werte enthält. Aus diesem Grund lassen sich Farbbilder
auf der GPU mit wesentlich weniger Aufwand berechnen als auf der CPU, da mit einer
Vektormultiplikation die 4 Werte auf einmal ausgerechnet werden.
4.2.3
Tipps & Tricks
Während unserer Diplomarbeit sind wir auf einige wichtige Optimierungsmöglichkeiten gestossen, die
wir hier auflisten möchten.
4.2.3.1
Möglichst einfacher Programmfluss
Shaderprogramme sollten möglichst ohne if, else Anweisungen auskommen. Da die Grafikkarte diese
Programme parallel abarbeiten lässt, könnten sie sich sonst gegenseitig ausbremsen.
4.2.3.2
Möglichst viel vorberechnen
Muss im Fragmentprogramm zum Beispiel der Wert width*height bekannt sein, könnte man diesen
Ausdruck so im Programm hinschreiben. Das bewirkt allerdings, dass diese Multiplikation für jeden
Pixel ausgeführt wird, da das Fragmentprogramm für jeden Bildpunkt aufgerufen wird. Bei einem
512x512 Pixel Bild wären das bereits 262’144 Multiplikationen!
Besser ist es, wenn dieser Wert einmal von der CPU berechnet und dann per Uniform Variable dem
Shaderprogramm übergeben wird.
4.2.3.3
Framebuffer Objekte
Normalerweise wird direkt in den Framebuffer gerendert. Dies bringt allerdings den Nachteil, dass
wenn das Ausgabebild für einen weiteren Schritt benötigt wird, dieses vom Framebuffer zurück in
eine Textur geschrieben werden muss.
Mit Framebuffer Objekten kann dieser Nachteil behoben werden. Detaillierte Informationen dazu
können dem Kapitel 4.2.5 entnommen werden.
4.2.3.4
Textur-Zugriffe
Texturzugriffe können die Performance von Fragmentshader-Programmen stark beeinflussen. Man
sollte möglichst wenig Texturzugriffe in einem Programm haben. Allfällige Offsets sollten wenn
möglich als Vektor oder Array dem Programm übergeben und nicht jeweils berechnet werden (siehe
4.2.3.2). Lässt man z.B. die Offsets für einen Filter in einer For-Schleife berechnen, wird die
Geschwindigkeit deutlich schlechter sein, als wenn man die Offsets als Uniform-Array bzw. Vektor
übergibt.
Basel, Januar 2006
Seite 19/42
Diplomarbeit Image Processing on GPU
4.2.4
Readback von der Textur oder des Framebuffers
Für den Bereich Bildverarbeitung ist oft ein Zurücklesen von Informationen bzw. des Bildes nötig, da
dieses meist nicht nur angezeigt, sondern auch abgespeichert oder auf der CPU weiter verarbeitet
werden soll.
Verschiedenste Möglichkeiten bieten sich dafür an.
Die einfachste ist, wenn sich das Endergebnis auf dem Framebuffer befindet. Ein einfaches Auslesen
des Framebuffers ist möglich, was auch relativ schnell ist. Jedoch muss dafür bei render-to-texture die
Ausgabe am Schluss auf den Framebuffer erfolgen. Ausserdem ist man in der Grösse eingeschränkt.
void glReadPixels(Glint x, Glint y, Glsizei width, GLsizei
format, Glenum type, GLvoid *pixels);
height, GLenum
Direktes Auslesen einer Textur. Dabei ist man deutlich langsamer, jedoch nicht mehr abhängig vom
Framebuffer und kann die vollen Texturgrössen lesen. Auch kann das via render-to-texture in die
Textur geschriebene Ergebnis direkt gelesen werden:
void glGetTexImage(Glenum target, Glint level, Glenum format, Glenum type,
GLvoid *pixels);
Auslesen des Framebuffer Object. Wird ein render-to-texture mit Framebuffer Object
durchgeführt, kann anstatt direkt die Textur auszulesen auch der Buffer ausgelesen werden.
Dafür muss der Buffer aktiviert werden und die richtige Textur zugeordnet sein. Danach
einfach den Buffer auslesen. Diese Methode erwies sich als die schnellste.
Bsp:
GLubyte * data = new GLubyte[_iWidth*_iHeight*4];
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
glReadPixels(0 ,0 , _iWidth, _iHeight, GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
Methoden:
void GenFramebuffersEXT(sizei n, uint *framebuffers);
void DeleteFramebuffersEXT(sizei n, const uint *framebuffers);
void glBindFramebufferEXT(int target, int framebuffer);
void glDrawBuffer(Glenum mode);
void glReadPixels(Glint x, GLint y, Glsizei width, Glsizei height, Glenum
format, Glenum type, GLvoid *pixels);
Referenz zu Framebuffer Object Extension, siehe [5].
4.2.4.1
Zeitmessungen am CannyEdge
Der mit FBO implementierte CannyEdge-Filter wurde gemessen.
Folgende Schritte wurden 100-mal durchlaufen:
- Die Daten des Inputbildes werden auf die Textur geschrieben
Basel, Januar 2006
Seite 20/42
Diplomarbeit Image Processing on GPU
-
Der CannyEdge-Filter läuft mit aktiviertem FBO darüber
Der Readback findet statt (2 Arten)
Anzahl
Wiederholungen
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
Readback-Art
Bildgrösse
Kein
Texture
FBO
openCV
Kein
Texture
FBO
openCV
Kein
Texture
FBO
openCV
Kein
Texture
FBO
openCV
512*512
512*512
512*512
512*512
1024*1024
1024*1024
1024*1024
1024*1024
2048*2048
2048*2048
2048*2048
2048*2048
4096*4096
4096*4096
4096*4096
4096*4096
Dauer Total Readback (sec)
(sec)
0.53
3.52
0.85
2.06
1.30
14.50
2.36
7.40
4.24
59.00
8.25
26.50
16.15
xxxx
32.35
96.8
Durchschn.
Readback
0.000
3.000
0.350
0.000
0.030
0.004
0.000
13.300
1.370
0.000
0.133
0.014
0.000
56.000
5.310
0.000
0.560
0.053
0.000
xxxx
21.700
0.000
xxxx
0.022
Durch einzelne Optimierungen wurden die Messwerte der GPU-Version noch deutlich
verbessert. Unter anderem wurde der Zugriff auf die Texturen verbessert. Siehe dazu auch
Kapitel 4.2.3.
4.2.5
Render-to-Texture
Render-to-Texture soll die Möglichkeit bringen, direkt in eine Textur zu rendern. Man erspart sich so
das Rendern in den Framebuffer, um die Daten von dort wieder auszulesen und diese dann für die
Weiterverarbeitung in eine Textur zu schreiben.
Für den Einsatz von Framebuffer-Objekten ist die Extension „EXT_framebuffer_object“ (String:
GL_EXT_framebuffer_object) nötig.
Basel, Januar 2006
Seite 21/42
Diplomarbeit Image Processing on GPU
Textur 1
Textur
Fragmentshader
Textur 1
Framebuffer
Object
Framebuffer
Input- und OutputTextur wählen
Textur rendern
Fragmentshader
Displayanzeige
Framebuffer
für
Ausgabe aktivieren
Bild 6: Rendern in Textur ohne FramebufferObjekt
Textur rendern
Display
Bild 5: Rendern in Textur mit Framebuffer-Objekt
4.2.5.1
Beispiel mit zwei Texturen und einem Framebuffer Object
Benötigte Attribute (Header-File):
GLuint fbo, color[2];
Anlegen des Framebuffer Object (Konstruktor):
glGenFramebuffersEXT(1, &fbo);
Basel, Januar 2006
Seite 22/42
Diplomarbeit Image Processing on GPU
Anlegen von zwei leeren Texturen (können auch mit Daten gefüllt werden):
glGenTextures(2, color);
glBindTexture(GL_TEXTURE_2D, color[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _iWidth, _iHeight, 0, GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, color[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _iWidth, _iHeight, 0, GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
Framebuffer Object aktivieren und Texturen an Color-Attachment anhängen, danach zurück zum
Framebuffer als Ausgabe wechseln:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_TEXTURE_2D, color[0], 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_TEXTURE_2D, color[1], 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
GL_COLOR_ATTACHMENT0_EXT,
GL_COLOR_ATTACHMENT1_EXT,
Render-to-Texture-Bereich:
Framebuffer Object aktivieren:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
Ziel wird Color-Attachment1 -> Ergebnis befindet sich in Textur color[1].
Quell-Textur ist color[0]:
glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
glBindTexture(GL_TEXTURE_2D, color[0]);
Zum Wechseln bzw. Ändern, einfach Argumente austauschen:
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
glBindTexture(GL_TEXTURE_2D, color[1]);
Ausgabe in Textur bzw. Framebuffer Object deaktivieren:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
Danach kann ganz normal wieder auf den Framebuffer gerendert werden. Anstelle einer festen Zahl
kann man auch zuerst den aktuellen Framebuffer auslesen und später wieder aktivieren. So wird im
Falle mehrerer Werte eine Verwechslung verhindert:
GLint _currentDrawBuf
glGetIntegerv(GL_DRAW_BUFFER, &_currentDrawbuf);
Anmerkungen:
Die benötigten Texturen, welche an das Framebuffer Object angehängt werden, müssen bezüglich
Format und Grösse identisch sein!!!
Vorteile:
Man wird unabhängig von der Display-Grösse und kann auch problemlos grössere Texturen rendern.
Zusätzlich entfällt das Kopieren vom Framebuffer in Texturen.
Basel, Januar 2006
Seite 23/42
Diplomarbeit Image Processing on GPU
5
Canny Edge auf der GPU
Anhand vom Beispiel des CannyEdge-Filters soll etwas auf die Implementierung von
Bildverarbeitung auf der GPU eingegangen werden. Es wird jedoch keine vollständige Implementation
dargelegt, sondern nur auf die wichtigsten Punkte hingewiesen.
Für den Einsatz der Bildverarbeitung wird der Ansatz genommen, dass Bilder durch 2D-Texturen
dargestellt werden. Filter werden entweder durch Matrizen oder durch kleine 2D-Texturen
repräsentiert, wobei Matrizen zu empfehlen sind. Das Ausgabe-Bild wird durch den Framebuffer oder
durch eine an ein Framebuffer Object angehängte Textur dargestellt.
Für jeden Pixel wird ein neuer Wert errechnet, in dem, wie auf der CPU, die gewünschten Pixel mit
dem Filter verrechnet werden. Auch hier können, wie auf der CPU, separierbare Filter realisiert
werden. Empfindlicher als auf die Anzahl Multiplikationen ist die GPU auf die Anzahl beanspruchter
Nachbarschaftstexel bzw. wie weit diese voneinander entfernt sind. Je grösser ein Filter ist, umso
langsamer wird die Berechnung. Dies nicht nur durch die erhöhte Anzahl Berechnungen, sondern
durch die Texturzugriffe. Grundsätzlich gilt es, die Anzahl der Texturzugriffe zu minimieren.
Für die Implementation des CannyEdge-Filters wird ein standardmässiges Gerüst für GPUProgramme, wie in Kapitel 4.1 erklärt wird, verwendet. Hier sollen nur die verwendeten
Fragmentprogramme dargestellt werden.
Gauss-Filterung in X-Richtung
uniform vec4 offset;
uniform sampler2D texUnit0;
// (1.0/_iWidth, 2.0/_iWidth, 0.0, 0.0)
// Inputbild
void main(void){
vec2 coord=gl_TexCoord[0].st;
// horizontale Filterung
vec4 oneL, twoL, oneR, twoR, current;
// Auslesen der Nachbarn und des aktuellen Pixels
oneL = texture2D(texUnit0, coord - offset.xz);
twoL = texture2D(texUnit0, coord - offset.yz);
oneR = texture2D(texUnit0, coord + offset.xz);
twoR = texture2D(texUnit0, coord + offset.yz);
current = texture2D(texUnit0, coord);
vec4 tmp = vec4(0.0625*twoL+0.25*oneL+0.375*current+0.25*oneR+0.0625*twoR);
gl_FragColor=tmp;
}
Gauss-Filterung in Y-Richtung
uniform vec4 offset;
uniform sampler2D texUnit0;
// (0.0, 1.0/_iHeight, 2.0/_iHeight, 0.0)
// Inputbild
void main(void){
vec2 coord = gl_TexCoord[0].st;
// vertikale Filterung
vec4 oneU, twoU, oneD, twoD, current;
// Auslesen der Nachbarn und des aktuellen Pixels
oneU = texture2D(texUnit0, coord + offset.xy);
twoU = texture2D(texUnit0, coord + offset.xz);
Basel, Januar 2006
Seite 24/42
Diplomarbeit Image Processing on GPU
oneD = texture2D(texUnit0, coord - offset.xy);
twoD = texture2D(texUnit0, coord - offset.xz);
current = texture2D(texUnit0, coord);
vec4 tmp = vec4(0.0625*twoU+0.25*twoU+0.375*current+0.25*oneD+0.0625*twoD);
gl_FragColor = tmp;
}
Gradienten
uniform sampler2D texGrad;
// Inputbild
uniform vec4 offset;
// (1.0/_iWidth, 1.0/_iHeight, 0.0, -1.0/_iHeight)
void main(void){
vec4 t,b,l,r,tl,tr,bl,br;
vec2 coord = gl_TexCoord[0].st;
// auslesen der 8 Nachbarn
t = texture2D(texGrad, coord + offset.zy);
b = texture2D(texGrad, coord - offset.zy);
l = texture2D(texGrad, coord - offset.xz);
r = texture2D(texGrad, coord + offset.xz);
tl = texture2D(texGrad, coord - offset.xw);
tr = texture2D(texGrad, coord + offset.xy);
bl = texture2D(texGrad, coord - offset.xy);
br = texture2D(texGrad, coord + offset.xw);
// berechnen von der Ableitung nach x bzw. y
vec3 gray = vec3(0.299, 0.587, 0.114);
float tmp_gx = dot(((tr+br+2.0*r)-(tl+bl+2.0*l)).xyz, gray);
float tmp_gy = dot(((tl+tr+2.0*t)-(bl+br+2.0*b)).xyz, gray);
// berechnen der Länge des Gradienten
float mag=sqrt(tmp_gx*tmp_gx+tmp_gy*tmp_gy);
// gx und gy Normieren und zwischen 0 und 1 bringen
tmp_gx = (((tmp_gx)/mag)+1.0)*0.5;
tmp_gy = (((tmp_gy)/mag)+1.0)*0.5;
gl_FragColor = vec4(tmp_gx,tmp_gy,mag, 0.0);
}
Non-Maximum-Suppression
uniform sampler2D texUnit0;
uniform float offsetX;
uniform float offsetY;
// Inputbild
void main(void){
vec2 coord = gl_TexCoord[0].st;
vec4 tmp = texture2D(texUnit0, coord);
vec4 n1, n2;
vec2 offset2;
// tmp.x und tmp.y sind die Gradienten gx, gy zw. -1 und 1,
// resized zu 0-1
offset2.x = offsetX*floor((2.0*tmp.x)-0.5);
offset2.y = offsetY*floor((2.0*tmp.y)-0.5);
// beide Nachbarn
n1 =texture2D(texUnit0, coord + offset2);
n2 = texture2D(texUnit0, coord - offset2);
// belasse es wenn Maximum, ansonsten schwarz
Basel, Januar 2006
Seite 25/42
Diplomarbeit Image Processing on GPU
If (tmp.z>=n1.z && tmp.z>=n2.z)
gl_FragColor=vec4(tmp.zzz, 1.0);
else
gl_FragColor=vec4(0.0);
}
Basel, Januar 2006
Seite 26/42
Diplomarbeit Image Processing on GPU
6
Optischer Fluss auf der GPU
6.1
Ablauf des Optischen Flusses auf der GPU
In Kapitel 3 wurde beschrieben, wie der Optische Fluss in der Theorie funktioniert. In diesem Kapitel
wird schematisch erklärt, wie die Implementation aussieht. Es wird nicht detailliert auf den Code
eingegangen, da dieser in der entsprechenden Dokumentation genauer beschrieben wird.
6.1.1
Die Klasse OpticalFlowGPU
Die wichtigste, öffentliche Methode ist displayMorphedImg(float dt, ...). Sie bewirkt, dass die vorgängig
gesetzten Bilder zusammengemorpht werden. Der Parameter dt, welcher zwischen 0 und 1 liegen
muss, bestimmt dabei das Mischverhältnis der beiden Bilder.
Nach dem Aufruf dieser Methode wird die ganze Rechenmaschinerie angeworfen:
Bild 7: Erstes Aktivitätsdiagramm
Bei diesem Diagram ist ersichtlich, dass das Flussfeld für beide Eingabebilder nur einmal berechnet
wird. Werden diese durch setImgs() ausgetauscht, wird die Variable flowComputed wieder auf false
gesetzt, damit das Flussfeld neu berechnet wird.
Da das Warpen der beiden Inputbilder jeweils rückwärts geschieht, müssen auch zwei Flussfelder
berechnet werden, denn das Flussfeld, welches aus dem Bild 1 zum Bild 2 berechnet wurde, ist nicht
gleich dem umgekehrten Flussfeld aus dem Bild 2 zum Bild 1!
Aus Zeit und Performancegründen haben wir auf einen „richtigen“ Vorwärtswarp verzichtet und
stattdessen zweimal den Rückwärtswarp angewendet.
Basel, Januar 2006
Seite 27/42
Diplomarbeit Image Processing on GPU
Bild 8: Zweites Aktivitätsdiagramm
Normalerweise wird mit den Eingabebildern jeweils vorgängig eine Gausspyramide mit der Tiefe
depth erstellt. Da wir allerdings nur 16 Texturen zur Verfügung haben, müssen wir die Gaussbilder der
aktuellen Tiefe bei jedem Schleifendurchgang neu berechnen.
Basel, Januar 2006
Seite 28/42
Diplomarbeit Image Processing on GPU
6.2
Benchmark
Innerhalb unserer Demo-Applikation haben wir verschiedene Zeitmessungen durchgeführt.
Die Berechnung des Flussfeldes stellt dabei die anspruchvollste Anwendung dar.
Das reine Berechnen des Flusses auf der GPU ist extrem schnell. Jedoch wird für den ersten Aufruf
sehr viel Zeit für den Aufbau der Shader und ähnlicher Dinge verbraucht. Dieses Phänomen ist aber
bekannt und eine einmalige Berechnung auf der GPU deshalb nicht sinnvoll, sondern mehr sich
wiederholende Algorithmen. Darum dürfte die Verbreitung der GPU für Video-Anwendung, wo jeder
Frame berechnet wird, so breit sein.
Für alle Zeitmessungen haben wir ein System mit folgender Ausstattung verwendet:
- Pentium 4, 3.2 GHz
- GeForce 7800 GTX, 256MB RAM, PCIe
- 2 GB RAM
Die benutzten Farbbilder hatten eine Kantenlänge von 512x512 Pixel.
6.2.1
Flussfeld
Die Berechnung des Optischen Flusses (Methode calcFlow()) dauert auf der GPU rund 29ms. Der
allererste Aufruf braucht ein Vielfaches davon.
Die Berechnung auf der CPU ist um ein Vielfaches langsamer. Sie dauert rund 1 Sekunde.
Bei dieser Betrachtung wurde, wie es üblich ist, nur die Berechnungszeit berücksichtigt. Der Anteil,
welcher für den Textur-Download gebraucht wird, fehlt.
6.2.2
Bildinitialisierung
Zum Vergleich wird hier deshalb noch die benötigte Zeit der Methode setImgs() berechnet. Diese setzt
zwei neue Bilder, lädt diese in den Speicher und führt einige Kontrollberechnungen durch, wie z.B.
die maximale Tiefe.
Auf der GPU dauert diese Methode rund 170ms. Die CPU ist hier deutlich schneller mit rund 35ms.
Hier spielt die CPU ihren Vorteil aus, dass der Bildtransfer in eine Textur fehlt. Des Weiteren finden
auch alle Kontrollberechnungen auf der CPU statt. Deshalb hat die GPU-Version keine Möglichkeit,
ihre Stärken auszuspielen.
6.2.3
Morphing
Beim reinen Morphen treten auch deutliche Geschwindigkeitsunterschiede auf. Die CPU braucht für
das reine Morphen 150ms. Die GPU glänzt hier mit Werten von unter 1ms!
Basel, Januar 2006
Seite 29/42
Diplomarbeit Image Processing on GPU
7
Demo-Applikation
Die Implementation des Optischen Flusses wurde in eine Demo-Applikation integriert. Diese bietet
die Möglichkeit, zwei Bilder zu laden und diese zusammen zu morphen.
Bild 9: Screenshot Demo-Applikation
Basel, Januar 2006
Seite 30/42
Diplomarbeit Image Processing on GPU
7.1
Technischer Aufbau
Die Demoapplikation besteht aus zwei Teilen. Einerseits aus der Recheneinheit, andererseits dem
GUI.
Als GUI Framework haben wir Qt 4 verwendet, da es sehr gut dokumentiert ist und in der
Entwicklungsumgebung integriert werden kann. Zusätzlich wird bei aktuellen Entwicklungen der Uni
Basel auch Qt 4 eingesetzt.
Bild 10: Klassendiagramm der Demo-Applikation
7.1.1
Kurze Beschreibung des Aufbaus
Auf eine detaillierte Beschreibung der Applikation wird hier verzichtet. Mehr Informationen dazu
kann der Dokumentation des Quellcodes entnommen werden.
Im Zentrum der Demo-Applikation steht die Klasse OpticalFlowDemo. Diese verwaltet alle nötigen
Widgets. Eines davon ist GLWidget, welches dazu dient, den Framebuffer auszugeben. Diese Klasse
besitzt ein OpticalFlowFactory Objekt. Dieses ist in der Lage, selbständig zu entscheiden, ob eine
OpticalFlowGPU oder OpticalFlowCPU Instanz angelegt werden soll, je nachdem, ob und welche
Grafikkarte im System vorhanden ist.
OpticalFlowGPU und OpticalFlowCPU enthalten die eigentliche Logik. Beide implementieren die
Methoden von OpticalFlow und können daher dasselbe tun.
Die Klasse FlowWindow ist in der Lage, das berechnete Flussfeld auszugeben. Dazu wird das erste
Eingabebild genommen und alle 10 Pixel eine Linie in die entsprechende Richtung mit korrekter
Länge darüber gezeichnet.
Mit dem ImageLoader können die zwei Eingabebilder ausgewählt werden. Unterstützt werden die
Bildformate JPEG, PNG, BMP und GIF.
Damit die berechneten Bilder zu einem Filmchen gerendert werden können, kann mittels BatchSave
eine einstellbare Anzahl gemorphter Zwischenbilder abgespeichert werden.
Wie funktioniert nun das Morphen?
In der Mitte der Applikation befindet sich ein Slider. Dieser dient dazu, das Mischverhältnis der
beiden Eingabebilder zu bestimmen. Wird dieser Slider bewegt, fordert das GLWidget-Objekt von
OpticalFlow ein neues, gemorphtes Bild an und gibt dieses aus.
Basel, Januar 2006
Seite 31/42
Diplomarbeit Image Processing on GPU
Das rechenintensive Flussfeld wird im Übrigen nur dann berechnet, wenn neue Bilder gesetzt wurden.
Danach wird für ein angefordertes, gemorphtes Bild mit dem berechneten Flussfeld weiter gearbeitet.
7.2
Funktionsumfang
Unsere Demo-Applikation bietet verschiedenste Funktionalitäten, die es auch ermöglichen, die Details
des Optischen Flusses besser zu verstehen.
Bild 11: Menü File
Obenstehendes Bild zeigt den Menüpunkt File, welcher eigentlich selbsterklärend ist.
Load Images…
Laden von neuen InputBildern.
Save as…
Speichern des aktuellen Ausgabebildes.
Save Flowfield…
Export…
Quit
Speichern des Flussfeld-Bildes.
Exportiert eine Reihe von Ausgabebildern.
Programm beenden.
Der Menüpunkt Edit beinhaltet die wichtigen Punkte, um die Eigenschaften des Optischen Flusses zu
erkunden bzw. zu ändern.
Bild 12: Menü Edit
Menü
Automatic
CPU
GPU
CannyEdgeDetection
Amount Depth
Decrease Depth
Basel, Januar 2006
Beschreibung
Wenn möglich wird alles auf der GPU gerechnet, sonst auf der CPU.
Die Berechnung des Flusses und alles andere geschieht auf der CPU.
Die Bildoperationen geschehen auf der GPU.
Der Kantenfilter wird aktiviert bzw. deaktiviert. Die Berechnung findet abhängig von
der Wahl auf der CPU bzw. GPU statt.
Erhöht die aktuelle Zahl der verwendeten Auflösungspyramide um 1. Standardmässig
ist der Wert zu Beginn auf 8 bzw. dem maximal möglichen Wert.
Senkt die aktuelle Zahl der verwendeten Auflösungspyramide um 1.
Seite 32/42
Diplomarbeit Image Processing on GPU
Amount/Decrease Depth:
Durch Veränderung der Tiefe der Auflösungspyramide lassen sich völlig unterschiedliche Resultate
erzielen. Ein höherer Wert ermöglicht grössere Bewegungen, bietet aber auch Nachteile. So wird die
Berechnungszeit deutlich höher und es können auch Fragmente entstehen. Ab einer gewissen Tiefe
wird ausserdem oft keine grosse Verbesserung mehr festgestellt.
Einsatz CannyEdge-Detection:
Bild 13: Gemorphtes Bild ohne CannyEdge-Detection
Bild 14: Gemorphtes Bild mit CannyEdge-Detection
Bild 15: Menü View
Der Menüpunkt View beinhaltet nur den Punkt „Show Flowfield“.
Dieser ist aber sehr interessant, da er das aktuelle Flussfeld zwischen den beiden Inputbildern anzeigt.
Dabei wird eine Auswahl von jedem 10. Verschiebungsvektor dargestellt. Der rote Punkt markiert den
Beginn der Vektoren. Das Feld wird bei Änderungen der Eingabe-Bilder oder des Depth aktualisiert.
Bild 16: Ausschnitt aus einem Flussfeld der Tramszene
Basel, Januar 2006
Seite 33/42
Diplomarbeit Image Processing on GPU
Bild 17: Menü Help
Unter „Help“ befinden sich die verschiedenen „About’s“.
Erwähnenswert ist hier nur das „About OpenGL“. Es bietet verschiedene Informationen über die
verwendete Grafikkarte bzw. OpenGL Version.
Basel, Januar 2006
Seite 34/42
Diplomarbeit Image Processing on GPU
8
8.1
Ausblick
Einsatz bei Projekten von Thomas Vetter
Unsere Diplomarbeit stellt für das Projekt von Thomas Vetter grundsätzlich nichts Neues dar. Es
waren auch schon Ansätze vorhanden, die Performance des Algorithmus mittels GPU zu verbessern.
Jedoch bieten die neuen Sprachen HLSL, Cg und GLSL deutliche Vereinfachungen gegenüber früher,
als GPU-Programme in Assembler geschrieben wurden. Auch die unterstützten Instruktionen auf der
GPU sind in den letzten Jahren vielfältiger geworden. Ein Grossteil von Berechnungen könnte deshalb
auf die GPU ausgelagert werden, speziell natürlich alle Berechnungen, welche mit Bildern bzw.
Texturen zu tun haben.
Zu den Projekten von Thomas Vetter lässt sich sagen, dass die Machbarkeit wohl klar ist. Jedoch ist
der Zeitpunkt für einen vollständigen Einsatz noch etwas zu früh. Das grösste Problem ist, dass
momentan nur 16 Texturen in den Grafikkarten-Speicher geladen werden können und es auch nicht
möglich ist, direkt in 3D-Texturen zu rendern (FBO). Jedoch sollte man die Entwicklung im Auge
behalten und versuchen, den Anschluss nicht zu verpassen, um im richtigen Augenblick den Einsatz
nochmals intensiv zu überprüfen.
Für einen richtigen Einsatz muss aber die Kartenunabhängigkeit noch breiter werden. nVidia hat
momentan wohl die Nase etwas vorne, da die Einsetzbarkeit höher ist. Allerdings liegen die Preise für
die Top-Karten auch deutlich über denen der Konkurrenz. Dadurch wird der breite Einsatz etwas
unwahrscheinlicher, da die Karten sehr teuer und zu schnell veraltet sind.
8.2
Image Processing on GPU
Während unserer Diplomarbeit konnten wir auf diesem Gebiet viele Erfahrungen sammeln. Wie aus
diesem Dokument ersichtlich ist, konnten wir gegenüber herkömmlichen CPU Implementationen
erhebliche Geschwindigkeitssteigerungen realisieren.
Auch die Wiederverwendbarkeit der geschriebenen Programme ist deutlich gestiegen. Dank GLSL ist
sichergestellt, dass die nächsten Versionen von Grafik-Karten den Code unterstützen werden. Ältere
Karten hingegen wohl kaum. Unser Code enthält keine herstellerspezifischen Anweisungen, jedoch
müssen einige Extensions verfügbar sein. Diese sind scheinbar auf den ATI-Karten noch nicht
verfügbar (zumindest waren sie dies nicht auf der getesteten ATI-Karte).
Den Aspekt der Wiederverwendbarkeit kann man jedoch auch wieder anders drehen. Der Code wird
zwar auf neuen Karten laufen, jedoch werden sich in nächster Zeit GLSL und die Extensions wohl
noch deutlich weiterentwickeln. Deshalb ist es sehr wahrscheinlich, dass in kürzester Zeit unsere
Implementierung nicht mehr dem aktuellen Stand der Technik entspricht und eine weitere Steigerung
des Leistungsgewinns möglich wäre.
Der Ansatz, durch die GPU Performance-Gewinne zu erhalten, ist sicher auf viele rechenintensive
Projekte anwendbar und somit auch auf den Bereich der verschiedenen Projekte von Thomas Vetter.
Momentan stellt die GPU eine deutlich höhere Performance zur Verfügung als die CPU. Jedoch wird
ein Grossteil bei falschem Einsatz durch Texture-Down- und –Upload zu Nichte gemacht. Der Einsatz
macht nur Sinn, wenn eine möglichst lange Kette von Berechnungen auf der GPU durchgeführt
werden kann und höchstens einzelne Argumente bzw. Vektoren geändert werden müssen, nicht jedoch
ganze Texturen. Bei komplizierteren Anwendungen kann man da auch unerwartet schnell an die
Grenzen der Anzahl Texturen stossen, obwohl diese nicht so klein erscheint. Voraussichtlich sollte
aber Besserung eintreten, sobald die Framebuffer-Objekte 3D-Texturen unterstützen.
Basel, Januar 2006
Seite 35/42
Diplomarbeit Image Processing on GPU
Grundsätzlich kann man die Ergebnisse der Arbeit sehr zwiespältig sehen. Einerseits ist die
Umsetzung geglückt und auch ein Performance-Gewinn klar erkennbar. Anderseits herrschen immer
noch deutliche Einschränkungen bei der GPU-Programmierung. Die Entwicklung ist momentan noch
sehr stark in Bewegung und die Folgen nicht genau abschätzbar. Auch kann man den Ansatz der
Parallelisierung auf der CPU langsam erkennen, da Dual-Core-Prozessoren höhere Leistungen
versprechen und trotzdem viele Instruktionen besitzen. Dürfte der Ansatz der GPU auf die CPU
übertragen werden, könnten die Vorteile der GPU sehr schnell verschwinden und Nachteile wie
Download/Upload und Einschränkungen bezüglich Programmierung überwiegen. Schaffen es die
GPU-Hersteller aber, eine stabile Situation in ihrem Markt durch klarere Standards und bessere
Dokumentationen zu erreichen, ist den Grafikkarten noch ein grosses Potential zuzuschreiben.
Da man meistens nicht das volle Risiko eingehen will und die Entwicklungen allfälliger Projekte
parallel auf GPU und CPU macht, entsteht auch ein deutlich höherer Aufwand. Das Vertrauen in die
CPU dürfte wohl momentan noch deutlich überwiegen.
Basel, Januar 2006
Seite 36/42
Diplomarbeit Image Processing on GPU
9
Fazit
Der Spassfaktor ist bei der Arbeit mit Bildverarbeitung auf der GPU relativ hoch, da man nebst
technischen Erfolgen wie Performance-Gewinnen auch diverse optisch interessante und spassige
Eindrücke gewinnen kann.
Die Arbeit bot jedoch auch Schattenseiten. Die Einarbeitung und auch das Mithalten mit der
technischen Entwicklung bezüglich der GPU ist sehr zeitintensiv und mühsam, da die Quellen oft
schlecht bzw. rar sind. Auch das Einarbeiten für das Verständnis des Optischen Flusses ist aufwändig
und die Komplexität des Algorithmus darf nicht unterschätzt werden.
Die Umsetzung des Optischen Flusses bzw. allgemein von bildverarbeitenden Algorithmen auf der
GPU ist zum heutigen Zeitpunkt grundsätzlich möglich. Einmal eingearbeitet, ist die Umsetzung von
bekannten BV-Algorithmen auf die GPU schnell gemacht. Jedoch muss immer noch mit einigen
Einschränkungen umgegangen werden. Diese sind im Vergleich zu früheren Arbeiten aber deutlich
geringer.
Das Potential der GPU für den allgemeinen Einsatz ist sicher weiterhin vorhanden und
sogar gestiegen. Die schnelle Entwicklung lässt zudem auf viele weitere Möglichkeiten hoffen.
Basel, Januar 2006
Seite 37/42
Diplomarbeit Image Processing on GPU
A
Screenshots
Die nachfolgenden Screenshots zeigen 2 Originalbilder und ein berechnetes Bild.
Zwei ähnliche Gesichter werden zusammen gemorpht.
Bild 18: Gesicht 1
Bild 19: Zusammen gemorpht
Bild 20: Gesicht 2
zu je 50%
Interpolation einer Kopfdrehung zwischen zwei Kopfpositionen.
Bild 21: Originalbild 1
Bild 22: Interpolation 50%
Bild 23: Originalbild 2
Interpolation zwischen zwei Mimiken.
Bild 24: Mimik „böse“
Bild 25: Interpolation 50%
Bild 26: Mimik „freundlich“
entspricht „neutral“
Basel, Januar 2006
Seite 38/42
Diplomarbeit Image Processing on GPU
Eine Landschaftsszene, in welche hinein gezoomt wurde.
Bild 27: Landschaft ohne Zoom
Bild 28: Landschaft mit Zoom
Bild 29: Flussfeld vom ersten zum zweiten Bild
Basel, Januar 2006
Seite 39/42
Diplomarbeit Image Processing on GPU
Morphing zwischen realen, relativ unterschiedlichen Gesichtern.
Bild 30: Clemens
Bild 31: ☺ Cleminik ☺
Bild 32: Dominik
Bei zwei so unterschiedlichen Gesichtern zeigen sich deutliche Schwächen des Algorithmus. Die
Distanzen sind teilweise zu gross, so dass Artefakte entstehen können – siehe Mundbereich.
Gleichwohl ist es erstaunlich, dass ein solch gutes Ergebnis, ohne Hilfe von manuell gesetzten
Referenzpunkten, erzielt werden kann.
Basel, Januar 2006
Seite 40/42
Diplomarbeit Image Processing on GPU
Quellenverzeichnis
Literatur
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
[13]
J.L. Barron and R. Klette. Quantitative Colour Optical Flow, Int. Conf. on Pattern Recognition
(ICPR2002), Vol. 4, pp251-255, August 2002
J.R. Bergen and R. Hingorani. Hierachical Motion-Based Frame Rate Conversion, April 1990
M. Christen. http://www.clockworkcoders.com
GPGPU http://www.gpgpu.org
S. Green. The OpenGL Framebuffer Object Extension, NVIDIA Corporation, 2005
B. Jähne. Digitale Bildverarbeitung, Springer, S. 405-415, 2002
A. Nischwitz und P. Haberäckler, Masterkurs Computergrafik und Bildverarbeitung, Vieweg,
2004
NVidia Corportaion, Fast Texture Downloads and Readbacks using Pixel Buffer Objects in
OpenGL, August 2005
NVidia Corporation, NVIDIA GPU Programming Guide Version 2.4.0, 2005
M. Phar and F. Randima, GPU Gems 2, Addison Wesley, 2005
D. Schreiner, M. Woo, J. Neider and T. Davis. OpenGL Programming Guide, 5th Edition,
Addison Wesley, 2005
R. J. Rost. OpenGL Shading Language, Addison Wesley, 2004
Wikipedia. http://de.wikipedia.org/wiki/Optischer_Fluss
Abbildungen
Bilder 18 und 20 stammen von der Uni Basel.
Bilder 21 und 23 sind Screenshots aus Porenut, Uni Basel (http://porenut.cs.unibas.ch).
Alle anderen Bilder wurden von uns selbst erstellt bzw. fotografiert.
Basel, Januar 2006
Seite 41/42
Diplomarbeit Image Processing on GPU
Anhang:
Ehrlichkeitserklärung
Hiermit bestätigen die unterzeichnenden Autoren dieses Berichts, dass alle nicht klar
gekennzeichneten Stellen von ihnen selbst erarbeitet und verfasst wurden.
Ort und Datum
Unterschrift
Basel, 06.01.2006
Clemens Blumer
Dominik Oriet
Kontakte
Clemens Blumer
[email protected]
Dominik Oriet
[email protected]
Website zum Projekt: http://www.fhbb.ch/informatik/bvmm/index_projekte.html
Uni Basel
Prof. Dr. Thomas Vetter
Bernoullistrasse 16
4056 Basel
http://www.cs.unibas.ch
Basel, Januar 2006
FHBB, Abteilung Informatik
Hofackerstr. 73
4132 Muttenz
[email protected]
http://www.fhbb.ch/informatik
Seite 42/42