lunedì 1 agosto 2011

Alla scoperta del Kinect: la classe Runtime

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:

image

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

SNAGHTML513572e

SNAGHTML515b803

A questo punto possiamo istanziare la classe:

  1. Dim nui = New Runtime

Istanziare la classe Runtime non è sufficiente per poter accedere ai sensori, è necessario inizializzare la classe con il metodo Initialize():

  1. Dim nui = New Runtime
  2. 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:

image

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

image

 

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:

image

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:

  1. Public Sub New()
  2.     If Not DesignerProperties.GetIsInDesignMode(New DependencyObject()) Then
  3.         Nui = New Runtime
  4.  
  5.         AddHandler Nui.DepthFrameReady, AddressOf DepthFrameHandler
  6.         AddHandler Nui.VideoFrameReady, AddressOf VideoFrameHandler
  7.  
  8.         Nui.Initialize(RuntimeOptions.UseColor Or RuntimeOptions.UseDepthAndPlayerIndex)
  9.  
  10.         Nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution640x480, ImageType.DepthAndPlayerIndex)
  11.         Nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color)
  12.     End If
  13. 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:

SNAGHTML5752519

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:

  1. Imports System.ComponentModel
  2. Imports Microsoft.Research.Kinect.Nui
  3. Imports Coding4Fun.Kinect.Wpf
  4.  
  5. Public Class MainWindowViewModel
  6.     Implements INotifyPropertyChanged
  7.  
  8.     Private Nui As Runtime
  9.  
  10.     Public Sub New()
  11.         If Not DesignerProperties.GetIsInDesignMode(New DependencyObject()) Then
  12.             Nui = New Runtime
  13.  
  14.             AddHandler Nui.DepthFrameReady, AddressOf DepthFrameHandler
  15.             AddHandler Nui.VideoFrameReady, AddressOf VideoFrameHandler
  16.  
  17.             Nui.Initialize(RuntimeOptions.UseColor Or RuntimeOptions.UseDepth)
  18.  
  19.             Nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color)
  20.             Nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.Depth)
  21.  
  22.         End If
  23.     End Sub
  24.  
  25.     Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
  26.  
  27.     Protected Sub OnNotifyPropertyChanged(propertyName As String)
  28.         RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
  29.     End Sub
  30.  
  31.     Private _VideoImage As ImageSource
  32.     Public Property VideoImage As ImageSource
  33.         Get
  34.             Return _VideoImage
  35.         End Get
  36.         Protected Set(value As ImageSource)
  37.             Me._VideoImage = value
  38.             OnNotifyPropertyChanged("VideoImage")
  39.         End Set
  40.     End Property
  41.  
  42.     Private _DepthImage As ImageSource
  43.     Public Property DepthImage As ImageSource
  44.         Get
  45.             Return _DepthImage
  46.         End Get
  47.         Protected Set(value As ImageSource)
  48.             Me._DepthImage = value
  49.             OnNotifyPropertyChanged("DepthImage")
  50.         End Set
  51.     End Property
  52.  
  53.     Private Sub DepthFrameHandler(sender As Object, e As ImageFrameReadyEventArgs)
  54.         Me.DepthImage = e.ImageFrame.ToBitmapSource()
  55.     End Sub
  56.  
  57.     Private Sub VideoFrameHandler(sender As Object, e As ImageFrameReadyEventArgs)
  58.         Me.VideoImage = e.ImageFrame.ToBitmapSource()
  59.     End Sub
  60.  
  61.  
  62.  
  63.     Protected Overrides Sub Finalize()
  64.         MyBase.Finalize()
  65.         Nui.Uninitialize()
  66.     End Sub
  67. End Class

Nessun commento: