giovedì 29 marzo 2012

Kinect: Gestiamo l’elevation angle del dispositivo

I cambiamenti apportati nell’SDK del Kinect rilasciata a febbraio scorso riguardano anche la gestione della modalità con cui si controlla l’angolo di elevazione del dispositivo.

Anche in questo caso, il punto di ingresso della gestione del dispositivo è l’istanza della KinectSensor che si ottiene come già abbiamo più volte visto nei precedenti articoli:

  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3.     If Sensor.Status = KinectStatus.Connected Then
  4.         Try
  5.             Sensor.Start()
  6.         Catch ex As Exception
  7.  
  8.         End Try
  9.     End If
  10. End If

Possiamo osservare che, per quanto riguarda la mera gestione dell’alzo, non è necessario abilitare alcuno stream, ma generalmente daremo all’utente un’anteprima di ciò che è inquadrato per permettergli di capire se l’alzo impostato è quello corretto.

  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3.     If Sensor.Status = KinectStatus.Connected Then
  4.         AddHandler Sensor.ColorFrameReady, AddressOf ColorFrameReadyHandler
  5.         Sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30)
  6.         Try
  7.             Sensor.Start()
  8.         Catch ex As Exception
  9.  
  10.         End Try
  11.     End If
  12. End If

La classe KinectSensor mette a disposizione tre proprietà che ci permettono di gestire il tutto:

image

La proprietà ElevationAngle, di tipo Integer, contiene l’angolo (in gradi) corrente del dispositivo e, nel momento in cui la impostiamo con un valore valido (vedremo in seguito quali sono), viene eseguito il brandeggiamento verticale del Kinect dall’attuale valore dell’angolo a quello impostato.

ElevationAngle è una proprietà di istanza, quindi deve essere utilizzata su un’istanza valida della classe KinectSensor.

La proprietà MaxElevationAngle, di tipo Integer, contiene il massimo valore dell’angolo verticale (in gradi, verso l’alto) raggiungibile dal dispositivo.

La proprietà MaxElevationAngle è, anch’essa una proprietà di istanza, il che, potenzialmente significa che, in futuro potremmo avere dispositivi connessi al nostro PC con differenti angoli di azimuth.

La proprietà MinElevationAngle, di tipo Integer, contiene il minimo valore dell’angolo verticale (in gradi, verso l’alto) raggiungibile dal dispositivo.

L’angolo 0 è l’angolo che posiziona il Kinect orizzontalmente.

Attualmente i valori degli angoli ammessi sono tra -27° e +27°.

La soluzione allegata propone un semplice client per gestire l’azimuth del Kinect.

 

lunedì 26 marzo 2012

Kinect: gli scheletri e l’uomo vitruviano

Continuando la carrellata di ciò che è cambiato nel nuovo SDK del Kinect rispetto alla beta2, eccoci all’uomo vitruviano (vedi post).

E’ oramai noto che uno degli aspetti più interessanti del Kinect è il fatto che il suo SDK è in grado di fornirci la posizione di alcuni punti ben determinati dei player inquadrati e riconosciuti dal sensore.

L’insieme di questi punti (20 per la precisione) per ogni player è detto Skeleton e i punti sono mostrati in figura:

skeleton joints

L’SDK è in grado di ritornare una collection di oggetti di tipo Joint, ognuno dei quali rappresenta i dati spaziali del singolo punto.

Cominciamo dal vedere come accedere al sensore Kinect e attivare il riconoscimento dello scheletro (Skeletal Tracking).

Come già visto nei precedenti due post della serie, il meccanismo per accedere al Kinect è quello di passare dalla collezione KinectSensors della classe KinectSensor, registrare l’opportuno gestore di evento e abilitare lo stream che ci interessa:

  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3.     If Sensor.Status = KinectStatus.Connected Then
  4.         AddHandler Sensor.SkeletonFrameReady, AddressOf SkeletonFrameHandler
  5.         Sensor.SkeletonStream.Enable()
  6.         Try
  7.             Sensor.Start()
  8.         Catch ex As Exception
  9.  
  10.         End Try
  11.     End If
  12. End If

L’evento SkeletonFrameReady viene sollevato dall’SDK del Kinect ogni volta che un nuovo frame contenente gli Skeleton è pronto.

La figura seguente mostra la struttura delle classi coinvolte nell’argomento dell’evento SkeletonFrameReady:

image

Come già visto per il video e la profondità, anche in questo caso, l’effettivo frame viene ottenuto utilizzando il metodo OpenSkeletonFrame.

Osserviamo che, a differenza della Beta2, la classe SkeletonFrame non ha più le proprietà NormalToGravity e Quality (poco significative) e la proprietà FloorClipPlane è di tipo Tuple(Of Single, Single, Single, Single) anziché Vector.

La proprietà FloorClipPlane fornisce i coefficienti dell’equazione del piano di terra (pavimento) stimata dall’SDK. L’equazione ha la forma:

Ax+By+Cz+D=0

dove:
    A = FloorClipPlane.Item1
    B = FloorClipPlane.Item2
    C = FloorClipPlane.Item3
    D = FloorClipPlane.Item4

L’equazione del piano è normalizzata, quindi D rappresenta, di fatto, l’altezza della camera rispetto al pavimento. Anche in questa versione dell’SDK, come nella versione Beta, il vettore è sempre nullo.

Una volta ottenuta l’istanza dello SkeletonFrame, possiamo utilizzare il metodo CopySkeletonDataTo  per recuperare gli effettivi dati degli scheletri:

  1. Private Skeletons() As Skeleton = New Skeleton(6) {}
  2.  
  3. Private Sub SkeletonFrameHandler(sender As Object, e As SkeletonFrameReadyEventArgs)
  4.     Dim skeletonFrame = e.OpenSkeletonFrame()
  5.     If skeletonFrame IsNot Nothing Then
  6.         skeletonFrame.CopySkeletonDataTo(Skeletons)
  7.         '
  8.         ' gestione degli skeleton recuperati
  9.         '
  10.     End If
  11. End Sub

La classe Skeleton (nella Beta2 si chiamava SkeletonData) contiene i dati essenziali degli scheletri e il diagramma delle classi coinvolte è il seguente:

ClassDiagram-SkeletonData

Ogni Skeleton ha un suo TrackingId che lo identifica univocamente, un TrackingState che ci dice se è tracciato o meno (ricordiamo che su 6 scheletri disponibili, solo fino a 2 sono tracciati contemporaneamente), una posizione (proprietà Position) che dovrebbe rappresentare il baricentro e una collezione di Joint (proprietà Joints).

Possiamo recuperare i singoli Joint a partire dal loro tipo (enumerazione JointType).

Questa enumerazione era già presente nella Beta2 ma il suo nome era JointId (ora è decisamente più chiara) così come la proprietà del Joint che la contiene si chiama JointType (si chiamava JointID nella Beta2).

Altri cambiamenti sono stati introdotti nelle Position (sia dello Skeleton che del Joint): nella Beta2 queste erano di tipo Vector, mentre ora sono di tipo SkeletonPoint. La SkeletonPoint non ha più la proprietà W (presente nel Vector) e non significativa nella Beta2.

Come abbiamo già visto quando abbiamo parlato del sensore di profondità, il Kinect for Windows (e, quindi, l’SDK) ha la modalità “Near Mode”.

Questa influenza non solo la minima distanza di riconoscimento di un player (come già visto nel precedente post) ma anche il numero di joint rilevati, come mostrato nella seguente tabella:

Near Mode vs Default Mode Funzionalità

La tabella va necessariamente spiegata perchè fuorviante: nel caso di Near Mode, la classe Skeleton valorizza la sola proprietà Position (e solo se l’intero scheletro è tracciato), quindi il punto denominato “Center Hip Joint” non è il Joint “HipCenter” (uno dei 20 a disposizione) anche se, in prima approssimazione, possiamo pensarli coincidenti.

Per poter passare nella modalità “Near Mode” si deve intervenire sulla proprietà Range del DepthStream esposto dal KinectSensor impostandola a KinectRange.NearMode.

Se si imposta la proprietà Range del DepthStream quando questo non è stato abilitato tramite il metodo Enable, non si ottiene alcun errore, ma il sensore non va il modalità near mode.

L’applicazione proposta in allegato mostra, istante per istante, le posizioni dei 20 punti sensibili dello skeletal tracking, come mostrato in figura:

15-03-2012 20-34-50

 

 

mercoledì 21 marzo 2012

Articolo su ioProgrammo di Aprile

Vi segnalo l’uscita del mio articolo “Traduzioni semplici con le API di Microsoft” in cui si parla di Microsoft Translator.

image

L’argomento verrà trattato anche nella sessione del DotNetCampus nella sezione “Gaming & Fun”.

 

Kinect: Questione di profondità

In questo post continuiamo ad analizzare i cambiamenti apportati all’SDK v.1.0 di Kinect pubblicato il 1 Febbraio 2012.
In particolare ci occuperemo della capacità che ha il Kinect di fornire frame in cui l’immagine non è la rappresentazione fedele della realtà che ci circonda ma la rappresentazione bidimensionale della distanza degli oggetti dai sensori di profondità.
Il Kinect, infatti, dispone di un sensore di profondità che è in grado di fornirci la distanza dei punti inquadrati da se stesso e, in più, è in grado di dirci a quale “player” fa riferimento ogni singolo pixel.
Come già visto nel precedente post (link), per recuperare l’istanza della classe che ci permette di accedere al dispositivo Kinect, abilitare lo stream dei dati di profondità e avviare il tutto possiamo procedere in questo modo:
  1. Verificare se sono presenti dispositivi Kinect collegati tramite la proprietà statica KinectSensors della classe KinectSensor;
  2. Recuperare, ad esempio, il primo dei dispositivi;
  3. Abilitare lo stream di profondità (selezionando la risoluzione desiderata);
  4. Agganciare il gestore di evento per manipolare i dati provenienti dall’evento DepthFrameReady;
  5. Avviare la ricezione dei dati con il metodo Start della classe KinectSensor.
  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3.     If Sensor.Status = KinectStatus.Connected Then
  4.         AddHandler Sensor.DepthFrameReady, AddressOf DepthFrameReadyHandler
  5.         Sensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30)
  6.         Try
  7.             Sensor.Start()
  8.         Catch ex As Exception
  9.  
  10.         End Try
  11.     End If
  12. End If
Cominciamo subito con il dire che, a differenza della versione Beta2, in cui potevamo scegliere se avere i dati di profondità solamente o anche i dati relativi al player rilevato (avendo una diversa disposizione dei bit all’interno dei bytes di profondità), nella versione 1.0, la struttura dei bit ritornati rimane sempre la stessa (cioè c’è sempre lo spazio per il player index – come mostrato in una figura in seguito) anche se il riconoscimento del player si ha solamente se viene attivato lo skeleton tracking (del quale ci occuperemo in un altro post).
Sono state introdotte, inoltre, due differenti possibilità di utilizzo riguardo la profondità: il “Default Mode” e il “Near Mode”.
La seguente figura mostra le distanze a cui lavora il dispositivo nelle due modalità:
Near Mode vs Default Mode Distances
In questa versione, l’SDK è in grado non solo di dirci la distanza (quando ci si trova nel range “Normal Values”) in termini di millimetri a partire dal sensore, ma anche quando il punto esaminato si trova in uno degli altri range (nella versione Beta2 l’SDK recuperava la distanza nel range corretto e basta).
La classe che contiene tutte le informazioni inerenti la lo stream di profondità è la DepthImageStream
image
La proprietà Range consente di impostare il tipo di modalità di funzionamento del Kinect tra Default e Near. Quando viene cambiata questa proprietà si ottengono, contestualmente i cambiamenti sulle proprietà MaxDepth e MinDepth che indicano i limiti del range rilevato dal sensore.
Le tre proprietà TooFarDepth, TooNearDepth e UnknownDepth forniscono i valori utilizzati dal sensore per identificare se un punto si trova, rispettivamente, troppo lontano (valore FFF esadecimale, 4095 in decimale), troppo vicino (valore 0) o sconosciuto (valore –1), secondo quanto mostrato dalla precedente figura.
Lo stream ci mette a disposizione anche dati sul campo visivo della camera di profondità:
  • NominalVerticalFieldOfView : angolo di visuale verticale, in gradi, della camera;
  • NominalHorizontalFieldOfView : angolo di visuale orizzontale, in gradi, della camera;
  • NominalDiagonalFieldOfView : angolo di visuale diagonale, in gradi, della camera.
Quando il sensore dispone di un frame di profondità, viene sollevato l’evento DepthFrameReady che possiamo gestire per recuperare effettivamente i bytes (due per ogni punto x,y)  che descrivono la scena.
  1. Dim depthArray = New Short(frame.PixelDataLength - 1) {}
  2. frame.CopyPixelDataTo(depthArray)
Per recuperare, dato un punto di coordinate (x,y), i valori di profondità e player index corrispondenti, possiamo utilizzare la seguente funzione:
  1. Private Sub GetDepthAndPlayerIndex(frame As DepthImageFrame,
  2.                                         x As Integer,y As Integer,
  3.                                         ByRef depth As Long,ByRef playerIndex As Short)
  4.     Dim arrayIndex = (y * frame.Width + x)
  5.     If arrayIndex >= frame.PixelDataLength Then Throw New ArgumentOutOfRangeException()
  6.  
  7.     Dim depthArray = New Short(frame.PixelDataLength - 1) {}
  8.     frame.CopyPixelDataTo(depthArray)
  9.  
  10.     Dim depthPoint = depthArray(arrayIndex))
  11.     depth = depthPoint >> DepthImageFrame.PlayerIndexBitmaskWidth
  12.     playerIndex = CShort(depthPoint And DepthImageFrame.PlayerIndexBitmask)
  13. End Sub
La variabile depthPoint contiene il valore effettivo letto dal sensore per il punto (x,y) e le due formule che restituiscono distanza e player index sono:
image
A differenza della Beta2 dell’SDK, i dati raw di profondità che arrivano dal sensore sono, punto per punto, su un solo elemento dell’array e non dobbiamo ricostruirli concatenando byte meno significativo a quello più significativo e, in più, troviamo le due costanti pubbliche PlayerIndexBitmaskWidth e PlayerIndexBitmask le quali ci dicono, rispettivamente, la lunghezza della maschera di bit del player index (3) e la vera e propria bitmask per estrarre il player index.
Importante novità dell’SDK, anticipata in precedenza, è che il player index viene valorizzato solo se è attivo lo skeletal tracking.
Per questo motivo, se vogliamo avere il player index dobbiamo instanziare il KinectSensor nel seguente modo.
  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3.     If Sensor.Status = KinectStatus.Connected Then
  4.         AddHandler Sensor.DepthFrameReady, AddressOf DepthFrameReadyHandler
  5.         Sensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30)
  6.         Sensor.SkeletonStream.Enable()
  7.         Try
  8.             Sensor.Start()
  9.         Catch ex As Exception
  10.  
  11.         End Try
  12.     End If
  13. End If
In allegato trovate una semplice solution in cui vengono utilizzati i concetti esposti.


lunedì 19 marzo 2012

Async : come trasformare un metodo non async in async

Windows 8, WinRT e le applicazioni Metro UI hanno portato una ventata di aria nuova nella progettazione delle interfacce, soprattutto per il fatto che tutte le operazioni la cui durata supera i 50 ms debbono essere asincrone (in realtà sarebbe stata buona norma anche prima, ma la legge è legge).

Il paradigma Async prevede due nuove parole chiave del linguaggio, Async e Await, il cui utilizzo congiunto permette di ottenere applicazioni completamente asincrone mantenendo un codice pulito e chiaro.

Vedremo in altri post (e, in ogni caso, internet è piena di fonti in da cui capire l’esatto funzionamento del meccanismo Async) come è implementato l’Async nel framework 4.5, ma in questo post ci chiediamo: “e se abbiamo già delle nostre classi con della logica (metodi) che potenzialmente possono durare più di 50ms?”………”Dobbiamo riscrivere tutto?”

Cerchiamo di vedere come si possono facilmente far diventare Async metodi che, in realtà, non lo sono.

Supponiamo di avere la nostra classe LegacyClass (frutto di duro ed estenuante lavoro ) con dei metodi di lunga durata:

  1. Public Class LegacyClass
  2.  
  3.     Public Function Method1(i As Integer) As Integer
  4.         Dim str As String = ""
  5.         For index = 1 To 100000
  6.             str += index.ToString()
  7.         Next
  8.         Return i
  9.     End Function
  10.  
  11.     Public Function Method2(s As String, i As Integer) As String
  12.         Dim str As String = ""
  13.         For index = 1 To 100000
  14.             str += index.ToString()
  15.         Next
  16.         Return s
  17.     End Function
  18.  
  19.     Public Sub Method3(s As String)
  20.         Dim str As String = ""
  21.         For index = 1 To 100000
  22.             str += index.ToString()
  23.         Next
  24.     End Sub
  25.  
  26. End Class

I tre metodi hanno una lunga durata e, quindi, nell’ottica Async, così come sono, non vanno bene.

Se abbiamo il classico pulsante nella nostra UI che deve richiamare i tre metodi in sequenza:

  1. Private Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
  2.     Dim lc = New LegacyClass
  3.     txt.Text = "Metodo1"
  4.     Dim i = lc.Method1(5)
  5.     txt.Text = "Metodo2"
  6.     Dim s = lc.Method2(i.ToString(), i)
  7.     txt.Text = "Metodo3"
  8.     lc.Method3(s)
  9.     txt.Text = "fine"
  10. End Sub

questi bloccheranno la UI rendendo la nostra applicazione non adatta ad un’applicazione “moderna”

Il trucco per farli diventare Async sta nel fatto di fare in modo che, tali metodi, restituiscano un Task(Of T) anziché il semplice T.

Ad esempio, dobbiamo fare in modo che il Method1 restituisca Task(Of Integer) anziché Integer.

In questo modo possiamo richiamare i metodi anteponendo la parola Await, il compilatore si occuperà della creazione della macchina a stati che sta dietro il meccanismo dell’Async e il metodo (richiamato con  Await davanti) ritorna di nuovo Integer (ma lo fa in maniera non bloccante).

Per ogni metodo della nostra LegacyClass possiamo creare la versione async nel seguente modo:

  1. Public Function Method1Async(i As Integer) As Task(Of Integer)
  2.     Dim tf = New TaskFactory(Of Integer)
  3.     Dim task = tf.StartNew(Function() Method1(i))
  4.     Return task
  5. End Function
  6.  
  7. Public Function Method2Async(s As String, i As Integer) As Task(Of String)
  8.     Dim tf = New TaskFactory(Of String)
  9.     Dim task = tf.StartNew(Function() Method2(s, i))
  10.     Return task
  11. End Function
  12.  
  13. Public Function Metodo3Async(s As String) As Task
  14.     Dim tf = New TaskFactory
  15.     Dim task = tf.StartNew(Sub() Method3(s))
  16.     Return task
  17. End Function

Method3Async restituisce un Task essendo una Sub e non ritornando alcun valore.

A questo punto il nostro gestore di evento del pulsante di cui sopra diventa:

  1. Private Async Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
  2.     Dim lc = New LegacyClass
  3.     txt.Text = "Metodo1"
  4.     Dim i = Await lc.Method1Async(5)
  5.     txt.Text = "Metodo2"
  6.     Dim s = Await lc.Method2Async(i.ToString(), i)
  7.     txt.Text = "Metodo3"
  8.     Await lc.Metodo3Async(s)
  9.     txt.Text = "fine"
  10. End Sub

In questo modo la UI non si blocca e i metodi sfruttano a pieno il meccanismo dell’Async.

Possiamo osservare che, di fatto, il codice presente all’interno delle versioni Async dei metodi, è sempre molto simile.

Razionalizzando il tutto, possiamo scrivere la seguente classe:

  1. Public NotInheritable Class AsyncHelper
  2.     Private Sub New()
  3.  
  4.     End Sub
  5.  
  6.     Public Shared Function CreateAsync(Of TRet)(func As Func(Of TRet)) As Task(Of TRet)
  7.         Dim tf = New TaskFactory(Of TRet)
  8.         Dim task = tf.StartNew(Function() func.Invoke())
  9.         Return task
  10.     End Function
  11.  
  12.     Public Shared Function CreateAsync(action As action) As Task
  13.         Dim tf = New TaskFactory
  14.         Dim task = tf.StartNew(Sub() action.Invoke())
  15.         Return task
  16.     End Function
  17. End Class

La classe ci permette di scrivere i metodi della nostra classe nel seguente modo:

  1. Public Function Method1Async(i As Integer) As Task(Of Integer)
  2.     Return AsyncHelper.CreateAsync(Of Integer)(Function() Method1(i))
  3. End Function
  4.  
  5. Public Function Method2Async(s As String, i As Integer) As Task(Of String)
  6.     Return AsyncHelper.CreateAsync(Of String)(Function() Method2(s, i))
  7. End Function
  8.  
  9. Public Function Metodo3Async(s As String) As Task
  10.     Return AsyncHelper.CreateAsync(Sub() Method3(s))
  11. End Function

L’utilizzo della AsyncHelper va adottato quando abbiamo del codice che non può essere convertito in asincrono utilizzando opportuni metodi (ad esempio il metodo GetResponseAsync della classe HttpWebRequest) come, ad esempio, quando si utilizzano librerie di terze parti che non hanno ancora metodi “awaitabili”.

In allegato trovate la soluzione con la classe mostrata in questo post.

 

venerdì 16 marzo 2012

Kinect a Codemotion

Vi segnalo che venerdì 23 Marzo alle ore 16:40 terrò una sessione a CodeMotion dal titolo “Kinect + .NET = NUI : Interfacce naturali facili con Kinect!!!”.

banner

L’evento è di prim’ordine (nonostante la mia presenza) e non potete perderlo!!!!

 

Tag di Technorati: ,,

lunedì 12 marzo 2012

Kinect: cosa cambia nella nuova SDK

Con questo post vorrei iniziare una serie dedicata alle novità e ai cambiamenti presenti nell’SDK rilasciato il 1 febbraio 2012.

Cominciamo con riportare il link del portale Kinect in cui trovare il pacchetto di installazione dell’SDK e le risorse necessarie per cominciare a lavorare con lo stesso.

In questi post prenderò spunto dagli esempi che avevo a suo tempo riportato in un’analoga serie di post (ai tempi della beta2) e proverò di farli funzionare nuovamente con il nuovo SDK analizzando ciò che è cambiato.

Installato l’SDK, abbiamo a disposizione un browser che ci permette di accedere facilmente alla documentazione e agli esempi.

SNAGHTML1cbd41a8

Prima cosa che balza all’occhio quando tentiamo di utilizzare l’assembly contenente le classi di gestione del Kinect è che l’assembly da referenziare non è più Microsoft.Research.Kinect.dll ma Microsoft.Kinect.dll.

In maniera analoga, il namespace principale delle classi dell’SDK non è più Microsoft.Research.Kinect ma Microsoft.Kinect.

La classe principale utilizzata nella Beta2 per accedere alle funzionalità del Kinect era la classe Runtime, nella versione 1.0 questa è completamente sparita ed è sparito anche il modo con cui si accede alla classe di gestione del Kinect.

La classe Runtime è stata sostituita dalla classe KInectSensor contenuta nel namespace Microsoft.Kinect.

Il seguente disegno mostra la struttura della classe KinectSensor:

KinectSensor class

La classe KinectSensor non ha costruttori ma espone una proprietà Shared chiamata KinectSensors che ci consente di avere a disposizione l’elenco dei sensori Kinect attaccati al nostro PC (fino a 4) e rilevati dal sistema. Altra cosa importante (di cui ci occuperemo in un prossimo post) è l’esistenza dell’evento AllFrameReady il cui scopo è quello di fornire dei frame sincronizzati per le sorgenti abilitate.

Possiamo, quindi, accedere al nostro Kinect nel seguente modo:

  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3. End If

dove Sensor è la variabile che utilizzeremo per accedere al dispositivo.

Una volta recuperata l’istanza della classe per la gestione del Kinect, dobbiamo sottoscrivere gli eventi da gestire per i frame che arrivano dal dispositivo e aprire gli opportuni stream.

Anche per questo, ci sono stati dei cambiamenti. In particolare, l’evento VideoFrameReady della vecchia Runtime, è stato rinominato in ColorFrameReady e l’attivazione degli stream non avviene più con il metodo Initialize (che apriva gli stream e avviava la gestione degli stessi) ma richiamando il metodo Enable (o Disable per disabilitare) delle proprietà ColorStream, DepthStream o SkeletonStream (in base a quale stream gestire) seguito dal metodo Start.

Il metodo Enable prevede, nel caso del ColorStream, la possibilità di passare come argomento la risoluzione delle immagini da recuperare. La presenza dell’Enable e del Disable fa si che si possano attivare e disattivare i flussi provenienti dal Kinect “a caldo” senza dover fermare l’istanza del KinectSensor e riavviarla.

Ad esempio per gestire una risoluzione video di 640x480 a 30 fps, possiamo scrivere:

  1. If KinectSensor.KinectSensors.Any() Then
  2.     Sensor = KinectSensor.KinectSensors(0)
  3.     If Sensor.Status = KinectStatus.Connected Then
  4.         AddHandler Sensor.ColorFrameReady, AddressOf ColorFrameReadyHandler
  5.         Sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30)
  6.         Try
  7.             Sensor.Start()
  8.         Catch ex As Exception
  9.  
  10.         End Try
  11.     End If
  12. End If

La proprietà Status della KinectSensor ci consente di sapere lo stato del sensore (nel nostro caso se è connesso) in modo da eseguire le operazioni di apertura degli stream e di aggancio degli eventi solo se effettivamente il sensore è connesso.

Anche la firma del gestore dell’evento ColorFrameReady è cambiata rispetto alla firma del precedente evento presente nella classe Runtime.

  1. Private Sub ColorFrameReadyHandler(sender As Object, e As ColorImageFrameReadyEventArgs)
  2.  
  3. End Sub

L’argomento ColorImageFrameReadyArgs permette di accedere alle informazioni relative al frame recuperato dal Kinect

image


Da notare la presenza del metodo OpenColorImageFrame per recuperare l’istanza della classe ColorImageFrame che è il vero contenitore dei dati provenienti dalla video camera del Kinect.



Per ottenere l’array di byte costituenti l’immagine effettiva è necessario definire un array di byte della lunghezza pari a PixelDataLenght e copiarvi dentro i dati con il metodo CopyPixelDataTo().







  1. Using colorImageFrame = e.OpenColorImageFrame()

  2.     If colorImageFrame IsNot Nothing Then

  3.         Dim imageArray() = New Byte(colorImageFrame.PixelDataLength - 1) {}

  4.         colorImageFrame.CopyPixelDataTo(imageArray)

  5.         '

  6.         ' Gestione dell'array dati proveniente dal Kinect

  7.         '

  8.     End If

  9. End Using






Dato l’array di byte possiamo creare una BitmapSource e possiamo visualizzare la stessa all’interno, ad esempio, di un controllo image WPF:







  1. Private Sub ColorFrameReadyHandler(sender As Object, e As ColorImageFrameReadyEventArgs)

  2.     Dim colorImageFrame = e.OpenColorImageFrame()

  3.     Dim imageArray(colorImageFrame.PixelDataLength) As Byte

  4.     colorImageFrame.CopyPixelDataTo(imageArray)

  5.     Dim stride = colorImageFrame.Width * colorImageFrame.BytesPerPixel

  6.     Me.VideoImage.Source = BitmapSource.Create(colorImageFrame.Width, colorImageFrame.Height,

  7.                                                96, 96,

  8.                                                PixelFormats.Bgr32, Nothing,

  9.                                                imageArray, stride)

  10. End Sub






Ogni volta che l’SDK avrà ricevuto un frame dalla video camera del dispositivo, richiamerà il nostro gestore di evento e visualizzeremo l’immagine corrispondente.



Infine, nel momento in cui dobbiamo chiudere la nostra applicazione e rilasciare la classe di gestione del Kinect, possiamo utilizzare il metodo Stop (per fermare il flusso degli stream e il Dispose per rilasciare effettivamente l’istanza:







  1. If Sensor IsNot Nothing Then

  2.     If Sensor.IsRunning Then

  3.         Sensor.Stop()

  4.         Sensor.Dispose()

  5.     End If

  6. End If






Prima di concludere questo primo post, un accenno allo stride di una immagine per capire meglio il codice precedente.



Quando memorizziamo una immagine in memoria, in realtà, questa è memorizzata in un buffer che potrebbe contenere dei byte in più rispetto a quelli necessari a memorizzare l’immagine stessa (vedi figura). Questi byte in più non influenzano la visualizzazione ma solo lo spazio occupato.



IC156482



Lo stride di un’immagine altro non è che la lunghezza di una riga della precedente matrice buffer. Nella fattispecie è data dalla larghezza del frame per il numero di byte per pixel.



In allegato trovate una solution di esempio: