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).
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:
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:
AddHandler BodyReader.FrameArrived, AddressOf BodyFrameArrivedHandler
End If
La struttura delle classi in gioco per quanto riguarda reader ed evento è la seguente:
Il metodo AcquireFrame() della classe BodyFrameReference ci permette di recuperare l’istanza di BodyFrame:
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:
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:
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 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:
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.”
Commenti