Projekt 27: Carolo Cup - Aufgabe
Aufgabenstellung: Objekt- und Spurerkennung mit Asus XTION
Das Projekt steht im Zusammenhang mit der Aufgabenstellung des Carolo Cups und befasst sich mit dem Teil der Objekt- und Spurerkennung. Das Ziel des Projektes ist die Erstellung eines Modules, welches rechteckige Objekte vor dem Fahrzeug erkennen kann. Gleichfalls soll das Modul in der Lage sein die Spur erkennen zu können.
Das Projekt wurde von Jan Kifmann und Hauke Ludwig (LCP) bearbeitet.
Implementierung - Hardware
Die Hardware besteht aus folgenden Teilen:
Die Asus XTION Kamera besteht aus zwei Sensoren, sowie einer USB-Schnittstelle[1]:
- RGB-Kamera
- Die RGB-Kamera hat eine Auflösung von maximal 640x480 Pixel. Die RGB-Daten werden hierbei mit 24 Bits pro Pixel gespeichert und als eindimensionales Array mit der Länge Höhe*Breite ausgegeben.
- Tiefeninformationssensor
- Der Tiefeninformationssensor besitzt die gleiche Auflösung und hat einen Messbereich zwischen 0.8m und 3.5m. Die Werte werden ebenfalls als eindimensionales Array mit der Länge Höhe*Breite ausgegeben. Hierbei werden die Werte als 2-Byte-Format gespeichert.
Ein CubieBoard dient als zentrale Verarbeitungseinheit. Auf dem CubieBoard wurde die Bibliothek OpenNI2 installiert [2].
Implementierung - Software
Die Implementierung der Software stützt sich auf eine sequenzielle Abarbeitung der Aufgaben. Dies ist durch die Architektur der Hardware bedingt. Das Ansprechen und Abfragen der Kameradaten wird durch die Einbindung der OpenNI2-Bibliothek[3] realisiert.
Grundlegend folgt die Software den Sequenzen Daten abfragen -> Daten aufarbeiten -> Auswerten -> Ausgeben. Hierbei wurde ein modularer Aufbau des Sourcecodes verwendet, dies diente der Transparenz und Orienterung im Code.
RGB-Kamera abfragen
- Die Abfrage der Daten wird über die OpenNI-Bibliothek abgearbeitet. Grundlage ist das Beispielprogramm MultiStreamRead[4]. Aus diesem Beispiel wurde folgender Code zur Abfrage der RGB-Kamera genutzt:
//Initialisierung der Bibliothek OpenNI
Status rc = OpenNI::initialize();
//Aufbau des Datenstreams
Device device;
rc = device.open(ANY_DEVICE);
VideoStream RGB;
rc = RGB.create(device, SENSOR_RGB);
rc = RGB.start();
VideoFrameRef frame;
int changedStreamDummy;
VideoStream* pStream = &RGB;
rc = OpenNI::waitForAnyStream(&pStream, 1, &changedStreamDummy, SAMPLE_READ_WAIT_TIMEOUT);
//Auslesen eines Frames
rc = RGB.readFrame(&frame);
RGB888Pixel* pRGB = (RGB888Pixel*)frame.getData();
//Zerstören des Datenstreams
depth.stop();
depth.destroy();
device.close();
OpenNI::shutdown();
- Wie aus dem Code ersichtlich, wird zuerst die Bibliothek OpenNI initialisiert. Nach der erfolgreichen Initialisierung kann ein Datenstream aufgebaut werden. Dieser Stream stellt die Verbindung zwischen dem Zielsystem und der Asus XTION. Durch diese Verbindung können einzelne Frames ausgelesen werden, welche dann in einem OpenNI-spezifischen Datentypen gespeichert werden.
Tiefeninformationen abfragen
- Gleich zur Abfrage der RGB-Kamera lässt sich auch eine Abfrage der Tiefeninformationen gestalten. Hierbei werden nur wenige Änderungen vollzogen, sodass folgender Code entsteht:
//Initialisierung der Bibliothek OpenNI
Status rc = OpenNI::initialize();
//Aufbau des Datenstreams
Device device;
rc = device.open(ANY_DEVICE);
VideoStream depth;
rc = depth.create(device, SENSOR_DEPTH);
rc = depth.start();
VideoFrameRef frame;
int changedStreamDummy;
VideoStream* pStream = &depth;
rc = OpenNI::waitForAnyStream(&pStream, 1, &changedStreamDummy, SAMPLE_READ_WAIT_TIMEOUT);
//Auslesen eines Frames
rc = depth.readFrame(&frame);
DepthPixel* pDepth = (DepthPixel*)frame.getData();
//Zerstören des Datenstreams
depth.stop();
depth.destroy();
device.close();
OpenNI::shutdown();
- Die Abfrage der Tiefeninformation unterscheidet sich von der Abfrage der RGB-Kamera nur durch die Auswahl des Sensors SENSOR_DEPTH und die Wahl des Datentypens, in dem gespeichert wird.
RGB zu Grayscale
- Nach dem Auslesen der RGB-Kamera müssen die Daten in eine binäre Form gebracht werden, um durch einen vereinfachten Canny-Algorithmus[5] die Kanten extrahieren zu können. Um eine dynamische Verarbeitung zu ermöglichen, werden folgende Prozessschritte vollzogen: RGB -> Grayscale -> Binär. Die grundlegenden Schritte für die Umwandlung eines RGB-Bildes zu einem Grayscale-Bild lässt sich durch folgenden Pseudo-Code beschreiben:
for (gesamtes Bild)
Grau = (Faktor)*R+(Faktor)*G+(Faktor)*B
Mittelwert aufaddieren
end
Mittelwert bilden
- Durch die Bildung des Mittelwertes kann nun die Umwandlung in ein binäres Bild erfolgen. Hierzu wird die generelle Helligkeit des Bildes bewertet und anhand dieses Wertes ein Threshold ermittelt, welches entscheidet, ob ein Bit gesetzt wird oder nicht. Dies lässt sich durch folgenden Pseudo-Code beschreiben:
Threshold ermitteln
for (gesamtes Bild)
if (Pixelwert < Threshold)
Pixel setzen
end
end
Spur extrahieren
- Durch die Ermittlung eines binären Bildes kann eine Kantendetektion erfolgen. Dies wird durch einen vereinfachten Canny-Algorithmus erzielt. An dieser Stelle wurde auf einen kompletten Canny verzichten, da dies die Leistung des RaspberryPi´s übersteigen würde. Der vereinfachte Canny bearbeitet das binäre Bild zeilenweise und sucht nach gesetzten Bits. Anhand der gefunden Bereiche können Linien durch mathematische Modelle gefunden werden.
for (Bildbereiche)
Suche nach gesetzten Pixeln
Zusammenfügen der Pixel zu Bereichen
end
Abgleich der Bereiche mit math.Modell
Ausgabe der Linien nach Abgleich
- Die Ausgabe der Linien erfolgt durch eine Matrix, welche die einzelnen Pixel der Linie pro Zeile beinhaltet. Anhand dieser Matrix kann später durch weitere Prozessschritte eine Linienführung verwirklicht werden.
Objekte extrahieren
- Die Informationen, welche der Tiefensensor ausgibt, müssen in Objekte transformiert werden, um aus diesen Informationen zu erkennen, wann Hindernissen ausgewichen werden muss. Um eine Optimierung des Algorithmus zu ermöglichen, werden sämtliche Bildbereiche ausgegraut, welche nicht interessant sind. Das Interesse an einzelnen Pixeln wurde im Vorweg anhand von Entfernung und Plausibilitäten definiert. Die gefunden interessanten Pixel werden nun anhand ihrer Nachbarpixel bewertet. Aus den Informationen über die Pixel können nun durch mathematische Modelle die Objekte extrahiert werden.
Spur und Objekte ausgeben
- Die aus den vorhergehenden Modulen extrahierten Objekte und Spuren werden durch eine definierte Schnittstelle an weitere Module übergeben. Die übergebenen Informationen werden anhand von Arrays folgender Form ausgegeben:
Objekte: int [n][7] //n definiert die Anzahl der Objekte
[n][0] | [n][1] | [n][2] | [n][3] | [n][4] | [n][5] | [n][6]
x-Pos Tiefe x-Pos Tiefe x-Pos Tiefe y-Pos
links links Mitte Mitte rechts rechts
Spur: int [5][2*x] //x wird beim Kompilieren definiert und stellt
die mögliche Anzahl an Punkten dar, wo eine
Linie gefunden werden kann
[1][1] | [1][2] | ... | [1][n] | [1][n+1] | [1][n+2] | [1][n+3]
x-Pos y-Pos x-Pos y-Pos -1 -1
//n=2*(Anzahl gefundenen Linie): als Delimiter wird der Wert -1
eingetragen, um das Ende der Linie zu kennzeichnen
Implementierung - Hardware
Einzelnachweise
- ↑ [1] Asus XTION Spezifikationen (07.01.2014)
- ↑ [2] How to: Installieren der OpenNI-Bibliothek auf RaspberryPi (14.01.2014)
- ↑ [3] OpenNI-Bibliothek (07.01.2014)
- ↑ [4] OpenNI2::MultiStreamRead Beispielprogramm (07.01.2014)
- ↑ [5] Papageorgiou, Leibold, Buss: Optimierung - Statistische, dynamische, stochastische Verfahren für die Anwendung, Springer, ISBN 978-3-540-34012-6 (S.441ff.) -> eBook-Bibliothek