La classe Runtime (del namespace Microsoft.Research.KInect.Nui e contenuta nell’assemlby Microsoft.Research.Kinect) è per la nostra avventura come lo specchio per Alice: la porta peser entrare in un altro mondo……quello del Kinect!!!
A parte gli scherzi, la classe Runtime è fondamentale perché espone tutte le funzionalità che possiamo utilizzare del Kinect per quel che riguarda la parte delle gesture.
Di fatto un’istanza della classe Runtime gestisce un device connesso e la sua struttura è mostrata nella seguente immagine:
Per poter accedere alle funzionalità del sensore video, di profondità e allo skeleton engine (il “robo” che fa si che il Kinect individui la struttura di un corpo umano che si agita come un forsennato davanti a lui) è sufficiente creare un’istanza della classe Runtime.
Ma procediamo con ordine, innanzitutto per avere visibilità sulla classe Runtime è necessario referenziare la libreria Microsoft.Research.Kinect e lo facciamo, come accade usualmente, utilizzando l’opzione di Visual Studio (menù Add Reference che si ottiene con il tasto destro):
A questo punto possiamo istanziare la classe:
- Dim nui = New Runtime
Istanziare la classe Runtime non è sufficiente per poter accedere ai sensori, è necessario inizializzare la classe con il metodo Initialize():
- Dim nui = New Runtime
- nui.Initialize(RuntimeOptions.UseColor or RuntimeOptions.UseDepth)
In questo caso ho richiesto alla Runtime l’utilizzo dello stream video e di quello della profondità.
Prima di procedere analizziamo a fondo cosa ci mette a disposizione la Runtime a livello di proprietà, metodi ed eventi.
Le proprietà della Runtime
- InstanceIndex : è un intero che identifica il numero di istanza della Runtime. Ogni device connesso al pc ha la sua Runtime e questo numero ci permette di identificare il singolo device;
- NuiCamera : è la proprietà che ci consente di accedere all’oggetto Camera per poter, ad esempio, modificare l’inclinazione;
- VideoStream : espone un’istanza di ImageStream che descive lo stream della video camera del Kinect. Vedremo in dettaglio in un altro post la classe ImageStream, per ora ci basti immaginarlo come uno stream che fornisce i fotogrammi rilevati dalla videocamera;
- DepthStream : anche in questa proprietà espone un’istanza di ImageStream ma questa volta riguarda le immagini di profondità, ovvero le immagini in cui il Kinect traduce la distanza degli oggetti da se stesso;
- SkeletonEngine : è la proprietà che espone il motore che gestisce il riconoscimento della figura umana (ce ne occuperemo in un altro post).
I metodi della Runtime
- Initialize : abbiamo già visto questo metodo ed ha lo scopo di “avviare” l’aggetto richiedendo le funzionalità di cui abbiamo bisogno. Il parametro che accetta come argomento è del tipo RuntimeOptions, un’enumerazione flag con i seguenti valori:
- Uninitialize : esegue lo shutdown dell’istanza di runtime. Una volta eseguito lo shutdown della Runtime, pur se l’oggetto managed non è Nothing, non è più possibile tentare di inizializzarlo in quanto viene rilasciata definitivamente tutta la parte unmanaged che c’è dietro. Se si prova si ottiene un’eccezione.
Gli eventi della Runtime
Gli eventi della classe Runtime sono i membri della classe che, di fatto, ci permettono un primo uso, anche se banale della stessa:
- VideoFrameReady : viene sollevato quando un’immagine dello stream video è pronta per essere utilizzata;
- DepthFrameReady : viene sollevato quando un’immagine dello stream di profondità è pronta per essere utilizzata;
- SkeletonFrameReady : viene sollevato quando un’immagine del riconoscimento della figura umana è stata elaborata dallo skeleton engine ed è pronta per essere utilizzata.
I primi due eventi hanno per argomento la classe ImageFrameReadyEventArgs, mentre il terzo ha come argomento la classe SkeletonFrameReadyEventArgs.
Vedremo in dettaglio la struttura della SkeletonFrameReadyEventArgs in un prossimo post, mentre la ImageFrameReadyEventArgs ha la seguente struttura:
Di fatto l’argomento dell’evento ci fornisce l’immagine dello stream corrispondente che possiamo elaborare a nostro piacimento.
Quindi, istanziamo la Runtime, la inizializziamo, gestiamo gli eventi di cui sopra e siamo in grado, almeno per ora di riportare a video ciò che il Kinect sta inquadrando. In realtà, tra l’inizializzazione della Runtime e la fruizione delle immagini manca uno step fondamentale ovvero l’apertura degli stream VideoStream e DepthStream che se non aperti non comincerebbero a produrre immagini.
Il metodo Open ha la seguente sintassi:
dove:
- streamType : ci consente di definire la tipologia di stream che vogliamo ricevere;
- poolSize : è il numero di frame che il runtime di NUI deve memorizzare. Il valore massimo è 4, nella maggior parte delle applicazioni è sufficiente 2;
- resolution : dichiara la risoluzione che si vuole per lo stream. Sono disponibili svariate risoluzioni ma se si hanno più stream aperti (ad esempio video e depth) non tutte le combinazioni funzionano;
- image : definisce la tipologia dell’immagine. Ad esempio DepthAndPlayerIndex cioè un’immagine che contiene al suo interno i dati di distanza e del numero del giocatore.
Andremo in dettaglio dello stream di distanza in un altro post prossimamente.
Nell’esempio allegato a questo post trovate una banale applicazione WPF, sviluppata con il pattern MVVM, che visualizza i due stream illustrati in precedenza.
La creazione del ViewModel relativo alla finestra principale è la seguente:
- Public Sub New()
- If Not DesignerProperties.GetIsInDesignMode(New DependencyObject()) Then
- Nui = New Runtime
- AddHandler Nui.DepthFrameReady, AddressOf DepthFrameHandler
- AddHandler Nui.VideoFrameReady, AddressOf VideoFrameHandler
- Nui.Initialize(RuntimeOptions.UseColor Or RuntimeOptions.UseDepthAndPlayerIndex)
- Nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution640x480, ImageType.DepthAndPlayerIndex)
- Nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color)
- End If
- End Sub
dove Nui è un attributo privato del ViewModel di tipo Runtime.
Per la gestione delle immagini provenienti dai due stream mi sono affidato ad un toolkit sviluppato da Coding4Fun che fornisce degli extension methods per la conversione tra immagini Kinect e immagini WPF. Il toolkit si chiama “Kinect toolkit for WPF” (ne esiste una versione anche per Windows Form) e si può referenziare nel nostro progetto tramite NuGet:
In questo caso il ViewModel espone le ultime due immagini dei due stream che possono essere messe in binding con un normale controllo Image dell’interfaccia:
- Imports System.ComponentModel
- Imports Microsoft.Research.Kinect.Nui
- Imports Coding4Fun.Kinect.Wpf
- Public Class MainWindowViewModel
- Implements INotifyPropertyChanged
- Private Nui As Runtime
- Public Sub New()
- If Not DesignerProperties.GetIsInDesignMode(New DependencyObject()) Then
- Nui = New Runtime
- AddHandler Nui.DepthFrameReady, AddressOf DepthFrameHandler
- AddHandler Nui.VideoFrameReady, AddressOf VideoFrameHandler
- Nui.Initialize(RuntimeOptions.UseColor Or RuntimeOptions.UseDepth)
- Nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color)
- Nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.Depth)
- End If
- End Sub
- Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
- Protected Sub OnNotifyPropertyChanged(propertyName As String)
- RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
- End Sub
- Private _VideoImage As ImageSource
- Public Property VideoImage As ImageSource
- Get
- Return _VideoImage
- End Get
- Protected Set(value As ImageSource)
- Me._VideoImage = value
- OnNotifyPropertyChanged("VideoImage")
- End Set
- End Property
- Private _DepthImage As ImageSource
- Public Property DepthImage As ImageSource
- Get
- Return _DepthImage
- End Get
- Protected Set(value As ImageSource)
- Me._DepthImage = value
- OnNotifyPropertyChanged("DepthImage")
- End Set
- End Property
- Private Sub DepthFrameHandler(sender As Object, e As ImageFrameReadyEventArgs)
- Me.DepthImage = e.ImageFrame.ToBitmapSource()
- End Sub
- Private Sub VideoFrameHandler(sender As Object, e As ImageFrameReadyEventArgs)
- Me.VideoImage = e.ImageFrame.ToBitmapSource()
- End Sub
- Protected Overrides Sub Finalize()
- MyBase.Finalize()
- Nui.Uninitialize()
- End Sub
- End Class
Commenti