Einführung in die Kinect-Programmierung

Know-how  –  0 Kommentare

Als Microsoft Kinect für die Xbox vorstellte, gingen die meisten Entwickler nicht davon aus, dass sie damit viel interagieren würden. Wenige Wochen nach der Erstauslieferung kündigte Microsoft jedoch an, ein SDK auszuliefern, dessen Kommunikationsprotokoll Hacker aus dem Unix-Bereich binnen weniger Tage geknackt hatten. Erste Schritte in die Kinect-Programmierung.

Einer der Gründe für das enorme Interesse der Linux-Anwender war der geringe Preis der Hardware. In der Vergangenheit kosteten derartige Sensorsysteme Hunderte Euros. Ein für die Xbox geeigneter Kinect-Sensor wechselt für weniger als 100 Euro den Besitzer. Natürlich arbeiten klassische Laserscanner um einiges genauer. Allerdings genügt die mit Kinect erreichbare Präzision für viele Anwendungsfälle voll und ganz aus, und der in wenigen Monaten erwartete Nachfolger soll höhere Auflösung im Tiefenbereich bieten.

Aus Entwicklersicht ist es höchste Zeit, einen Blick auf den Sensor zu werfen, denn die Bedeutung von Natural User Interfaces (NUI) wird in den nächsten Jahren zunehmen. Smart TVs und andere Appliances profitieren von "alternativen Eingabemöglichkeiten" – es zahlt sich also aus, Grundkenntnisse in Sachen Sensorsysteme zu erhalten.

Hardware im Blick

Das in Abbildung 1 gezeigte Flussdiagramm gibt eine Übersicht über die Sensor-Hardware. Im Grunde genommen besteht Kinect aus zwei voneinander unabhängigen Modulen, die Tiefen- und Farb-Frames zurückliefern. Die restliche Verarbeitung erfolgt am Rechner.

Die Kinect-Hardware ist vergleichsweise einfach (Abb. 1).

Sowohl der Farb- als auch der Tiefendatenstrom besteht normalerweise aus 30 Frames pro Sekunde, die in VGA-Auflösung angeliefert werden. Bei den Farbdaten kann man zudem ein hochauflösendes Sonderregime aktivieren – in diesem Fall ist die Frame-Rate aufgrund des USB-2.0-Anschlusses stark reduziert.

Zum jetzigen Zeitpunkt bietet Microsoft drei Versionen an. Die Entwicklung von Applikationen für Endanwender ist nur mit dem (vergleichsweise teuren) Kinect for Windows erlaubt, der zudem den sogenannten Near Mode mit reduzierter Scan-Mindestentfernung unterstützt. Der Sensor ist teurer, da Microsoft die Hardware hier nicht durch den Verkauf von Spielen "quersubventioniert".

In den Anfangszeiten der Kinect-Entwicklung nutzten viele Programmierer einen Konsolen-Kinect, der in zwei Versionen verfügbar ist: Der als Nachrüstsatz für die Xbox 360 angebotene Sensor wurde mit einem USB-Anschluss und einem externen Netzteil angeboten. Die Xbox 360s hat stattdessen einen proprietären Port. Wer einen derartigen Sensor besitzt, muss ein externes Netzteil anschaffen.

Aufgrund der zunehmend geringer werdenden Preisdifferenz der Sensoren ist bei Neuanschaffungen nur noch Kinect für Windows empfehlenswert. Microsoft hat Nutzer "umgemodelter" Konsolensensoren noch nicht aus neuen Versionen des SDKs ausgeschlossen – es dürfte aber nur eine Frage der Zeit sein, bis man in Redmond diesen Schritt ergreift.

Die Entwicklung und Nutzung von Anwendungen für Kinect setzt einen unter Windows 7 oder 8 betriebenen Rechner voraus. Zudem ist das SDK zu installieren. Als IDE empfiehlt sich Visual C#, das Projektskelett ist nach der Erstellung um eine Referenz auf die Bibliothek Microsoft.Kinect zu ergänzen.

Sensor auswählen

Microsofts Schnittstelle unterstützt die Verwendung mehrerer Sensoren an einer Arbeitsstation. Zur Vereinfachung der Auswahl enthält das SDK ein Steuerelement. Es fordert den Nutzer zum Anstecken eines Sensors an. Aufgrund der weiten Verbreitung sind die angezeigten Prompts für die meisten Sensorbesitzer bekannt.

Wer das Modul in einem Programm einsetzen möchte, benötigt das Kinect Developer Toolkit. Diese Erweiterung bringt eine Vielzahl zusätzlicher Codebeispiele. Das für die GUI erforderliche Modul hört auf den Namen Microsoft.Kinect.Toolkit und ist in die Projektmappe zu integrieren und mit notwendigen Referenzen zu versehen. Der Code der .xaml-Datei beginnt mit dem Einbinden des für das Toolkit zuständigen Namespace:

<Window x:Class="KinectWPFD1.MainWindow"
xmlns:toolkit="clr-namespace:Microsoft.Kinect.Toolkit;
assembly=Microsoft.Kinect.Toolkit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="759" Width="704">

Im eigentlichen Korpus des Formulars findet der Entwickler neben der Anzeige der vom Sensor ankommenden Daten eine Deklaration der KinectSensorChooserUI. Dieses Widget positioniert sich während der Programmausführung automatisch an der Oberseite des Formulars:

    <Grid>
<Image Height="480" HorizontalAlignment="Left" Name="image1"
Stretch="Fill" VerticalAlignment="Top" Width="640" />
<toolkit:KinectSensorChooserUI x:Name="SensorChooserUI"
IsListening="True" HorizontalAlignment="Center"
VerticalAlignment="Top" />
</Grid>
</Window>

Damit ist es an der Zeit, einen Blick auf die CodeBehind-Klasse zu werfen. Sie enthält vier Member-Variablen, die die vom Sensor angelieferten (oder die vom Sensor zurückgegebenen) Daten entgegennehmen:

public partial class MainWindow : Window
{
KinectSensor mySensor;
WriteableBitmap myBitmap;
short[] myArray;
KinectSensorChooser myChooser;

Nach der Initialisierung des Formulars ist das KinectSensorChooser-Steuerelement mit seiner Logik zu versorgen. Diese muss aber von Hand verdrahtet werden. Danach fehlen nur noch die Zuweisung eines Event-Handlers und der Aufruf der Start()-Methode:

public MainWindow()
{
InitializeComponent();

myChooser = new KinectSensorChooser();
myChooser.KinectChanged += new EventHandler<KinectChangedEventArgs>
(myChooser_KinectChanged);
this.SensorChooserUI.KinectSensorChooser = myChooser;
myChooser.Start();
}

Änderungen am Status des angeschlossenen Sensors werden durch einen Aufruf des Event-Handlers kommuniziert. Im Beispiel ist im ersten Schritt zu prüfen, ob man einen neuen Sensor zugewiesen bekommt (oder ob der Nutzer diesen nur abgesteckt hat). Ist das der Fall, setzt man die Verarbeitung in Gang:

void myChooser_KinectChanged(object sender, KinectChangedEventArgs e)
{
if (null != e.OldSensor)
{
//Alten Kinect deaktivieren
if (mySensor != null)
{
mySensor.Dispose();
}
}

if (null != e.NewSensor)
{

Der dazu notwendige Code zeigt die zum Initialisieren von Streams verwendete Vorgehensweise. Nach dem Sichern der Referenz auf den Sensor wird die Enable-Methode des jeweiligen Streams aufgerufen. Sie erwartet Parameter, die die Art des anzuliefernden Datenstroms näher beschreiben. Danach erstellt man eine WriteableBitmap-Instanz und einen Datenspeicher. Vor dem Aufruf der Start()-Methode des Sensorobjekts fehlt nur noch die Zuweisung des Event-Handlers:

        mySensor = e.NewSensor;
mySensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
myArray = new short[this.mySensor.DepthStream.FramePixelDataLength];
myBitmap = new WriteableBitmap(this.mySensor.DepthStream.FrameWidth,
this.mySensor.DepthStream.FrameHeight,
96.0, 96.0, PixelFormats.Gray16, null);
image1.Source = myBitmap;
mySensor.DepthFrameReady += this.SensorDepthFrameReady;
try
{
this.mySensor.Start();
//SensorChooserUI.Visibility = Visibility.Hidden;
}
catch (IOException)
{
this.mySensor = null;
}
}
}

Die erfolgreiche Beschaffung eines Frames wird durch das Aufrufen der Ereignis-Behandlungsfunktion angezeigt. Microsoft hat im Rahmen des API-Designs großen Wert darauf gelegt, dass sich die zurückgegebenen Daten so einfach wie möglich durch eine WPF PictureBox weiterverarbeiten lassen. Hier beschränkt sich die Aufgabe des Programms auf das "Herausschreiben" der Daten in eine für die PictureBox ansprechbare Speicherstelle. Wer mit den zurückgegebenen Informationen rechnen möchte, muss sie Schritt für Schritt aus dem Array entnehmen:

private void SensorDepthFrameReady(object sender, 
DepthImageFrameReadyEventArgs e)
{
using (DepthImageFrame dFrame = e.OpenDepthImageFrame())
{
if (dFrame != null)
{
dFrame.CopyPixelDataTo(myArray);
myBitmap.WritePixels(
new Int32Rect(0, 0, myBitmap.PixelWidth,
myBitmap.PixelHeight),
myArray,
myBitmap.PixelWidth * sizeof(short),
0);
}
}
}

Es ist außerdem sinnvoll, die Server-Instanz nach dem Programmende durch Aufruf von Stop() freizugeben. Wenn das entfällt, ist der Sensor unter Umständen bis zum Neustart der Workstation geblockt:

    private void Window_Closing(object sender, 
System.ComponentModel.CancelEventArgs e)
{
mySensor.Stop();
}

}

Die von Kinect zurückgelieferten Tiefendaten lassen sich mit den von der Webcam zurückgegebenen Daten zusammenfügen. Aufgrund optischer Parallaxfehler ist es nicht ratsam, die Korrelation durch den Index im Array zu bewerkstelligen. Das SDK bietet mehrere APIs an, die die Zuordnung unter Berücksichtigung der Hardware durchführen. Das Resultat davon ist eine wesentlich gesteigerte Genauigkeit (s. Abb. 2 und 3).

Bei "primitiver Korrelation" decken sich die Silhouetten nicht ... (Abb. 2)
... die Routine des SDKs arbeitet weitaus besser (Abb. 3).