Teil 2: Skeleton API und Interaktion

Kinectologie zum Zweiten
Kommentare

Im zweiten Teil der Artikelserie „Kinectologie“ beschäftigen wir uns mit skelettalem Tracking und Tiefenframes, die zur Interaktion mit Objekten aus der realen Welt eingesetzt werden können.

Kinect von Microsoft kostet wenig und eignet sich dank der flachen Lernkurve als geradezu perfekter Partygag. Microsoft nutzte ihn einige Zeit zum Auflockern der Wartezeit in der Wiener U-Bahn [1]. Auch für Informatiker bietet das Produkt aus dem Hause Microsoft jede Menge „Spielspaß“. Noch nie war es so einfach, dreidimensionale Daten aus der realen Welt „in den Computer“ zu holen und zu verarbeiten. Im ersten Teil dieser Serie haben wir uns mit der Hardware und der Installation des SDKs befasst. Dieser Artikel geht davon aus, dass Ihr Kinect erstens mit dem PC kommunizieren kann, zweitens mit ausreichend Energie versorgt ist, und dass drittens das SDK samt halbfertigem Beispiel SusKinect3 auf Ihrem PC installiert ist.

Artikelserie
  1. Erste Schritte mit Kinect
  2. Skeleton API und Interaktion
  3. Verarbeitung von Audiodaten mit Kinect
Histogramme?

Auf der Xbox benötigt der Kinect-Sensor maximal zehn Prozent der verfügbaren Rechenleistung. Am PC erreicht man diese Effizienz (noch) nicht. Der zugegebenermaßen nicht mehr allzu neue Samsung X360 des Autors hat mit seinem SU9400-Prozessor rund 33 Prozent der Leistung der Xbox, geht beim Verarbeiten von mehreren Streams aber völlig in die Knie. Aus diesem Grund berechnet das im vorherigen Beispiel erstellte Programm sein Histogramm nur mit jedem zehnten Videoframe. Aber halt, was ist eigentlich ein Histogramm? Die Darstellungsart des Histogramms stammt an sich aus der Bildbearbeitung, ist aber auch in vielen anderen Anwendungsszenarien hilfreich. Die dahinterliegende Idee ist, die Werteverteilung einer Datenfolge zu quantisieren. Als Beispiel dafür zeigt Abbildung 1 ein Foto samt dem dazugehörigen Histogramm.

Abb. 1: Die Farbverteilung des Bilds ist im Histogramm abgebildet
Abb. 1: Die Farbverteilung des Bilds ist im Histogramm abgebildet

Das Erstellen eines Histogramms aus den vom Kinect rückgelieferten Tiefeninformationen würde – oh Wunder – ein Diagramm ergeben, das die „Masseverteilung“ der Objekte über den Abstand vor dem Sensor darstellt. Zum Berechnen des Histogramms greifen wir auf die Funktion createHistogram zu, die als Parameter das PlanarImage verlangt, wie in Listing 1 zu sehen.

Listing 1

void calculateHistogram(PlanarImage _mI)
{
  int[] bitNumber = new int[320];
  int limit = _mI.Bits.Count() / _mI.BytesPerPixel;
  for (int i = limit-1; i >= 0; i--) //Explain this!!! 
  {
    byte lower = _mI.Bits[i * _mI.BytesPerPixel];
    byte higher = _mI.Bits[i * _mI.BytesPerPixel + 1];

    int value = higher;
    value = value << 8;
    value += lower;

    bitNumber[value * 320 / 8192] += 1;
  }


  DrawingVisual drawingVisual = new DrawingVisual();
  DrawingContext drawingContext = drawingVisual.RenderOpen();

  int maxVal=0;
  for (int i = 319; i > 0; i--) //= is always maxval
  {
    if (bitNumber[i] > maxVal) maxVal = bitNumber[i];
  }

  for (int i = 319; i > 0; i--)
  {
    drawingContext.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(120, 120, 128, 128)), 1), new Point(i, 240), new Point(i, 240 - bitNumber[i] * 240 / maxVal));
  }

  drawingContext.Close();

  RenderTargetBitmap myTarget = new RenderTargetBitmap(320, 240, 96, 96, PixelFormats.Pbgra32);
  myTarget.Render(drawingVisual);
  
  ImgHisto.Source = myTarget;
}  

Die Routine besteht im Prinzip aus einer for-Schleife, die jedes Pixel des übergebenen PlanarImages auswertet und ein Array mit Häufigkeitsinformationen erstellt. An der Schleife ist nur die Bedingung interessant: Sie zählt abwärts, da ein Vergleich einer Zahl gegen Null auf den meisten Maschinen schneller erledigt wird als ein Vergleich zweier Zahlen gegeneinander. Im zweiten Teil der Routine wird das Flussdiagramm gezeichnet. Auch hier findet sich eigentlich nichts Weltbewegendes. Zu beachten ist allerdings, dass der Wert 0 von der Auswertung ausgenommen ist. Auf „Kinectisch“ heißt 0 nämlich „keine Ahnung, was dort passiert“. Und das hat in einem Entfernungs-Histogramm nichts verloren, würde es doch die Gewichtung der Entfernungen gefundener Objekte beeinflussen und stören. Der Aufruf der Methode erfolgt aus dem Event Handler für den Tiefenframe. Die Variable Counter dient dabei als Laufvariable, die die Anzahl der bereits absolvierten Frames anzeigt. Wichtig ist, dass diese Methode nur mit „unkorrigierten“ Frames arbeiten kann. Die Ausrichtung ist für das Histogramm egal, und die Skalierung würde nur zu Fehlern in den Distanzinformationen führen.

Lasset uns clippen

Damit sind drei der vier Image-Boxen belebt. Das vierte Bild soll einen „Ausriss“ präsentieren, dessen obere und untere Grenze mit den beiden Slidern festgelegt wird. Zum Rendern des Ausrisses verwenden wir die Funktion wie in Listing 2 zu sehen.

Listing 2

void calculateSmartImage(PlanarImage _mDep, int _lower, int _upper)
{
  int limit = _mDep.Bits.Count() / _mDep.BytesPerPixel;
  for (int i = 0; i < limit; i++)
  {
    byte lower = _mDep.Bits[i * _mDep.BytesPerPixel];
    byte higher = _mDep.Bits[i * _mDep.BytesPerPixel + 1];
    int value = higher;
    value = value << 8;
    value += lower;

    if (value >= _lower && value <= _upper)
    {
      //Do nothing
    }
    else 
    {   //Kill the four points which belong to this depth tuple
      int y320 = i / 320;
      int x320 = i - y320 * 320;
      whitenPoint(x320 * 2, y320 * 2);
      whitenPoint(x320 * 2 + 1, y320 * 2);
      whitenPoint(x320 * 2, y320 * 2 + 1);
      whitenPoint(x320 * 2 + 1, y320 * 2 + 1);
    }
  }


  BitmapSource bs = BitmapSource.Create(myVideoImage.Width, myVideoImage.Height, 96, 96, PixelFormats.Bgr32, null, myVideoImage.Bits, myVideoImage.Width * myVideoImage.BytesPerPixel);
  ImgFinal.Source = bs;
  
}  
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -