Wie sich Kinect-1-Apps nach Kinect 2 portieren lassen

Frames

Wer die Datenbasis der Kinect der ersten Generation anzapfen wollte, registrierte sein Interesse an einem oder mehreren FrameReady-Events. Bei der Kinect 2 ist die Lage aufgrund der neuen Treiberarchitektur etwas anders: Frames werden über als Demultiplexer agierende FrameReader-Instanzen an ihre Konsumenten weitergeleitet. Die für die Initialisierung notwendige Funktion setupSources sieht nach der Anpassung so aus:

void setupSources() 
{
myDReader = mySensor.DepthFrameSource.OpenReader();
myDReader.FrameArrived += myDReader_FrameArrived;
FrameDescription myFrameSpecs = myDReader.DepthFrameSource.
FrameDescription;
myDArray1 = new ushort[myFrameSpecs.LengthInPixels];
myDArray2 = new ushort[myFrameSpecs.LengthInPixels];
myDArray3 = new ushort[myFrameSpecs.LengthInPixels];
myHistoArray = new int[50];
myFinalArray = new ushort[myFrameSpecs.LengthInPixels];

}

Bei der Kinect 2 steht AllFramesReady nicht mehr zur Verfügung. Entwickler müssen daher die von den einzelnen Quellen abgefeuerten Frames sammeln und können ihre Bearbeitung bei Bedarf in einer gemeinsamen Methode bündeln. Als Alternative bietet sich die Nutzung des MultiFrameSourceReader an. Für das Beispiel hier genügt es, wenn man FrameArrived nach folgendem Schema implementiert:

void myDReader_FrameArrived(object sender, DepthFrameArrivedEventArgs e) 
{
DepthFrame d = e.FrameReference.AcquireFrame();

if (d == null) return;
myHistoArray = new int[50];

myDArray3 = (ushort[])myDArray2.Clone();
myDArray2 = (ushort[])myDArray1.Clone();
d.CopyFrameDataToArray(myDArray1);
d.Dispose();

Die erste und wichtigste Änderung betrifft die Art des Frame-Handlings. Jede FrameSource stellt ihrem Klienten immer nur einen Frame zur Verfügung: Solange dieser nicht durch Aufruf von Dispose "über den Jordan geschickt" wird, kommen bei neuen Aufrufen von AcquireFrame keine weiteren Informationen nach.

Bei der Kinect 1 wurden die Tiefeninformationen mit dem Spielerindex verarbeitet und gemeinsam an den Entwickler ausgeliefert. Microsoft spendiert der Kinect 2 mit der BodyIndexFrameSource eine eigene Datenquelle für Spielerindizes, weshalb das bisher notwendige "Bit Twiddling" ersatzlos entfällt:

for (int x = 0; x < 512; x++) 
{
for (int y = 0; y < 424; y++)
{
//Get Depth
int innerCoord = y * 512 + x;
ushort depthVal = myDArray1[innerCoord];
//Nicht mehr notwendig!
//depthVal = (short)(depthVal >>
DepthImageFrame.PlayerIndexBitmaskWidth);
myDArray1[innerCoord] = depthVal;
}
}

Zu guter Letzt ist die für die Histogrammerstellung zuständige Auswertungsmethode an die neuen Gegebenheiten anzupassen. Die aus der Fotografie entlehnten Histogramme stellen die Werte aller in einem Feld befindlichen Tupel übersichtlich dar: Als klassischer Anwendungsfall hat sich das Anzeigen der Helligkeitsverteilung eines Fotos etabliert. Das im Beispiel errechnete Tiefenhistogramm zeigt, wie viel Prozent der vom Sensor gesehenen Objekte wie weit von seinem Mittelpunkt entfernt sind. Bei der Kinect der zweiten Generation liegen Tiefendaten immer in einer Auflösung von 512 x 424 Pixel vor. Das Datenformat selbst ist unverändert, der Erfassungsbereich reicht nun von 0,5 bis 4,5 Metern:

    for (int x = 0; x < 512; x++) 
{
for (int y = 0; y < 424; y++)
{
int innerCoord = y * 512 + x;
ushort depth1Val = myDArray1[innerCoord];
ushort depth2Val = myDArray2[innerCoord];
ushort depth3Val = myDArray3[innerCoord];
myFinalArray[innerCoord] = (ushort)(depth1Val /
3 + depth2Val / 3 + depth3Val / 3);

//Perform binning
int histoCoord=myFinalArray[innerCoord] /100;
if(histoCoord>49)histoCoord=49;
myHistoArray[histoCoord]++;
}
}

int maxVal = 1; //Prevent divide by zero
for (int i = 49; i > 0; i--)
{
if (myHistoArray[i] > maxVal) maxVal = myHistoArray[i];
}

DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
SolidColorBrush brickBrush =
new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
Pen brickPen = new Pen(brickBrush, 10);
for (int i = 49; i > 0; i--)
{
drawingContext.DrawLine(brickPen, new Point(i * 10, 480),
new Point(i * 10, 480 - myHistoArray[i] * 480 / maxVal));

}

SolidColorBrush gridBrush =
new SolidColorBrush(Color.FromArgb(255, 128, 128, 128));
Pen gridPen = new Pen(gridBrush, 1);
for (int i = 0; i <= 49; i = i + 10)
{
drawingContext.DrawLine(gridPen, new Point(i * 10, 480),
new Point(i * 10, 0));

}

//Calculate maximum height

int percentage = maxVal / ((640 * 480) / 100);
label1.Content = percentage.ToString() + "%";

drawingContext.Close();
RenderTargetBitmap myTarget =
new RenderTargetBitmap(640, 480, 96, 96, PixelFormats.Pbgra32);
myTarget.Render(drawingVisual);
image1.Source = myTarget;

d.Dispose();
}

Da die neuen Treiber-Assemblies auf der Version 4.5 des .NET Framework basieren, ist die Manifestdatei ein wenig anzupassen. Dafür muss man das Projekt im Solution Explorer rechts anklicken und im daraufhin erscheinenden Menü die Option "Eigenschaften" auswählen. Dann wird das Ziel-Framework auf 4.5 gesetzt, und nach dem Neustarten des Frameworks ist das Programm einsatzbereit – fertig ist das in Abbildung 1 gezeigte Histogramm.

Auch mit Kinect 2 lassen sich Umgebungsdaten abtasten (Abb. 1)