Der Start mit der Kinect Kamera und dem Tiefensensor

Kinect_Titel

Nachdem ihr Visual Studio und das Kinect SDK installiert habt, kann mit dem ersten Programm begonnen werden. Das erste Programm ließt die Daten aus dem Tiefensensor und aus der Farbkamera aus und zeigt diese an. Anschließend implementieren wir eine Screenshotfunktion, welche die aktuellen Bilder speichert.

-> Erstellen des Anwendungsfensters:

Als erstes erstellt ihr euch eine neue WPF-Anwendung, öffnet die Datei MainWindow.xaml und klickt unten im Reiter auf Entwurf:

Der_Start(3) Sobald ihr das getan habt, kann damit begonnen werden das Fenster zu designen. Für den Anfang benötigen wir zwei nebeneinander angeordnete Fenster, wo die beiden Bilder der Kamera angezeigt werden. Hierfür wird als erstes ein Grid-Element aus der Toolbox in das Fenster gezogen und an die Fenstergröße angepasst:

Der_Start(1)
Danach fügt ihr zwei Image-Elemente ein, welche ihr Image_Depth und Image_Color nennt. Die beiden Elemente werden jeweils auf die Hälfte der Fenstergröße gezogen:

Der_Start(2)
Damit ist die grafische Oberfläche für unser Programm fertig entworfen und wir können uns dem Code zuwenden.

-> Ein bisschen C#:

Der C#-Code wird in der Datei MainWindow.xaml.cs geschrieben. Wenn ihr diese Datei öffnet, findet ihr bereits folgendes vor:

Als ersten Schritt fügen wir das Kinect Modul hinzu.
Hierfür macht ihr einen Rechtsklick auf Verweise und anschließend klickt ihr auf Verweis hinzufügen:

Der_Start(4)

Jetzt wählt ihr .NET aus und dann fügt ihr das Modul Microsoft.Kinect hinzu. Wenn ihr das getan habt, muss das Modul noch eingebunden werden.
Hierfür fügt ihr folgende Zeilen am Kopf des Programmes ein:

Jetzt werden noch ein paar notwendige Variablen deklariert:

Wie ihr bereits gesehen habt, besitzt die Datei bereits eine Funktion mit dem Namen Loaded (z.B. wie bei mir Kamera_Loaded).
Diese Funktion wird ausgeführt sobald das Fenster geladen wird und genau diese Funktion machen wir uns jetzt zu Nutze.
Als erstes möchten wir das sich das Programm automatisch mit einer angeschlossenen Kinect verbindet.
Dies erledigt die folgende Schleife:

Die Foreach Schleife geht jedes Element in der Liste KinectSensor.KinectSensors, welche alle angeschlossenen Kinect Sensoren beinhaltet, durch und speichert dabei bei jedem Durchlauf den aktuellen Sensor in der Variable aktuellerSensor.
Mit der Zeile

kann der Status des Kinect-Sensors abgerufen werden.
Dies geschieht in der If-Abfrage, wo der Status mit dem Status einer verbundenen Kinect verglichen wird:

Sobald dieser Vergleich erfüllt ist, wird der aktuelle Sensor unter dem Objekt mySensor gespeichert.
Das ganze sollte dann bereits so aussehen:

Der_Start(5)

Besonders interessant ist die Tatsache, dass das Kinect SDK keine maximale Anzahl an verwendbaren Kinect-Sensoren kennt. Es ist also auch problemlos möglich zwei oder mehrere Sensoren gleichzeitig zu verwenden (dafür müssen die Programme aber ausgelegt sein)!

Als nächstes wird das Verhalten des Programms bestimmt sobald ein Sensor gefunden wurde.
Wenn ein Sensor gefunden wurde, soll die Farbkamera und der Tiefensensor eingeschaltet werden und es sollen zwei Bitmaps erzeugt werden, welche die Bilder der beiden Sensoren beinhalten.
Sobald ein Sensor gefunden und damit die If-Abfrage erfüllt wurde, wird mit den Zeilen

der Farb- und der Tiefensensor des Objektes mySensor aktiviert und auf eine Framerate von 30Fps bei 640×480 Pixeln eingestellt.
Die nächsten beiden Zeilen legen zwei Arrays für die Daten der beiden Sensoren an:

Mit dem Befehl FramePixelDataLength erhält man als Rückgabewert die Länge von einem Pixel des Streams um so das Array optimal dimensionieren zu können.
Jetzt werden zwei schreibbare Bitmaps erzeugt:

Diese Bitmaps enthalten später das Bild des jeweiligen Sensors und müssen dem entsprechend veränderbar sein.
Aus diesem Grund wird ein Objekt vom Typ WriteableBitmap verwendet.
Mit den Zeilen

ermittelt man die Höhe bzw. die Breite des jeweiligen Streams um mit diesen Werten die Höhe und die Breite des „Rohbitmaps“ zu definieren.
Die Parameter 96 legt die dpi der X, bzw. der Y-Achse fest und der Parameter PixelFormats.Bgr32 legt das Pixelformat Bgr32, sprich 32 Bits pro Pixel, fest.
Anschließend werden die beiden writeable Bitmaps den beiden Images Image_Depth und Image_Color als Quelle zugewiesen:

Jetzt zeigen die beiden Images die Daten an, die in den Bitmaps depthBitmap und colorBitmap gespeichert sind.
Jetzt fügen wir noch zwei Eventhandler hinzu:

Diese Eventhandler geben uns jedes mal Bescheid wenn der Tiefensensor oder die Kamera ein fertiges Bild erzeugt haben.
Auf diese Weise springt das Programm dann später automatisch in zwei Unterprogramme, wo das Bild dann bearbeitet werden kann (ähnlich einem Interrupt eines Mikrocontrollers).
Einen eventuellen Fehler vom Visual Studio (Der Name … ist im aktuellen Kontext nicht vorhanden) kann man erst mal getrost ignorieren.
Er erscheint, weil noch keine Unterprogramme für die Events programmiert wurden!
Mit dieser If-Abfrage

versucht das Programm sich mit der Kinect zu verbinden.
Mit Hilfe des try – catch sorgt das Programm dafür, dass es nicht abstürzt wenn der Computer sich nicht mit der Kinect verbinden kann (z.B. wenn die Kinect auf einmal aus dem USB Port gezogen wird). Stattdessen fängt es den Fehler ab und führt den Programmteil, der in dem catch Block steht, aus.
Der ganze Programmblock sieht fertig dann so aus:

Der_Start(6)

Jetzt muss die Kinect natürlich auch wieder frei gegeben werden, wenn das Fenster geschlossen wird.
Dazu verwenden wir diese Methode:

Diese Methode wird aufgerufen sobald das Fenster geschlossen wird. Sie prüft ob das Objekt mySensor ungleich null ist, sprich ob dem Objekt überhaupt eine Kinect zugewiesen wurde. Ist dies der Fall, wird der Sensor gestoppt und das beenden des Programms ist abgeschlossen.

Im nächsten Schritt kümmern wir uns um die beiden vorhin aktivierten Eventhandler.
Als erstes bearbeiten wir die Daten für den Tiefensensor.
Sobald das Unterprogramm durch den Eventhandler aufgerufen wurde, wird mit der Zeile

der Datenframe von dem Tiefensensor unter dem Namen depthFrame geöffnet.
Dieser Ausdruck ist nur in dem mit Klammern abgegrenzten Bereich gültig (ähnlich einer If-Abfrage wo eine lokale Variable deklariert ist)!
Danach wird geprüft ob ein Bild vorhanden ist:

Ist ein Bild vorhanden, wird es mit der Zeile

in das vorhin definierte Array depthPixels kopiert.
Auch wenn das Array etwas anders definiert wird als das „normale“ Array handelt es sich um ein ganz normales Array!
Mit der Deklaration DepthImagePixel wird nur ein Array definiert, welches speziell für die Daten aus dem Tiefensensor geeignet ist.
Im letzten Schritt werden die Pixel aus dem Array „depthPixels“ in das Bitmap depthBitmap geschrieben:

Die Funktion WritePixels erwartet als ersten Parameter ein Rechteck, bzw. einen Rahmen der aktualisiert werden soll.
Dieser Rahmen wird mit dem Befehl

erzeugt. Dabei wird der Startpunkt des Rahmens auf die Koordinate 0,0 gelegt und mit den Parametern depthBitmap.PixelWidth und depthBitmap.PixelHeight gibt man an wie hoch bzw. wie breit der Rahmen sein soll.
Wir erinnern uns:
Wir haben bei der Erzeugung des Bitmaps depthBitmap als Höhe und Breite folgendes angegeben:

Dadurch wurde das Bitmap genau so groß wie das Bild vom Tiefensensor. Und diese Werte übertragen wir nun auf den Rahmen.
Nach der Definition des Rahmens wird mit dem Parameter depthPixels gesagt, welches Array von Daten verwendet werden soll um das Bitmap zu erzeugen.
Der Parameter depthBitmap.PixelWidth * sizeof(int) gibt an, dass alle Pixel über die komplette Breite des Bitmaps aktualisiert worden.
Fertig programmiert sieht die Eventroutine dann schließlich so aus:

Der_Start(7)Das selbe Spielchen machen wir jetzt noch für die Kamera.
Da der Aufbau der Eventroutine nahezu identisch mit der des Tiefensensor ist, spare ich mir die Erklärungen.
Die Routine sieht so aus:

Der_Start(8)

Das Grundprogramm ist nun soweit fertig.
Mit einem Klick auf den grünen Pfeil oder der Taste F5 kann das Programm getestet werden.
Es sollte nun etwa sowas hier erscheinen:

Der_Start(9)

Wenn das funktioniert, können wir uns daran machen eine Screenshotfunktion zu implementieren.

-> Der Screenshot Button:

Als allererstes müsst ihr einen Button einfügen.
Dafür wechselt ihr in das .xaml File und wählt unter Toolbox einen Button aus:

Der_Start(10)
Mit einem Doppelklick auf den Button wechselt ihr dann zurück in das .cs File.
Wie ihr seht, erzeugt Visual Studio bereits eine passende Eventroutine für das Betätigen des Buttons.
Als nächstes erstellen wir ein Unterprogramm zum speichern eines Bildes:

Beim Aufruf des Unterprogramms wird das Bitmap, welches wir speichern wollen und der Name worunter es gespeichert werden soll, übergeben.
Als ersten Schritt wird mit der If-Abfrage geprüft ob das Programm mit einer Kinect verbunden ist und dann wird aus dem Bitmap ein Bitmap Frame mit dem Namen Frame, sowie ein Stream für den Arbeitsspeicher mit dem Namen stream generiert:

Im nächsten Schritt wird ein Bitmap zu .png Encoder erzeugt, welcher die Umwandlung des Bildes übernimmt.
Mittels

wird dem Encoder der Bitmapframe übergeben, der daraus einen Bildstream generiert, den wir anschließend mittels

in dem Arbeitsspeicher speichern.
Durch den Befehl

wird ein neues Bitmap-Objekt aus dem Bildstream generiert, welches wir anschließend mit dem Befehl

unter dem angegebenen Pfad als .png Datei speichern.
Das Unterprogramm wird nun ganz einfach bei einem Betätigen des Buttons aufgerufen:

Das Bild findet ihr anschließend unter dem angegebenen Pfad mit dem Namen color.png.
Fertig programmiert sehen die beiden Routinen dann so aus:

Der_Start(11)

Wenn alles funktioniert, habt ihr erfolgreich euer erstes Programm für die Kinect geschrieben!
Für zukünftige Projekte werde ich den Anfangsteil (sprich der Teil wo sich das Programm mit der Kinect verbinden – in meinem Beispiel das Unterprogramm KameraLoaded als bekannt voraussetzen, da es keinen Sinn macht in jedem Artikel das selbe Unterprogramm zu erklären ;)

 

Dokumentation:

 

-> Zurück zu Kinect für Windows

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload CAPTCHA.