Nächste Seite: 3D-Visualisierung
Aufwärts: report
Vorherige Seite: Offline-Algorithmen zur Objektsegmentierung
  Inhalt
Unterabschnitte
Programminterna
Dieses Kapitel stellt den internen Ablauf der Scanner-Applikation näher vor:
die parallele Architektur, die Klassenhierarchie sowie das JAVA-Interface
zur benutzerfreundlichen Bedienung des Programmes.
Ablauf-Skizze
Das Programm verläuft in folgenden Schritten:
Zu Beginn werden für den weiteren Programmablauf relevante
Einstellungen aus der Konfigurationsdatei scanner.cfg gelesen.
Diese Parameter lassen sich grob in zwei Gruppen unterteilen:
- Zum einen sind dies rein Hardware-bedingte
Größen, die durch den Aufbau des 3D-Scanners gegeben sind oder
auf diese direkte Auswirkung haben, wie z.B. die Höhe des
Gerätes, der Start- sowie End-Drehwinkel, die Schrittweite, die maximal sichtbaren Koordinaten der Kamera,
etc.
- Zum anderen sind dort Variablen einstellbar, die den Ablauf des Programmes beeinflussen.
So ist es an dieser Stelle möglich, eine Reihe von heuristischen Parametern zu wählen, wie
beispielsweise die in den Formeln (5.1) - (5.3) benutzen Schranken
, die das Matching zwischen zwei Linien bestimmen; weiterhin diverse Variablen zur Steuerung der Linienerkennung
sowie Boolesche Parameter, die u.a. die Online-Anzeige während des Scannens ein- oder ausschalten.
Die nächsten Schritte dienen der Vorbereitung der Parallelisierung des Programmes (siehe dazu auch Abbildung 5.1): es werden zwei Pipes geöffnet, die die Kommunikation
zwischen den parallelen Prozessen gewährleisten. Über die erste Pipe werden während eines 3D-Scans die laufenden Daten geschickt,
so daß die Scannpunkte online angezeigt werden können. Der Benutzer kann somit den Scan online auf dem Bildschirm verfolgen.
Das Anzeigeprogramm 2show (näher beschrieben in Abschnitt 6), welches diese Daten im Empfang nimmt, wird daraufhin mittels fork als
paralleler Prozeß gestartet.
Die zweite Pipe dient der Kommunikation zwischen dem Hauptprozeß, der die Daten verarbeitet (siehe unten), und dem Kindprozeß, der die Daten liefert.
Haupt- und Kindprozeß werden daraufhin gestartet, sobald die Pipe bereitsteht.
Per Kommandozeilenparameter kann dem Programm mitgeteilt werden, aus welcher Quelle die Daten stammen sollen (intern wird dazu lediglich ein anderer Kindprozeß geforkt):
- Bei der neuen Aufnahme eines 3D-Scans ist es Aufgabe des entsprechenden Kindprozesses, die Daten, die der Scanner liefert, von der
seriellen Schnittstelle abzugreifen und sie an den Hauptprozeß weiterzuleiten sowie gleichzeitig in eine Abfolge von Dateien abzuspeichern.
Damit es es nachfolgend möglich, einen 3D-Scan zu simulieren, also auch ohne laufenden Scanner durchzuführen.
Ferner muß der Motor gesteuert werden, der den Scanner um seine horizontale Achse dreht, und es muß die Kamera ausgelöst werden,
deren Bilder später als Quellen der Texturen in dem Anzeigeprogramm dienen. Schließlich hat der Prozeß dafür Sorge zu tragen, ,,Ausrutscher``
zu eliminieren, indem er bei einem Scan die Anzahl der Punkte, die eindeutig als Meßfehler klassifizierbar sind, mitzählt, korrigiert bzw., falls dies
nicht mehr möglich ist, diesen Scan wiederholt.
- Die zweite Möglichkeit des Aufrufes benutzt die bei einem früheren 3D-Scan gespeicherten Daten-Dateien, d.h. dieser Kindprozeß muß
lediglich die Dateien einlesen und sie an den Hauptprozeß senden.
Der Hauptprozeß übernimmt die Verarbeitung der Daten. Dies sind im Einzelnen:
- Die von dem Kindprozeß ankommenden Daten werden im ersten Schritt scanweise online verarbeitet:
- Die nachfolgenden Aufgaben müssen offline durchgeführt werden, da im folgenden alle Datensätze benötigt werden:
- Dies ist in erster Linie die Objektsegmentierung.
Die aktuelle Repräsentation eines Objektes (vergleiche dazu auch Abbildung 4.5) besteht im einzelnen aus:
- Einem Punkt , der den Mittelpunkt des Objektes bezeichnet.
- Von ausgehend existieren 3 orthogonale Linien (parallel zur -, - bzw. -Achse des Koordinatensystems),
die somit einen eindeutigen Würfel aufspannen.
Dieser Würfel stellt die oben beschriebene Bounding Box dar, in der alle Elemente,
die das Objekt bestimmen, enthalten sind. Wird also ein neues Element in das Objekt mit aufgenommen, wird sich im allgemeinen dieser Würfel weiter ausdehnen.
- Ferner führt jedes Objekt noch einen Radius mit sich: Das Paar beschreibt eine Kugel, welche das gesamte Objekt (inklusive Bounding Box) einfaßt.
Auf diese Weise ist es möglich, einen sehr schnellen Test durchzuführen,
ob überhaupt eine Möglichkeit besteht, daß das zu untersuchende Element sich nahe genug an der Bounding Box des Objektes befindet, um aufgenommen zu werden.
Dies bedeutet, daß das Enthaltensein des Elementes in der umschließende Kugel natürlich keineswegs eine hinreichende Bedingung ist, um
als Teil des Objektes betrachtet werden zu müssen, jedoch stets eine notwendige, die zu testen sehr schnell möglich ist.
- Schließlich noch einer Listenstruktur vom Typ myQueue, um Referenzen auf die Elemente zu speichern, die das Objekt ausmachen.
Der folgende Code-Ausschnitt beschreibt das in Abschnitt 4.2.1 erwähnte Vergrößern eines Objektes o um einen Punkt p (Elemente wie Linien und Flächen, die aus 2 bzw. 4 Punkten bestehen, werden später auf diesen Fall reduziert).
int distance = (int)((o->center - *p).norm()) -
o->radius;
if (distance > 0) o->radius += distance;
// difference of border- and point coordinate
int delta;
// is the point right of the right border, but not
// too far away?
if (delta = (p->x - o->xAxis.to->x),
delta > 0 && delta < maxObjectDistance) {
// move the leftmost border;
o->xAxis.to->x = p->x;
// correct the center, according to the x-Axis
o->center.x += (int)(delta/2);
// now we have to correct all the x-coordinates
// of all other Axis! (otherwise, the axis
// wouldn't cross in the center anymore)
o->yAxis.from->x += (int)(delta/2);
o->yAxis.to->x += (int)(delta/2);
o->zAxis.from->x += (int)(delta/2);
o->zAxis.to->x += (int)(delta/2);
}
// ..... (repeat for the other side of the xAxis;
// afterwards, for y- and zAxis!)
- Als letzter Schritt werden alle erkannten Linien, Flächen und Objekte in einer Datei gespeichert,
welche von 2show zum Anzeigen der gesamten Szene eingelesen wird.
Abbildung:
Überblick über die parallele Architektur der Scanner-Applikation
|
Abbildung 5.2:
Klassenhierarchie
|
Einen Überblick über die Klassenhierarchie liefert Abbildung
5.2.
- Wie bereits beschrieben, werden Punkte,
Linien, Flächen und Objekte in jeweils einer eigenen
Datenstruktur (points_queue,
lines_queue, surfaces_queue,
objects_ queue) verwaltet, die jedoch prinzipiell die selben Funktionalitäten
aufweisen: diese vier Klassen sind abgeleitet von der Basisklasse
myQueue, welche großtenteils übliche Listenoperationen bereitstellt.
- Die Basisklasse myQueue verwaltet Daten vom Typ queue_data, wiederum eine
Basisklasse für die vier Element-Klassen Point, Line, Surface und Object.
Die abgeleiteten Klassen implementieren nun die Datentypen und stellen Element-spezifische Funktionalität bereit. Den besten Überblick
darüber liefert die Datei elements.h.
Diese Vererbung ist insbesondere im Fall der Objekt-Liste (der Listenstruktur als Teil der Klasse Object) nützlich, da Objekte selber eine Liste als Klassenelement besitzen,
welche als Elemente Punkte, Linien und Flächen zu speichern vermag.
- Die vom Scanner gesendeten Daten werden zu Beginn als
Pointer auf Instanzen der Datenstruktur Point
gespeichert. Nahezu alle weiteren Speicherformen (als
Lines oder Surfaces) referenzieren
lediglich auf diese Punkte, d.h. es existiert keine redundante
Mehrfachspeicherung der Daten. Dies geschieht nicht nur aus
Speicherplatzgründen, sondern gewährleistet auch, daß der
Datenbestand permanent konsistent gehalten wird: es ist nicht
notwendig, beispielsweise beim Verschieben eines Punktes alle mit
diesem Punkt inzidenten Linien zu aktualisieren.
- Die doppelt verketteten Listen (Queues) wurden
wiederum gewählt, um den Speicherverbrauch zu minimieren.
Aufgrund der internen Struktur der Queues ist es trotzdem möglich,
einen kompletten Durchlauf der Liste5.1 (aller
Element) in Zeit anstatt der durchaus üblichen Laufzeit
durchzuführen.
Benutzer-Interface
An dieser
Stelle soll das Benutzer-Interface kurz dargestellt werden. Zwar ist es
möglich, sowohl das OpenGL- als auch das Scanner-Programm von der
Kommandozeile aus zu starten. Die vom Benutzer modifizierbaren Parameter
werden dann - wie bereits in Abschnitt 5.1 beschrieben - aus
der Konfigurationsdatei scanner.cfg gelesen.
Eine sehr viel komfortablere Möglichkeit der Modifikation besagter Parameter
und Kontrolle der externen Programme
bietet jedoch das graphische Benutzerinterface, geschrieben in der Sprache JAVA.
Abbildung 5.3:
JAVA-Benutzerinterface: Hauptfenster
|
Das Hauptfenster (vergleiche Abbildung 5.3)
ist gegliedert in drei Bereiche, abgeschlossen durch die Buttons
Scan, Display und Motor.
Innerhalb des Scan-Bereiches lassen sich all jene Parameter einstellen,
welche den eigentliche Scan-Vorgang beeinflussen. Die Betätigung des
Scan-Buttons startet die Scanner-Applikation unter den spezifizierten
Parametern.
Der Display-Bereich beinhaltet dementsprechend Parameter, welche die
Anzeige beeinflussen. Desweiteren existiert ein Textfenster, in welchem der
textuelle Output der Scanner-Applikation ausgegeben wird (siehe auch Abbildung 5.4). Dies sind
Informationen, welche die einzelnen Algorithmen als Statusmeldungen dem
Benutzer übermitteln, wie beispielsweise die Anzahl erkannter Linien pro
Scan. Diese Statusmeldungen werden bei manuellem Start der Scanner-Applikation
auf der Textkonsole ausgegeben. Der Display-Button startet das Anzeigeprogramm
2show, welches den zuletzt durchgeführten Scan visualisiert.
Die Betätigung des Motor-Buttons gibt einen Schieberegler frei, mit
dessen Hilfe sich die Scanner-Hardware manuell direkt um den eingestellten
Winkel drehen läßt.
- Input:
Wird die Einstellung Scanner gewählt, so
werden die Daten aus dem Scanner ausgelesen. Die Einstellung File
hingegen liest die Daten aus Dateien, welche bei einem früheren 3D-Scan
erstellt worden sind.
Bei der Wahl File werden eine Reihe von Parametern (wie beispielsweise die Schrittweite des Motors)
deaktiviert, die sich ausschließlich auf das Verhalten der Scanner-Hardware während eines 3D-Scans auswirken.
- Output:
Die Ausgabe läßt sich von einem ,,normalen`` Modus in
einen dump only Modus schalten, so daß die Daten des Scanners lediglich
als Dateien abgespeichert, jedoch nicht weiter verarbeitet werden.
Hier werden bei der Wahl von dump only alle Parameter deaktiviert, die sich auf
Algorithmen zur Linien-, Flächen- oder Objekterkennung auswirken. Desweiteren ist es nicht möglich,
eine Kombination zu wählen, so daß die Daten aus Dateien gelesen und unbearbeitet als Datei abgespeichert werden.
- Scan settings:
- angle resolution:
Gibt an, in wieviel-Grad-Schritten der Scanner gedreht werden soll, mit 1 Grad als die feinste Einstellung.
Der Wert ist wahlweise über den Schieberegler zu wählen oder direkt in dem Textfeld einzugeben.
- scan resolution:
Der Scanner kann in drei Modi betrieben werden, in denen er wahlweise 500, 250 oder 125 Werte pro Scan liefert.
- Hough transformation:
Die Hough-Transformation ist die eine von zwei möglichen Linienerkennungs-Algorithmen. Dieser Algorithmus liefert die
besseren Ergebnisse als der nachfolgende, ist jedoch auch langsamer in der Ausführung.
- simple algorithm:
Die Güte dieses zweiten Linienerkennungs-Algorithmus läßt sich über den Wert step distance steuern. Es gilt:
Je höher der Wert, desto genauer die Abtastung, also die Vektorisierung der diskreten Eingabedaten in Liniensegmente.
- endpoint deviation:
Dieser Parameter entspricht dem Wert
aus Formel (5.1), der angibt, wie weit die Endpunkte zweier
Linien voneinander entfernt liegen dürfen, um von dem Flächenerkennungs-Algorithmus gematcht zu werden.
- angle deviation:
Dieser Wert entspricht
(Formel (5.3) und gibt den maximalen Winkel zwischen zwei zu matchenden Linien ab.
- min. line length:
Ist die Länge einer Linie geringer als der hier angegebene Wert, so wird sie von dem Flächenerkennungs-Algorithmus ignoriert.
- min. surface size:
Alle Flächen mit mindestens dieser Größe (gemessen in
cm) werden von vornherein als eigenständige Objekte angesehen. Alle weiteren
Elemente werden versucht zu Objekten zu segmentieren.
- max. object distance:
Sind zwei Objekte weiter als die angegebene (euklidische) Distanz voneinander entfernt, so werden sie nicht mehr zu einem einzigen miteinander Objekt verschmolzen.
- Display settings:
- line width:
Gibt die Breite der Linien in Pixel an, die in dem OpenGL-Programm gezeichnet werden.
- output on the fly:
Wenn aktiviert, wird noch vor dem Scanvorgang 2show gestartet, so daß das Ergebnis
des 3D-Scans schon direkt während der Durchführung nahezu in Echtzeit am Bildschirm beobachtet werden kann.
- take photos:
Wenn aktiviert, werden während des Scannens vier Photos erstellt, anhand derer das Anzeigemodul
Texturen auf die dargestellten Elemente legen kann.
- calculate & show textures:
Dieser Parameter kann wahlweise deaktiviert werden, um den Scannvorgang zu beschleunigen, da in diesem Fall
selbst wenn die Photos geschossen werden die Koordinaten der Texturen nicht berechnet werden.
- Configuration: (siehe Abschnitt 5.3.3 sowie Abbildung 5.4)
- data directory:
In diesem Verzeichnis werden die Daten des Scanners als Dateien abgelegt. Um mehrere 3D-Scans
zu speichern ist an dieser Stelle ein anderes Verzeichnis zu wählen (über den ,,select``-Button).
- scanner height:
Die Höhe des Scanners ist relevant für Programm-interne Transformationen der einkommenden Daten.
- coordinates:
Diese vier Koordinaten spezifizieren die maximalen Koordinaten, die von der Kamera abgedeckt werden könne, und sind somit relevant für die Berechnung der Texturen.
- device:
Das Device, an dem der Scanner angeschlossen ist, ist üblicherweise eine der beiden seriellen Schnittstellen /dev/ttyS0 oder
/dev/ttyS1, es kann jedoch auch ein anderes Device angegeben werden.
Abbildung 5.4:
JAVA-Benutzerinterface: Konfigurationsfenster
|
Menüstruktur
- File
- Es stehen folgende Untermenüs zur Verfügung:
- Load
- Lädt eine zuvor gespeicherte Konfigurationsdatei.
- Save as...
- Die aktuellen Einstellungen der Oberfläche lassen sich unter einem anderen Namen abspeichern.
- Save
- Sofern zuvor eine Konfigurationsdatei geladen oder der aktuelle Zustand unter einem anderen Namen gespeichert worden ist,
so lassen sich mit diesem Menüpunkt Modifikationen speichern.
- Exit
- Beendet die Oberfläche.
- Options
- Es stehen folgende Untermenüs zur Verfügung:
- Colors
- Die Farben der Oberfläche können neben den Standard-Einstellungen noch in einen Präsentationsmodus gesetzt werden,
der sich insbesondere für die Präsentation mittels eines Beamers eignet, sowie in einen Modus, der die Farbe der Scanner-Hardware nachahmt.
- Configuration
- Hier öffnet sich ein weiteres Fenster (vergleiche Abbildung 5.4), in dem die bereits beschriebenen
Konfigurations-Parameter einstellbar sind.
- Demonstration
- Der Benutzer kann hier zwischen 4 verschiedenen Demos auswählen (siehe Abbildung 5.5):
Bei Betätigung eines der vier Demo-Buttons wird die entsprechende Konfigurationsdatei samt den zugehörigen Datendateien geladen.
Der Input ist fest auf ,,File``, der Output auf ,,normal`` gesetzt.
Desweiteren wird der Scanvorgang automatisch gestartet.
Sobald das Demonstrationsfenster geschlossen ist, werden die vor dem Demobetrieb
geltenden Einstellungen wieder hergestellt, die Applikation kann wie gewohnt bedient werden.
- Help
- Zu guter letzt führt dieser Menüpunkt zu einer kurzen Hilfe-Einblendung.
Im folgenden sei kurz auf die Implementation des Aufrufes der Scanner-Applikation von dem JAVA-Benutzerinterface aus eingegangen.
Das externe Programm scanner wird mittels Runtime.getRuntime().exec() gestartet.
Dabei ist es möglich, die Ausgaben des aufgerufenen Programmes auf den Standardausgabestream abzufangen und als
InputStream weiterzuverarbeiten: wie in Abbildung 5.5 zu sehen, wird die Ausgabe in das Textfenster der Applikation geschrieben. Aus
Performancegründen wird dabei über den Inputstream noch ein BufferedReader gelegt.
Damit die Ausgabe schon während der Laufzeit des aufgerufenen Programmes angezeigt wird - und nicht erst nach Verlassen der Funktion,
wenn die Bildschirmanzeige neu gezeichnet wird - war es notwendig, diese Funktion als eigenständigen Thread zu realisieren.
Abbildung 5.5:
JAVA-Benutzerinterface: Demonstrationssfenster
|
class startScanner extends Thread {
Frame father;
startScanner(Frame f) { father = f; }
public void run() {
Process scanner;
try {
// save the current values
father.writeValues(new File(System.getProperty(üser.dir") +
"/bin/scanner.cfg"),true);
// compose command string
String exec = "bin/scanner " +
("File".equals(father.InputComboBox.getSelectedItem()) ?
"f" : ("dump only".equals(father.OutputComboBox.
getSelectedItem()) ? "d" : ß"));
// start the scanner
scanner = Runtime.getRuntime().exec(exec);
// catch scanner output via 'getInputStream()'
BufferedReader inStrm = new BufferedReader(
new InputStreamReader(
scanner.getInputStream()));
String c;
while ( (c = inStrm.readLine()) != null) {
father.jTextArea1.append(c + "\n");
};
} catch (Exception e) {
System.err.println("Error: " + e);
e.printStackTrace();
}
}
}
Nächste Seite: 3D-Visualisierung
Aufwärts: report
Vorherige Seite: Offline-Algorithmen zur Objektsegmentierung
  Inhalt