Bewegungsdrang (Teil 5): Die Kinect geht unter die Haut

8. April 2013

Wie der ein oder andere vielleicht bemerkt hat, ist es ein wenig still geworden um meine Serie. Die langen grauen Monate der Winters und die tägliche Projektarbeit haben Ihren Tribut gefordert. Doch mit dem Frühlingsanfang, der uns heute mal eben 20 cm Neuschnee samt eines atemberaubenden Verkehrskollaps gebracht hat, ist sie wieder da … die Lust am Schreiben … und ich fange dabei mit der Einleitung an die schon seit Oktober meine Festplatte ziert.

Ich glaube das letzte und einzige Skelett welches mich je begeistert hat war "Achmed the dead terrorist". Als sich dieser Clip quasi viral durch sämtliche Social Networks ausbreitete hatte man als Internet affiner Mensch gar keine Möglichkeit sich dem Ganzen zu entziehen. Dass es jemals dazu kommen könnte dass ein Haufen Knochen mich ähnlich begeistert hätte ich nicht gedacht, aber wie meine Oma schon damals sagte, erstens kommt es anders und zweitens als man denkt.

Die Rede ist von meinem eigenen Bewegungsapparat, oder vielmehr über das was die Kinect intern aus mir macht wenn ich mal wieder hektisch vor dem Bildschirm zapple. Selbiges ist vielleicht nicht ganz so lustig wie der Clip, aber immerhin muss ich mir keinen einen Meter großen Handwärmer auf den Arm stülpen und mit mir selbst reden. In diesem Sinne werde ich heute auf die Skeleton API eingehen.

Punkt, Punkt, Komma, Strich

Mittels des Infrarotsensors ist die Kinect in der Lage bis zu sechs Personen zu erkennen wovon zwei aktiv getrackt werden können. Der Skeletonstream spielt dabei die zentrale Rolle denn er sorgt dafür dass bis zu 30 Frames pro Sekunde mit allen relevanten Joints (Knotenpunkte des Körpers) beim Rechner angeliefert werden.

Quelle: MSDN – Skeletal Tracking

Will man die so erzeugten Daten verarbeiten muss man, ebenso wie bei der RGB-Kamera oder dem Tiefensensor, den genannten Stream aktivieren und die so gefeuerten SkeletonFrameReady-Events entsprechend verarbeiten.

   1: // Generell ist es möglich mehr als einen Kinect Sensor an einem Rechner zu betreiben. Für

   2: // unsere Zwecke reicht es deshalb aus den ersten verfügbaren Sensor mit Status Connected 

   3: // zu ermitteln.

   4: this.Kinect = KinectSensor.KinectSensors.FirstOrDefault(ks => ks.Status == KinectStatus.Connected);

   5:  

   6: if (this.Kinect != null)

   7: {

   8:     try

   9:     {

  10:         // Der Skeleton Stream kann mit und ohne TransformSmoothParameters enabled werden. Für ein

  11:         // einfaches Beispiel reicht der Aufruf ohne, was aber beinhaltet dass Jittereffekte sprich

  12:         // Ungenauigkeiten bei der Auswertung der Koordinaten zum Tragen kommen.

  13:         this.Kinect.SkeletonStream.Enable();

  14:         

  15:         // Last-but-not-least sorgen wir dafür dass die FrameReady Events in der Methode 

  16:         // Kinect_SkeletonFrameReady verarbeitet werden können

  17:         this.Kinect.SkeletonFrameReady += this.Kinect_SkeletonFrameReady;

  18:  

  19:         // Starten des Sensors

  20:         this.Kinect.Start();

  21:     }

  22:     catch (IOException)

  23:     {

  24:         this.Kinect = null;

  25:     }

  26: }

Hat man dies getan, so lassen sich bis zu 20 Joints pro getracktem User verarbeiten, je nachdem was im Field Of View der Kinect erkannt wurde oder eben nicht.

Quelle: MSDN – Tracking Users with Kinect Skeletal Tracking

Eine Auswertung der Koordinaten im Eventhandler folgt hierbei oft dem gleichen Schema. Man übernimmt die durch das Frame gelieferten Informationen und analysiert und/oder vergleicht die gelieferten Daten der Joints je nach vorliegender Aufgabenstellung.

So könnte der Code für eine einfache Analyse wie folgt aussehen:

   1: private void Kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)

   2: {

   3:     // Öffnen des Skeleton Frames

   4:     using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())

   5:     {

   6:         if (skeletonFrame != null)

   7:         {

   8:             // Erstellen eines Arrays für die Skeleton Daten

   9:             Skeleton[] skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];

  10:  

  11:             // Übernahme der Skeletinformation in das Array

  12:             skeletonFrame.CopySkeletonDataTo(skeletons);

  13:  

  14:             // Die Daten des ersten "Benutzers" im getracktem Zustand ermitteln

  15:             Skeleton userSkeleton = skeletons.FirstOrDefault(s => s.TrackingState == SkeletonTrackingState.Tracked);

  16:  

  17:             if (userSkeleton != null)

  18:             {

  19:                 // Ermittlung der Position des Kopfes

  20:                 Joint head = userSkeleton.Joints[JointType.Head];

  21:  

  22:                 // Ermittlung der Position des linken Hand

  23:                 Joint leftHand = userSkeleton.Joints[JointType.HandLeft];

  24:  

  25:                 // Prüfen ob linke Hand "über* dem Kopf ist

  26:                 if (leftHand.Position.Y > head.Position.Y)

  27:                 {

  28:                     // Tue was auch immer

  29:                 }

  30:             }

  31:         }

  32:     }

  33: }

Ergänzend sei an dieser Stelle erwähnt, dass alle gelieferten 3D Koordinaten zwischen –1 und 1 angeliefert werden. Für das obige Beispiel heißt der Verzicht auf die X- bzw. Z Koordinaten also dass wir nur die Höhe der linken Hand bezogen auf die Höhe des Kopfes auswerten.

Darüber hinaus sind noch viele andere Dinge möglich die den Rahmen dieses Artikels sprengen würden. So beinhaltet z.B. das Property BoneOrientations des Skeleton Objekts Informationen über die Orientierung der Joints zueinander wie auch bezogen auf die Sicht der Kamera. Es lassen sich Clipping Bereiche definieren oder gar Ungenauigkeiten des Skeletonstreams durch Smoothing ausgleichen. Die Spielwiese hier ist groß und bleibt an dieser Stelle den eifrigen Lesern mit entsprechendem Spieltrieb überlassen.

Eine Frage der Sichtweise

Bevor ich diesen Artikel abschließe möchte ich jedoch noch kurz auf den Near-Mode der Kinect for Windows eingehen, den ich schon das ein oder andere Mal in der Vergangenheit erwähnt hatte. Will man selbigen nutzen, so muss die Initialisierung im obigen Beispiel um den Aufruf der folgenden Methode erweitert werden:

   1: private void EnableNearMode()

   2: {

   3:     if (this.Kinect != null && this.Kinect.DepthStream != null && this.Kinect.SkeletonStream != null)

   4:     {

   5:         // Near Range für Depth aktivieren

   6:         this.Kinect.DepthStream.Range = DepthRange.Near;

   7:  

   8:         // Near-Mode Tracking aktivieren

   9:         this.Kinect.SkeletonStream.EnableTrackingInNearRange = true;

  10:  

  11:         // Von Default auf "Sitzend" umschalten

  12:         this.Kinect.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;

  13:     }

  14: }

Dies führt dann auch tatsächlich dazu, dass ausschließlich die oberen Joints eines Skeletons getrackt werden.

Ausblick

Heute habe ich Ihnen die Grundlagen des Skeleton API geliefert. Selbige werde ich im nächsten Teil aufgreifen um das Thema Gesten ein wenig zu betrachten. Diese stellen meiner Meinung nach mit den zentralsten Aspekt dar, wenn es darum geht ob und wie weit man dieses kleine schwarze Gadget künftig in unserem Büroalltag vorfinden wird. Ich würde mich freuen wenn Sie auch dann wieder ein bisschen Bewegungsdrang verspüren.