27.12.2014 Aufrufe

Praktikum Medizinische Bildverarbeitung in C# ... - Bernd Radig

Praktikum Medizinische Bildverarbeitung in C# ... - Bernd Radig

Praktikum Medizinische Bildverarbeitung in C# ... - Bernd Radig

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<strong>Praktikum</strong> <strong>Mediz<strong>in</strong>ische</strong> <strong>Bildverarbeitung</strong> <strong>in</strong> <strong>C#</strong>:<br />

„Volume-Render<strong>in</strong>g“<br />

Technische Universität München / Lehrstuhl Informatik IX SS 2005<br />

Dr. Michael Roth Prof. Dr. <strong>Bernd</strong> <strong>Radig</strong><br />

Aufgabe 37<br />

Blatt 9<br />

Erweitern Sie Ihre Klasse TriangleObject3D um e<strong>in</strong>en Konstruktor, welcher als<br />

Parameter den Date<strong>in</strong>amen e<strong>in</strong>er Videoscape-Datei (siehe Hilfebox) erwartet und<br />

diese e<strong>in</strong>liest.<br />

Es soll <strong>in</strong> Ihrem Programm möglich se<strong>in</strong>, e<strong>in</strong> Objekt über e<strong>in</strong>en Menüpunkt „Load<br />

Object“ mit anschließendem Dateiauswahldialog zu laden.<br />

Zum E<strong>in</strong>lesen der Datei wird Ihnen die Klasse ParseStream auf den WWW-<br />

Seiten des <strong>Praktikum</strong>s zur Verfügung gestellt, die diverse Hilfsfunktionen zum<br />

Verarbeiten e<strong>in</strong>er Textdatei bietet.<br />

Ebenfalls auf den WWW-Seiten f<strong>in</strong>den Sie e<strong>in</strong>ige Beispiel-Dateien im<br />

Videoscape-Format.<br />

Wie sieht e<strong>in</strong>e VideoScape-Datei aus<br />

Dateien <strong>in</strong> diesem Format s<strong>in</strong>d e<strong>in</strong>fache Text-Dateien, deren erste Zeile den zur Identifikation e<strong>in</strong>er<br />

Videoscape-Datei dienenden Str<strong>in</strong>g 3DG1 enthält.<br />

Danach folgt <strong>in</strong> der nächsten Zeile die Anzahl der Vertices des Objekts. Diese werden <strong>in</strong> den darauf<br />

folgenden Zeilen angegeben. Dabei enthält jede Zeile e<strong>in</strong>en Vertex aufgeschlüsselt <strong>in</strong> die X-, Y- und<br />

Z-Komponenten des Positionsvektors.<br />

Nach den Vertices enthalten die restlichen Zeilen nur noch die Flächen<strong>in</strong>formation, wieder jeweils<br />

e<strong>in</strong>e Fläche pro Zeile. Der erste Wert ist dabei die Anzahl der Eckpunkte der Fläche (<strong>in</strong> unserem<br />

<strong>Praktikum</strong> immer 3) danach die drei Indices der Vertices. Das letzte Element e<strong>in</strong>er solchen Zeile<br />

enthält die Farbe des Vertex als Hexadezimale Zahl, die wir aber ignorieren können.<br />

Hier e<strong>in</strong> Beispiel für e<strong>in</strong>e Videoscape-Datei (e<strong>in</strong>e simples Rechteck):<br />

3DG1<br />

4<br />

-1.0 -1.0 0.0<br />

1.0 -1.0 0.0<br />

-1.0 1.0 0.0<br />

1.0 1.0 0.0<br />

3 0 1 2 0xFF0000<br />

3 0 2 3 0xFF0000<br />

Wie funktioniert die Klasse ParseStream<br />

Mit dem Konstruktor wird e<strong>in</strong> ParseStream für e<strong>in</strong>e Datei erzeugt. Danach können Sie mit den<br />

Methoden ReadStr<strong>in</strong>g(), ReadInt() oder ReadDouble() das nächste Datenelement <strong>in</strong> der Datei<br />

auslesen. Dabei werden automatisch führende Leerzeichen oder Neue-Zeile-Symbole übersprungen<br />

und zum nächsten Datenelement vorgerückt.<br />

Seite 1 von 6


Aufgabe 38<br />

Wenn Sie e<strong>in</strong>e Videoscape-Datei e<strong>in</strong>lesen, dann kann es vorkommen, dass das Objekt<br />

zu groß oder nicht zentriert dargestellt wird. Um dies auszugleichen müssen die Vertex-<br />

Positionen des Objekts noch geeignet verarbeitet werden.<br />

Verwenden Sie die <strong>in</strong> den folgenden Teilaufgaben beschriebenen Methoden um<br />

e<strong>in</strong> TriangleObject3D nach dem E<strong>in</strong>lesen zu zentrieren und die Größe geeignet<br />

e<strong>in</strong>zustellen.<br />

a.) Erweitern Sie die Klasse TriangleObject3D um e<strong>in</strong>e Methode Center(),<br />

welche den Schwerpunkt des Objekts (die durchschnittliche Position aller<br />

Vertices) berechnet und von allen Vertex-Positionen abzieht.<br />

b.) Mittels e<strong>in</strong>er weiteren Methode SetScale(double s) soll der Maßstab des<br />

Objekts verändert werden können, und zwar so, dass der durchschnittliche<br />

Abstand der Vertices zum Ursprung gleich s wird. Dazu muss zunächst der<br />

tatsächliche durchschnittliche Abstand t ermittelt werden. Wenn Sie nun alle<br />

Vertex-Positionen mit s/t multiplizieren, wird Ihr Objekt geeignet verkle<strong>in</strong>ert<br />

oder vergrössert.<br />

Aufgabe 39<br />

Für e<strong>in</strong>e realistischere Darstellung von 3D-Objekten bietet OpenGL<br />

Möglichkeiten für schattierte Oberflächen.<br />

Dazu müssen folgende D<strong>in</strong>ge über OpenGL-Kommandos e<strong>in</strong>gestellt werden:<br />

• Materialeigenschaften der Oberfläche<br />

• Lichteigenschaften wie Position und Farbe des Lichts<br />

• Orientierung der Oberfläche, da die Menge des reflektierten Lichts von<br />

dem E<strong>in</strong>fallsw<strong>in</strong>kel der Lichtstrahlen abhängt.<br />

Es bietet sich an, die ersten zwei Konzepte objektorientiert <strong>in</strong> eigenen Klassen zu<br />

kapseln:<br />

a.) Programmieren Sie e<strong>in</strong>e Klasse Material im Namensraum Render<strong>in</strong>g mit<br />

den Properties Color Ambient, Color Diffuse und Color Specular sowie<br />

e<strong>in</strong>er Property float Sh<strong>in</strong><strong>in</strong>ess. Jedes TriangleObject3D hat e<strong>in</strong> eigenes<br />

Material, welches über e<strong>in</strong>e geeignete Property zugänglich ist.<br />

b.) Programmieren Sie e<strong>in</strong>e Klasse Light, ebenfalls im Namensraum<br />

Render<strong>in</strong>g. E<strong>in</strong> Objekt dieses Typs besitzt e<strong>in</strong>e Position und e<strong>in</strong>e Farbe,<br />

die wie immer über Properties zugänglich se<strong>in</strong> sollen. Es gibt <strong>in</strong> diesem<br />

<strong>Praktikum</strong> nur e<strong>in</strong> Licht-Objekt, das im Document gespeichert ist.<br />

Mit der Methode GLCode() können für beide Klassen die Eigenschaften für die<br />

aktuellen OpenGL Render<strong>in</strong>g-Vorgang gesetzt werden.<br />

Die Orientierung e<strong>in</strong>er Fläche wird <strong>in</strong> der Computergrafik durch e<strong>in</strong>e so genannte<br />

Oberflächennormale angegeben, dies ist e<strong>in</strong> E<strong>in</strong>heitsvektor der senkrecht auf der<br />

Oberfläche steht. Dabei kann für jeden Vertex e<strong>in</strong>e eigene Normale angegeben<br />

Seite 2 von 6


werden, dadurch kann man z.B. den E<strong>in</strong>druck e<strong>in</strong>er gekrümmten Oberfläche<br />

erwecken.<br />

Erweitern Sie Ihre Vertex-Klasse um e<strong>in</strong>en Vektor, der die Normale enthält.<br />

Damit Sie die Schattierung auch Testen können, sollten Sie zu TriangleObject3D<br />

e<strong>in</strong>e Methode AddVertex( Vector3D pos, Vector3D normal ); h<strong>in</strong>zufügen, mit der<br />

Sie Vertices mit e<strong>in</strong>er Normale an das Objekt anhängen können.<br />

Erzeugen Sie unter Verwendung dieser Methode e<strong>in</strong> TriangleObject3D mit nur<br />

e<strong>in</strong>em Dreieck und stellen Sie dieses schattiert dar.<br />

Wie werden Materialeigenschaften <strong>in</strong> OpenGL def<strong>in</strong>iert<br />

Dafür wird nur der Befehl PGL.glMaterialfv( PGL.GL_FRONT_AND_BACK, , ); benötigt.<br />

Es bietet sich dabei an, e<strong>in</strong>e Hilfsmethode bereitzustellen, die e<strong>in</strong> System.Draw<strong>in</strong>g.Color-Objekt <strong>in</strong><br />

e<strong>in</strong> entsprechendes float-Array umwandelt, z.B. float[] ToFloatArray( Color c );.<br />

Es ist dann möglich die Properties der Material-Klasse e<strong>in</strong>fach auf die OpenGL-<br />

Materialeigenschaften abzubilden:<br />

PGL.glMaterialfv( PGL.GL_FRONT_AND_BACK, PGL.GL_DIFFUSE,<br />

ToFloatArray( this.Diffuse ) );<br />

Die weiteren nötigen Konstanten lauten PGL.GL_AMBIENT und PGL.GL_SPECULAR.<br />

Lediglich der Sh<strong>in</strong><strong>in</strong>ess-Parameter weicht hiervon ab, weil er ke<strong>in</strong>e Farbe enthält:<br />

PGL.glMaterialfv( PGL.GL_FRONT_AND_BACK, PGL.GL_SHININESS,<br />

ref this.sh<strong>in</strong><strong>in</strong>ess );<br />

Wie werden die Licht-Eigenschaften <strong>in</strong> OpenGL def<strong>in</strong>iert<br />

Zunächst muss die Beleuchtungsberechnung aktiviert werden:<br />

PGL.glEnable( PGL.GL_LIGHTING );<br />

PGL.glEnable( PGL.GL_LIGHT0 );<br />

// Beleuchtung e<strong>in</strong>schalten<br />

// Erste Lichtquelle aktivieren<br />

Die weiteren Lichteigenschaften werden analog zu PGL.glMaterialfv() durch den OpenGL-Befehl<br />

PGL.glLightfv( , ,<br />

);<br />

Für kann PGL.GL_POSITION oder PGL.GL_DIFFUSE e<strong>in</strong>gesetzt werden.<br />

Der Aufbau des Arrays ist für PGL.GL_DIFFUSE klar, bei PGL.GL_POSITION taucht jedoch das<br />

Problem auf, dass offensichtlich e<strong>in</strong> Positionvektor übergeben wird, dieser aber nur drei<br />

Komponenten X,Y und Z besitzt. Die Lösung hierfür: Der Vektor wird als so genannter homogener<br />

Vektor angeben, dies ist e<strong>in</strong> vierdimensionaler Vektor dessen letzte Komponente (häufig W genannt)<br />

für Positionsvektoren immer 1 ist.<br />

Die Position wird durch die aktuelle ModelView-Matrix transformiert, so dass das Setzen der Licht-<br />

Position bevorzugt nach e<strong>in</strong>em PGL.glLoadIdentity() aber noch vor dem Setzen der Transformation<br />

für das 3D-Objekt kommen sollte.<br />

Wie werden die Normalen für e<strong>in</strong>en Vertex gesetzt<br />

Mittels der Methode PGL.glNormal3d( x, y, z ); kann e<strong>in</strong> Normalenvektor für die nachfolgenden<br />

Vertices gesetzt werden.<br />

Seite 3 von 6


Aufgabe 40<br />

Wenn Sie e<strong>in</strong> 3D-Objekt aus e<strong>in</strong>er Datei e<strong>in</strong>lesen, haben Sie ke<strong>in</strong>e Normalen-<br />

Vektoren, weil diese im Videoscape-Format nicht gespeichert werden. Die<br />

Normalenvektoren e<strong>in</strong>es Triangles können aber recht e<strong>in</strong>fach aus den Vertex-<br />

Positionen und dem Kreuzprodukt berechnet werden:<br />

Normale n<br />

C-A<br />

•<br />

A<br />

B-A<br />

Mathematisch formuliert:<br />

n = ( B − A)<br />

× ( C − A)<br />

C<br />

B<br />

Für e<strong>in</strong>en Vertex kann dann e<strong>in</strong>e Normale berechnet werden, <strong>in</strong>dem die Normalen<br />

der angrenzenden Triangles gemittelt und wieder normalisiert werden.<br />

Zusatzfrage: Ist die Division bei der Mittelung noch nötig, wenn man<br />

normalisiert<br />

Erweitern Sie die Klasse Triangle um e<strong>in</strong>e Property Normal (die über obige<br />

Formel entsprechend gesetzt wird, z.B. <strong>in</strong> AddTriangle(...)) sowie die Klasse<br />

TriangleObject3D um e<strong>in</strong>e Methode CalculateNormals(), die den beschriebenen<br />

Algorithmus für die Normalen der Vertices kapselt und automatisch aufgerufen<br />

wird, sobald e<strong>in</strong> Objekt geladen wird.<br />

Aufgabe 41<br />

Wenn Sie nun e<strong>in</strong> e<strong>in</strong>gelesenes Dreiecksobjekt auf dem Bildschirm darstellen, dann<br />

kann es se<strong>in</strong>, dass e<strong>in</strong> Dreieck im Vordergrund durch e<strong>in</strong> weiter h<strong>in</strong>ten liegendes Dreieck<br />

überzeichnet wird.<br />

Um dieses Problem zu lösen unterstützt OpenGL so genannte Depth-Buffers (häufig<br />

auch Z-Buffer genannt). Es wird dabei <strong>in</strong> e<strong>in</strong>em zusätzlichen Buffer für jeden Pixel e<strong>in</strong>e<br />

Tiefe (die Z-Komponente des zugehörigen 3D Oberflächenpunktes nach Anwendung der<br />

Model-View-Matrix) gespeichert. Dadurch kann dann jedes Mal wenn der Pixel mit<br />

e<strong>in</strong>em neuen Oberflächenpunkt überzeichnet werden soll überprüft werden, ob sich der<br />

neue Punkt vor oder h<strong>in</strong>ter dem bereits gezeichneten Punkt bef<strong>in</strong>det. Im letzteren Fall<br />

wird der neue Punkt ignoriert und so die Dreiecke <strong>in</strong> korrekter Reihenfolge dargestellt.<br />

Verwenden Sie e<strong>in</strong>en Depth-Buffer für Ihre Darstellung im OpenGLView, dafür<br />

brauchen Sie nur:<br />

a.) … den Depth-Buffer e<strong>in</strong>schalten, dies geschieht mittels PGL.glEnable(<br />

GL_DEPTH_TEST );<br />

b.) … den Depth-Buffer vor jedem Render<strong>in</strong>g-Durchlauf löschen, <strong>in</strong>dem Sie<br />

bei dem glClear-Aufruf noch das PGL.GL_DEPTH_BUFFER_BIT<br />

setzen. Dieser Wert kann mittels e<strong>in</strong>em bitweisem Oder | mit<br />

PGL.GL_COLOR_BUFFER_BIT verknüpft werden, so dass Sie glClear<br />

nur e<strong>in</strong>mal verwenden brauchen.<br />

Seite 4 von 6


Optionale Aufgabe 4<br />

Diese Aufgabe soll Ihnen zeigen, wie Sie mittels des Freeware-3D-<br />

Modellierungs-Tools Blender eigene 3D-Objekte im Videoscape-Format für das<br />

<strong>Praktikum</strong>s-Programm erzeugen können.<br />

Blender ist von der Seite www.blender3d.org downloadbar.<br />

Wenn Sie Blender starten, ersche<strong>in</strong>t zunächst e<strong>in</strong> 3D-Fenster mit der Default-<br />

Szene, bestehend aus e<strong>in</strong>em Würfel, e<strong>in</strong>er Kamera und e<strong>in</strong>er Lichtquelle.<br />

Das Programm wird mit e<strong>in</strong>er Mischung aus Maus und Tastatur-Kürzeln bedient,<br />

wobei sich die Tastatur-Kommandos meist auf das aktuelle Objekt beziehen,<br />

welches <strong>in</strong> der 3D-Ansicht Rosa dargestellt wird. Das aktuelle Objekt kann durch<br />

Anklicken mit der rechten Maustaste ausgewählt werden.<br />

Die wichtigsten Tastatur-Kürzel s<strong>in</strong>d:<br />

r Rotieren<br />

g Verschieben („grab“)<br />

s Skalieren<br />

n Numerische E<strong>in</strong>gabe der Transformation<br />

x Löschen<br />

Ctrl+x Alles Löschen und zur Default-Szene<br />

a Alle Objekte auswählen/abwählen<br />

b Bereichsauswahl<br />

Tab Wechsel zwischen Vertex-/Objekt-Editor und Szenen-Editor<br />

F12 Rendern der aktuellen Szene<br />

Shift+a Menü mit dem man neue Objekte e<strong>in</strong>fügen kann<br />

Zusätzlich kann man den Tasten auf dem Nummernblock (Num-Lock muss aktiv<br />

se<strong>in</strong>) die Ansicht auf die Szene verändern.<br />

Um z.B. e<strong>in</strong>en 3D-Text zu erstellen gehen Sie folgendermaßen vor:<br />

1. Löschen Sie den Würfel, <strong>in</strong>dem Sie es mit der rechten Maustaste<br />

auswählen und dann die x-Taste drücken. Es ersche<strong>in</strong>t e<strong>in</strong> Menü mit<br />

„Erase Selected“, klicken Sie diesen Menüpunkt an und das Rechteck ist<br />

verschwunden.<br />

2. Drücken Sie Shift+a und wählen Sie <strong>in</strong> dem ersche<strong>in</strong>enden Menü den<br />

Punkt „Add/Text“ aus. Es wird dadurch e<strong>in</strong> 3D-Text erzeugt, den Sie ganz<br />

normal mit der Tastatur editieren können. Mit der Tab-Taste können Sie<br />

den Editor-Modus wieder verlassen.<br />

3. Das Objekt ist momentan noch flach, wie Sie z.B. sehen können, wenn Sie<br />

mittels des Nummernblocks die Ansicht auf das Objekt verändern . Um<br />

dies zu ändern, drücken Sie die F9-Taste (Sie können auch auf das Icon<br />

der „Edit-Buttons“ klicken). Dadurch wechselt <strong>in</strong> der unteren Hälfte des<br />

Bildschirms das Fenster. Mit den Buttons „Ext1“ und „Ext2“ können Sie<br />

die Tiefe und die Stärke der Kantenabflachung des Texts e<strong>in</strong>stellen. Dies<br />

Seite 5 von 6


geschieht durch die l<strong>in</strong>ke Maustaste und Mausbewegungen nach L<strong>in</strong>ks<br />

und Rechts. Wenn Sie zusätzlich die Alt-Taste drücken, können Sie den<br />

Wert fe<strong>in</strong>er e<strong>in</strong>stellen oder Sie können den Button mit Shift und der l<strong>in</strong>ken<br />

Maustaste anklicken, dadurch wird e<strong>in</strong>e Werte<strong>in</strong>gabe per Tastatur<br />

ermöglicht. S<strong>in</strong>nvolle Werte s<strong>in</strong>d z.B. Ext1=0.1 und Ext2 = 0.03.<br />

4. Bis jetzt verwaltet Blender das Objekt noch als Text, für den Videoscape-<br />

Export muss es aber <strong>in</strong> e<strong>in</strong> „Mesh“ umgewandelt werden. Dies geschieht,<br />

<strong>in</strong>dem Sie zweimal Alt+c drücken. Dadurch wird das Objekt zuerst <strong>in</strong> e<strong>in</strong>e<br />

„Curve“ und dann <strong>in</strong> e<strong>in</strong> „Mesh“ umgewandelt.<br />

5. Leider hat die Text-Funktion <strong>in</strong> Blender e<strong>in</strong>en kle<strong>in</strong>en Bug. Diesen sehen<br />

Sie, wenn Sie F9 drücken und die Option „Double-Sided“ deselektieren<br />

und <strong>in</strong> der 3D-Ansicht die Taste z drücken. Teile des Objekts werden<br />

Schwarz dargestellt, weil die Normalen der Dreiecke <strong>in</strong> die falsche<br />

Richtung zeigen. Der nächste Schritt behebt das Problem.<br />

6. Wählen Sie mit den Tasten auf dem Nummern-Block e<strong>in</strong>e Ansicht, bei der<br />

Sie von oben auf den Text schauen. Wechseln Sie mit der Tab-Taste <strong>in</strong><br />

den Edit-Mode, drücken Sei e<strong>in</strong>mal die b-Taste und ziehen Sie mit der<br />

l<strong>in</strong>ken Maustaste e<strong>in</strong> Rechteck um die falsch orientierten Dreiecke aus.<br />

Die Normalen für die ausgewählten Dreiecke können Sie nun mit dem<br />

Button „Flip Normals“ spiegeln.<br />

7. Verlassen Sie den Edit-Modus und wählen Sie im Menü „File/Save<br />

Videoscape“. Im anschliessenden Dialog können Sie e<strong>in</strong>en Date<strong>in</strong>amen<br />

auswählen und das Objekt speichern.<br />

Soweit die Kurze<strong>in</strong>führung <strong>in</strong> Blender. Im Internet gibt es weiterführende<br />

Tutorials, z.B. unter http://www.blender3d.org/Education/, <strong>in</strong> denen auch gezeigt wird,<br />

wie man mit Blender z.B. Animationen macht.<br />

Optionale Aufgabe 5<br />

Die <strong>in</strong> der optionalen Aufgabe 1 erstellte Datei ist zwar e<strong>in</strong>e Videoscape-Datei,<br />

Ihr Programm wird sie aber noch nicht lesen können weil die Laderout<strong>in</strong>e nicht<br />

leistungsfähig bzw. robust genug ist:<br />

Die folgenden beiden Probleme können aber leicht behoben werden:<br />

• Die Datei enthält nicht nur Dreiecke sondern auch Vierecke. Diese kann<br />

man aber abfangen und <strong>in</strong> zwei Dreiecke aufspalten.<br />

• Es treten vere<strong>in</strong>zelt degenerierte Dreiecke auf. Das s<strong>in</strong>d Dreiecke, bei<br />

denen alle Punkte auf e<strong>in</strong>er L<strong>in</strong>ie liegen. Dies ist eigentlich nicht schlimm,<br />

führt aber dazu, dass die Normale für e<strong>in</strong> solches Dreieck (0,0,0) ist und <strong>in</strong><br />

GetNormalized() dann durch 0 geteilt wird. Dadurch entsteht e<strong>in</strong>e<br />

ungültige Normale über die sich dann PGL.glNormal3d() beschwert. Die<br />

e<strong>in</strong>fachste Behandlung dieses Problems ist GL.glNormal3d() anstelle von<br />

PGL.glNormal3d() zu verwenden. Dadurch wird e<strong>in</strong>fach die<br />

entsprechende Fehlermeldung ausgeschaltet.<br />

Seite 6 von 6

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!