Teil 1: Erste Schritte mit Kinect

Kinectologie (Teil 2)
Kommentare

Erste Schritte mit Kinect
Doch damit genug der Theorie. Das erste Beispiel will realisiert werden. Obwohl der Sensor auch mit C++ angesprochen werden kann, verwenden wir in diesem Beispiel C#. Nach dem

Erste Schritte mit Kinect

Doch damit genug der Theorie. Das erste Beispiel will realisiert werden. Obwohl der Sensor auch mit C++ angesprochen werden kann, verwenden wir in diesem Beispiel C#. Nach dem Start von Visual Studio Express Edition erstellen wir ein neues Projekt vom Typ Windows Forms Application. Der Name kann beliebig gewählt werden. Das Beispiel auf der Heft-CD heißt SuSKinect1. Sofort nach der Erstellung des Projekts erweitern wir es um eine Referenz auf die DLL Microsoft.Research.Kinect, die in der Rubrik .NET zu finden ist. Kinect-Instanzen werden in .NET als Instanzen einer bestimmten Runtime-Klasse gehandhabt. Da es in .NET mehrere Runtime-Klassen gibt, empfiehlt sich das im Beispiel gezeigte Ausschreiben der Definition:

public partial class Form1 : Form
{
  Microsoft.Research.Kinect.Nui.Runtime nui;  

Im nächsten Schritt öffnen wir das Formular zur Bearbeitung. Wir ergänzen es um eine 640 x 480 Pixel große PictureBox, die zum Anzeigen der von der Kamera kommenden Bilddaten dient. Im Konstruktor des Formulars initialisieren wir Kinect nun mit dem Codesegment aus Listing 1.

Listing 1

public Form1()
{
  nui = Microsoft.Research.Kinect.Nui.Runtime.Kinects[0];
  nui.Uninitialize();
    nui.Initialize(Microsoft.Research.Kinect.Nui.RuntimeOptions.UseColor);
  myStream = nui.VideoStream;
  myStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
  InitializeComponent();
  timer1.Start();
}  

Als Erstes benötigen wir eine Runtime-Instanz, die auf eine mit dem System verbundene Kinect-Einheit zeigt. Das SDK bietet uns hierzu das Kinect Array. Das „erste“ ans System angeschlossene Kinect befindet sich in der Position 0. Der Aufruf von Uninitialize() erfolgt aus reiner Vorsicht, falls das Objekt aus irgendwelchen Gründen schon „offen“ ist. Im Initialize-Aufruf wird Kinect mitgeteilt, dass wir Videoinformationen benötigen (Color). Danach initialisieren wir den Videostream des Kinect. myStream ist dabei eine globale Variable, die den Videostream aus Effizienzgründen zwischenspeichert. Open() aktiviert den Stream, sodass er fortan Daten von Kinect entgegennimmt. Beim Aufruf von Open sind insgesamt vier Parameter erforderlich: Der erste legt fest, dass ein Videostream geliefert werden soll. Der zweite läuft von null bis vier. Er gibt an, wie viele Frames in Kinect zwischengespeichert werden sollen. Ist der PC kurzfristig zu langsam, speichert Kinect die Frames, anstatt sie zu verwerfen. Parameter Nummer drei gibt die Auflösung des Streams an, Parameter Nummer vier den Typ der zurückzuliefernden Bilddaten. Zu guter Letzt zeichnen wir das Formular auf den Bildschirm und starten den Timer. Mindestens ebenso wichtig ist das Deaktivieren des Sensors nach der Verwendung. In unserem Beispiel greifen wir auf das FormClosed-Event zurück:

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
  timer1.Stop();
  nui.Uninitialize();
}  

Zum Schluss werfen wir noch einen Blick auf die Timer-Routine, die das eigentliche Anzeigen der Daten erledigt (Listing 2). Diese Methode sieht bösartiger aus, als sie ist. Es handelt sich dabei nur um ein wenig Bitmap-Manipulation in Windows Forms. Nach dem Beschaffen eines PlanarImage aus dem Frame erstellen wir uns eine Windows Forms Bitmap in der Größe der von Kinect gelieferten Daten und weisen ihr als Bitfeldtyp 32 RGB-Bits pro Pixel zu. Diese „Bit-Anordnung“ finden wir nämlich auch im von Kinect zurückgegebenen PlanarImage-Objekt. Im nächsten Schritt beschaffen wir uns „Pointer“ auf die Bitmap-Daten und auf die zurückgelieferten Bits und kopieren die Daten aus dem PlanarImage ins Bitmap. Danach folgt nur noch etwas Aufräumen und das Programm ist einsatzbereit.

Listing 2

private void timer1_Tick(object sender, EventArgs e)
{
  ImageFrame myFrame = myStream.GetNextFrame(1);
  if (myFrame != null)
  {
    PlanarImage planarI = myFrame.Image;
    Bitmap myBitmap = new Bitmap(planarI.Width, planarI.Height, PixelFormat.Format32bppRgb);
    BitmapData bmapdata = myBitmap.LockBits(new Rectangle(0, 0, planarI.Width, planarI.Height), ImageLockMode.WriteOnly, myBitmap.PixelFormat);
    IntPtr ptr = bmapdata.Scan0;
    Marshal.Copy(planarI.Bits, 0, ptr, planarI.Width * planarI.BytesPerPixel * planarI.Height);
    myBitmap.UnlockBits(bmapdata);
    pictureBox1.Image = myBitmap;
  }
}
Ende  
Kinect, kipp‘ dich

Obwohl unser Kinect mit dem soeben realisierten Code schon über großes Spionagepotenzial verfügt, ist der mechanische Teil des Geräts bisher noch außer Betrieb. Dabei wäre es oft extrem hilfreich, wenn man den Blickwinkel der Kamera anpassen könnte. Die folgenden Erweiterungen rüsten die Tilt-Funktion nach. Dabei ist zu beachten, dass der Motor des Kinect nicht für regelmäßiges „Tilten“ vorgesehen ist. Microsoft empfiehlt nicht mehr als einen Tilt pro Sekunde, zusätzlich nicht mehr als 15 Tilts in 20 Sekunden. Da man Kinect derzeit nur horizontal tilten kann, benötigen wir ein Label für den derzeit gültigen Winkel und zwei Buttons (Listing 3).

Listing 3

private void CmdUp_Click(object sender, EventArgs e)
{
  myAngle = nui.NuiCamera.ElevationAngle = nui.NuiCamera.ElevationAngle + 2;
  LblAngle.Text = myAngle.ToString();
}

private void CmdDown_Click(object sender, EventArgs e)
{
  myAngle = nui.NuiCamera.ElevationAngle = nui.NuiCamera.ElevationAngle - 2;
  LblAngle.Text = myAngle.ToString();
}  

Interessant ist hier die Verwendung von 2 als Winkelwert. In Tests des Autors reagierte Kinect beim Addieren von 1 oft nicht auf Repositionierungsbefehle. Zur Visualisierung legen wir eine Member-Variable des Formulars an, die den derzeitigen Winkel zwischenspeichert. Im Konstruktor lesen wir den „derzeitigen“ Positionswinkel von Kinect aus, sodass wir von Anfang an „gültige“ Werte anzeigen (Listing 4). Damit sind wir soweit fertig.

Listing 4

public partial class Form1 : Form
{
  Microsoft.Research.Kinect.Nui.Runtime nui;
  ImageStream myStream;
  int myAngle;

  public Form1()
  {
    nui = Microsoft.Research.Kinect.Nui.Runtime.Kinects[0];
    nui.Initialize(Microsoft.Research.Kinect.Nui.RuntimeOptions.UseColor);
    myAngle = nui.NuiCamera.ElevationAngle;
    . . .
    LblAngle.Text = myAngle.ToString();
  }  
WPF

Nach diesen eher grundlegenden Arbeiten wollen wir uns nun in Richtung WPF weiterbewegen. Der Timer ist außerdem ausgesprochen hässlich. Eine eventgetriebene Lösung wäre schöner. All diese Forderungen realisieren wir in SuSKinect2 nun mit Visual Studio in Form eines Projektskeletts vom Typ WPF Application. Auch hier muss das Skelett um die weiter oben besprochene Bibliothek erweitert werden.

Im nächsten Schritt passen wir die Initialisierungsroutine an, wie in Listing 5 zu sehen ist. Der Unterschied zur vorherigen Version der Routine besteht darin, dass wir dem Runtime-Objekt diesmal einen Event Handler einschreiben. Er wird aufgerufen, wann immer ein Frame bereitsteht, und erspart uns so die Arbeit mit dem Timer.

Listing 5

public partial class MainWindow : Window
{
  Runtime myRuntime;

  public MainWindow()
  {
    myRuntime = Microsoft.Research.Kinect.Nui.Runtime.Kinects[0];
    myRuntime.Initialize(RuntimeOptions.UseColor);
    myRuntime.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
    myRuntime.VideoFrameReady += new EventHandler(myRuntime_VideoFrameReady);
    InitializeComponent();
  }  

Danach erweitern wir das Formular um ein Image-Steuerelement namens image1, das zum Anzeigen der von Kinect beschafften Daten dient (Listing 6).

Listing 6

void myRuntime_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
  PlanarImage myImg = e.ImageFrame.Image;
  BitmapSource bs = BitmapSource.Create(myImg.Width, myImg.Height, 96, 96, PixelFormats.Bgr32, null, myImg.Bits, myImg.Width * myImg.BytesPerPixel);
  image1.Source = bs;
}  

Damit fehlt nur noch der Event Handler. Die Event Handler der NUI-Klasse bekommen stets einen Parameter vom Typ ImageFrameReadyEventArgs. Er enthält den ImageFrame, der zu bearbeiten ist. Da die WPF im Bezug auf die Bitmap-Daten sehr flexibel ist, schreiben wir den Inhalt des PlanarImage über eine BitmapSource direkt ins Image-Steuerelement. Um der Spezifikation Genüge zu tun, implementieren wir zuletzt noch den Destruktor:

private void Window_Unloaded(object sender, RoutedEventArgs e)
{
  myRuntime.Uninitialize();
}  
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -