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 : Aggregare stringhe con LINQ

Tip facile facile, ma a qualcuno potrebbe servire. Supponiamo di avere una lista di stringhe (magari come risultato di una query LINQ) e di voler ottenere una stringa con la concatenazione delle stesse: Dim list = CreateList() Dim concatStr = (From s In list _ Select s).Aggregate( Function (currentString, nextString) currentString + nextString) MessageBox.Show(concatStr) Il metodo CreateList non ci interessa, in questo momento, ma crea una lista di oggetti String. Protected Function CreateList() As IEnumerable( Of String ) Dim list As String () = {" stringa1 ", " stringa2 ", " stringa3 ", " stringa4 ", " stringa5 "} Return list.AsEnumerable() End Function Questo metodo potrebbe restituire una qualsiasi lista di oggetti di cui, nella select successiva recuperiamo solo stringhe. La stessa tecnica è utilizzabile per concatenare stringhe inserendovi un carattere separatore Dim list = CreateList() Dim

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

VB.NET: Convertire un file DOC in RTF e PDF con office interop

In questo post vorrei proporvi del codice per poter convertire un file .doc in un file .rtf oppure .pdf utilizzando le API di interoperabilità di Office. Creeremo una classe, DocConverter, che esporrà le due funzionalità sopra citate. Cominciamo con il prevedere un attributo privato della classe che rappresenterà l’applicazione Word che utilizzeremo per la conversione. Creeremo l’istanza dell’attributo privato all’interno del costruttore della classe: Public Sub New () If Not CreateWordApp() Then Throw New ApplicationException(" Assembly di interoperabilità con Office non trovato! ") End If End Sub Private _wordApp As Word.ApplicationClass Protected Function CreateWordApp() As Boolean Dim retval = True Try _wordApp = New Word.ApplicationClass() _wordApp.Visible = False Catch ex As System.Exception _wordApp = Nothing retval = False End Try Return retval End Function La conve