lunedì 17 marzo 2014

Kinect V2: body source – joints

In questo post prenderemo in esame lo stream body source fornito dal Kinect V2 e, in particolare, ci occuperemo della funzionalità che sostituisce quella, già presente nella versione 1 dell’SDK e che prendeva il nome di Skeletal Tracking.

Come accadeva per la versione precedente, il device è in grado di fornirci la posizione nello spazio di un insieme di punti del corpo umano.

Nella precedente versione del Kinect avevamo a disposizione 20 punti (detti joints) mentre nella nuova versione sono 25. Sono stati aggiunti il collo (neck), il pollice (Thumb) di entrambe le mani e la punta dell’insieme delle altre dita (Hand_Tip).

image

Nella precedente versione dell’SDK, la struttura dati che conteneva le informazioni relative ai joints prendeva il nome di Skeleton, in questa versione la struttura dati che contiene i dati dei joints si chiama Body.
Questo perchè, come vedremo anche nei prossimi post, la classe Body contiene un numero di informazioni superiore rispetto alla vecchia skeleton: tra queste troviamo le espressioni, le attività, lo stato delle mani.

Inoltre, a differenza della vecchia versione, in questa abbiamo fino a 6 player tracciati contemporaneamente in modo completo (nella vecchia ne avevamo 2 completamente e 4 solo per il baricentro).

Occupiamoci, in questo post di analizzare solo la parte relativa allo scheletro (sia in termini di joints che di orientamento di questi).

Come già visto in precedenti post, abbiamo la necessità di recuperare il device e, quindi, il reader dello stream body:

Sensor = KinectSensor.Default
If Sensor IsNot Nothing Then
    Sensor.Open()

   BodyReader = Sensor.BodyFrameSource.OpenReader()
End If

In maniera analoga a quanto visto in precedenza dobbiamo gestire l’evento FrameArrived:

If BodyReader IsNot Nothing Then
    AddHandler BodyReader.FrameArrived, AddressOf BodyFrameArrivedHandler
End If

La struttura delle classi in gioco per quanto riguarda reader ed evento è la seguente:

image

image

Il metodo AcquireFrame() della classe BodyFrameReference ci permette di recuperare l’istanza di BodyFrame:

Private Sub BodyFrameArrivedHandler(sender As Object, e As BodyFrameArrivedEventArgs)
    Dim frameReference = e.FrameReference
    Dim frame As BodyFrame = Nothing
    Try
        frame = frameReference.AcquireFrame()
        If frame IsNot Nothing Then

            'Elabora il frame

        End If
    Catch ex As Exception

    Finally
        If frame IsNot Nothing Then
            frame.Dispose()
        End If
    End Try
End Sub

La classe BodyFrame ci mette a disposizione il metodo GetAndRefreshBodyData che ci permette di recuperare effettivamente le istanze dei Body tracciati.

In particolare, il metodo GetAndRefreshBodyData ha un comportamento del tutto particolare:

  • E’ nostro compito preparare e dimensionare opportunamente l’array di oggetti Body da passare al metodo. Il numero di body tracciabili dal Kinect è contenuto nella proprietà  BodyCount della classe BodyFrameSource;
  • Per ogni elemento dell’array da noi passato:
    • se l’elemento è Nothing, viene creata una nuova istanza di Body e memorizzata nell’array;
    • se l’elemento non è nullo, l’istanza viene riutilizzata e riempita con i nuovi dati ottenuti dal nuovo frame.

Questo meccanismo consente di ottimizzare la memoria, riutilizzando le strutture dati e allocando, quindi, meno memoria.

Se abbiamo la necessità di memorizzare una particolare istanza di Body in un determinato frame, è sufficiente salvare l’elemento dell’array desiderato in una variabile d’appoggio e impostare l’elemento al valore Nothing. L’SDK ne creerà uno nuovo quando recupereremo il frame e la vecchia istanza resterà a nostra disposizione )avendo un puntatore valido nella variabile d’appoggio).

La classe Body contiene moltissime informazioni (alcune delle quali verranno prese in esame nei prossimi post) tra le quali troviamo quelle relative ai joint e al loro orientamento:

image

Chi conosce la funzione di skeletal tracking del Kinect V1, potrà osservare che le classi in gioco sono praticamente le stesse.

Ogni joint, identificato da un valore dell’enumerazione JointType, espone una posizione nello spazio (CameraSpacePoint) e un TrackingState.

Ogni JointOrientation, anch’esso identificato dal valore di JointType, un vettore a 4 dimensioni il cui significato è mostrato nella seguente figura:

image

Da notare anche la proprietà ClippedEdges della classe Body che ci permette di sapere se lo scheletro corrispondente viene “tagliato” da uno o più dei bordi (cioè uno dei joints finisce fuori dallo schermo da uno dei lati).

Un esempio di elaborazione dei dati ottenuti dallo stream del body può essere il seguente:

Private Property DrawingGroup As DrawingGroup

Private Property DrawingImage As DrawingImage

Private Sub BodyFrameArrivedHandler(sender As Object, e As BodyFrameArrivedEventArgs)
    Dim frameReference = e.FrameReference
    Dim frame As BodyFrame = Nothing
    Try
        frame = frameReference.AcquireFrame()
        If frame IsNot Nothing Then

            Using dc = DrawingGroup.Open()
                frame.GetAndRefreshBodyData(BodyData)
                dc.DrawRectangle(Brushes.Black, Nothing, New Rect(0.0, 0.0, DisplayWidth, DisplayHeight))
                For Each Body In BodyData

                    If Body.IsTracked Then
                        BodyGraphicHelper.DrawClippedEdges(Body, DisplayHeight, DisplayWidth, dc)
                        Dim joints = Body.Joints

                        Dim jointPoints = New Dictionary(Of JointType, Point)()
                        For Each jointType In joints.Keys
                            Dim depthSpacePoint = CoordinateMapper.MapCameraPointToDepthSpace(joints(jointType).Position)
                            jointPoints(jointType) = New Point(depthSpacePoint.X, depthSpacePoint.Y)
                        Next

                        BodyGraphicHelper.DrawBody(joints, jointPoints, dc)
                    End If
                Next
                DrawingGroup.ClipGeometry = New RectangleGeometry(New Rect(0.0, 0.0, DisplayWidth, DisplayHeight))
            End Using
        End If
    Catch ex As Exception

    Finally
        If frame IsNot Nothing Then
            frame.Dispose()
        End If
    End Try
End Sub

In questo caso, utilizziamo un’istanza della classe DrawingGroup come superfice di disegno e, per ogni body tracciato (proprietà IsTracked a true), disegnamo una riga rossa lungo gli eventuali bordi che “tagliano” lo scheletro e lo scheletro vero e proprio.
Per eseguire il disegno utilizziamo la seguente classe statica:

Imports Microsoft.Kinect

Public Class BodyGraphicHelper

    Private Shared HandSize As Double = 30

    Private Shared JointThickness As Double = 3

    Private Shared ClipBoundsThickness As Double = 10

    Private Shared ReadOnly HandClosedBrush As Brush = New SolidColorBrush(Color.FromArgb(128, 255, 0, 0))

    Private Shared ReadOnly HandOpenBrush As Brush = New SolidColorBrush(Color.FromArgb(128, 0, 255, 0))

    Private Shared ReadOnly HandLassoBrush As Brush = New SolidColorBrush(Color.FromArgb(128, 0, 0, 255))

    Private Shared ReadOnly TrackedJointBrush As Brush = New SolidColorBrush(Color.FromArgb(255, 68, 192, 68))

    Private Shared ReadOnly InferredJointBrush As Brush = Brushes.Yellow

    Private Shared ReadOnly TrackedBonePen As Pen = New Pen(Brushes.Green, 6)

    Private Shared ReadOnly InferredBonePen As Pen = New Pen(Brushes.Gray, 1)

    Public Shared Sub DrawBody(joints As IReadOnlyDictionary(Of JointType, Joint),
                                 jointPoints As IDictionary(Of JointType, Point),
                                 drawingContext As DrawingContext)
        ' Draw the bones

        ' Torso
        DrawBone(joints, jointPoints, JointType.Head, JointType.Neck, drawingContext)
        DrawBone(joints, jointPoints, JointType.Neck, JointType.SpineShoulder, drawingContext)
        DrawBone(joints, jointPoints, JointType.SpineShoulder, JointType.SpineMid, drawingContext)
        DrawBone(joints, jointPoints, JointType.SpineMid, JointType.SpineBase, drawingContext)
        DrawBone(joints, jointPoints, JointType.SpineShoulder, JointType.ShoulderRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.SpineShoulder, JointType.ShoulderLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.SpineBase, JointType.HipRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.SpineBase, JointType.HipLeft, drawingContext)

        ' Right Arm    
        DrawBone(joints, jointPoints, JointType.ShoulderRight, JointType.ElbowRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.ElbowRight, JointType.WristRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.WristRight, JointType.HandRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.HandRight, JointType.HandTipRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.WristRight, JointType.ThumbRight, drawingContext)

        ' Left Arm
        DrawBone(joints, jointPoints, JointType.ShoulderLeft, JointType.ElbowLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.ElbowLeft, JointType.WristLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.WristLeft, JointType.HandLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.HandLeft, JointType.HandTipLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.WristLeft, JointType.ThumbLeft, drawingContext)

        ' Right Leg
        DrawBone(joints, jointPoints, JointType.HipRight, JointType.KneeRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.KneeRight, JointType.AnkleRight, drawingContext)
        DrawBone(joints, jointPoints, JointType.AnkleRight, JointType.FootRight, drawingContext)

        ' Left Leg
        DrawBone(joints, jointPoints, JointType.HipLeft, JointType.KneeLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.KneeLeft, JointType.AnkleLeft, drawingContext)
        DrawBone(joints, jointPoints, JointType.AnkleLeft, JointType.FootLeft, drawingContext)

        ' Draw the joints
        For Each jointType In joints.Keys
            Dim drawBrush As Brush = Nothing

            Dim trackingState = joints(jointType).TrackingState

            If trackingState = trackingState.Tracked Then

                drawBrush = TrackedJointBrush
            ElseIf trackingState = trackingState.Inferred Then
                drawBrush = InferredJointBrush
            End If

            If drawBrush IsNot Nothing Then drawingContext.DrawEllipse(drawBrush, Nothing, jointPoints(jointType), JointThickness, JointThickness)
        Next
    End Sub

    Public Shared Sub DrawBone(joints As IReadOnlyDictionary(Of JointType, Joint),
                                  jointPoints As IDictionary(Of JointType, Point),
                                   jointType0 As JointType, jointType1 As JointType,
                                    drawingContext As DrawingContext)

        Dim joint0 = joints(jointType0)
        Dim joint1 = joints(jointType1)

        If joint0.TrackingState = TrackingState.NotTracked Or joint1.TrackingState = TrackingState.NotTracked Then Exit Sub

        If joint0.TrackingState = TrackingState.Inferred And joint1.TrackingState = TrackingState.Inferred Then Exit Sub

        Dim drawPen = InferredBonePen
        If joint0.TrackingState = TrackingState.Tracked And joint1.TrackingState = TrackingState.Tracked Then drawPen = TrackedBonePen

        drawingContext.DrawLine(drawPen, jointPoints(jointType0), jointPoints(jointType1))
    End Sub

    Public Shared Sub DrawClippedEdges(body As Body, displayHeight As Double, displayWidth As Double, drawingContext As DrawingContext)
        Dim clippedEdges = body.ClippedEdges

        If clippedEdges.HasFlag(FrameEdges.Bottom) Then drawingContext.DrawRectangle(Brushes.Red, Nothing,
            New Rect(0, displayHeight - ClipBoundsThickness, displayWidth, ClipBoundsThickness))

        If clippedEdges.HasFlag(FrameEdges.Top) Then drawingContext.DrawRectangle(Brushes.Red, Nothing,
            New Rect(0, 0, displayWidth, ClipBoundsThickness))

        If clippedEdges.HasFlag(FrameEdges.Left) Then drawingContext.DrawRectangle(Brushes.Red, Nothing,
            New Rect(0, 0, ClipBoundsThickness, displayHeight))

        If clippedEdges.HasFlag(FrameEdges.Right) Then drawingContext.DrawRectangle(Brushes.Red, Nothing,
            New Rect(displayWidth - ClipBoundsThickness, 0, ClipBoundsThickness, displayHeight))
    End Sub
End Class

In sostanza andiamo a disegnare le linee che congiungono le parti del corpo (in pratica le ossa), disegnando lo scheletro del player.

Nei prossimi post andremo ad esaminare quali altre informazioni ci mette a disposizione la classe Body e come possiamo sfruttarle.

Disclaimer: “This is preliminary software and/or hardware and APIs are preliminary and subject to change.”

Technorati Tags: ,,,

Nessun commento: