Praktikum Medizinische Bildverarbeitung in C# ... - Bernd Radig
Praktikum Medizinische Bildverarbeitung in C# ... - Bernd Radig
Praktikum Medizinische Bildverarbeitung in C# ... - Bernd Radig
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