Passa ai contenuti principali

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: ,,,

Commenti

Post popolari in questo blog

VB.NET for Dummies: Gli Eventi

Vorrei iniziare con questo post una serie dedicata ad aspetti di VB.NET di base che possono essere utile a coloro che si avvicinano al mondo .NET e che, in genere, non vengono trattati a livello base. La serie di post non ha la pretesa di essere assolutamente esaustivi sugli argomenti che tratterò In questo primo post parleremo degli eventi. Cosa sono e a cosa servono Un evento è la “notifica” dell’accadimento di qualcosa. Quando, ad esempio, premiamo un bottone della finestra di un’applicazione, dietro le quinte, il bottone stesso “notifica” al mondo circostante che qualcuno, in quell’istante, lo ha premuto. Sta, poi, al mondo circostante preoccuparsi di “intercettare” l’evento stesso per gestirlo (tramite un gestore di evento). Attenzione a non confondere la pressione del tasto con la “notifica” della pressione del tasto: l’evento è la “notifica” dell’accadimento, non l’accadimento stesso. Ma a cosa serve tutto ciò? Utilizzare gli eventi è un modo per disaccoppiare ...

VB.NET: SplashScreen con effetto fade-in

In questo post vorrei proporvi un modo per realizzare una splash screen per le nostre applicazioni Windows Form che appare progressivamente con un effetto fade. Supponiamo di avere il nostro progetto VB.NET in una soluzione Visual Studio 2008 in cui abbiamo il sorgente della nostra applicazione Windows Form. Inseriamo una splash screen utilizzando il menù Progetto->Aggiungi Nuovo Elemento e selezionando il tipo di elemento “Schermata Iniziale” A questo punto Visual Studio creerà, automaticamente, la schermata iniziale che possiamo personalizzare graficamente come vogliamo. Per poter fare in modo che questa finestra appaia nel momento in cui avviamo l’applicazione, è necessario aprire le proprietà del progetto e impostare la maschera di avvio: In questo modo, all’avvio dell’applicazione, la schermata appare immediatamente e scompare un attimo prima della visualizzazione della finestra dell’applicazione. Possiamo far apparire la schermata iniziale con un ef...

Alla scoperta del Kinect: presentazioni e convenevoli

Oggi è arrivato un Kinect nuovo nuovo su cui cominciare a fare sperimentazione ed ho, quindi, deciso di condividere tutto ciò che scopro, le cavolate che faccio e i segreti che scopro con chi mi segue. I post che cercherò di scrivere con frequenza sono post di un neofita che si avvicina all’”Aggeggio” e che quindi sono diretti a chi nulla dell’argomento. Gli esperti troveranno noiosi e banali questi post, ma non si può aver tutto dalla vita. Per cominciare, scartato l’”Aggeggio” ho cominciato a preparare l’ambiente di sviluppo: 1) Visual Studio 2010 Ultimate (che uso normalmente ma che non è necessario in quanto si può sviluppare tranquillamente con la express edition); 2) Kinect SDK, scaricabile all’indirizzo http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/download.aspx 3) DirectX Software Development Kit scaricabile all’indirizzo http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6812 A questo punto vi basta connettere il Kinect al...