Prérequis pour ce tutoriel :
- Kinect SDK doit être installé sur votre machine
- Coding4Fun Kinect Toolkit (la dll utilisée est fournie dans le code exemple a la fin de cet article).
- Kinect et un câble USB permettant de le connecter à un PC (seules les versions de Kinect achetée séparément de la console en ont un si ma mémoire est bonne. Si vous n’avez pas de câble Kinect/USB, vous devriez pouvoir en trouver sur le Microsoft Store).
- Créer un projet Windows Application WPF et ajouter les références Microsoft.Research.Kinect (dans GAC) et Coding4Fun.Kinect.Wpf.
Suite à la présentation rapide de Kinect et de son SDK, nous allons maintenant rentrer dans le vif du sujet en créant une application WPF dans lequel nous allons afficher le flux couleur et profondeur de Kinect.
Afin de permettre la réutilisation de ce code, nous allons utiliser une architecture MVVM composée d’un ViewModel pour la fenêtre et d’un « ViewModel » Kinect afin de pouvoir réutiliser la partie Kinect indépendamment dans d’autres projets.
Les fichiers du projet sont :
- MainWindow.xaml : Fichier xaml représentant la fenêtre.
- MainWindowViewModel.cs : ViewModel de la fenêtre MainWindow
- KinectVideoViewModel.cs : « ViewModel » pour acceder aux données Kinect. Une instance de cette classe est incluse dans MainWindowViewModel.
La fenêtre principale (MainWindow.xaml)
Screenshot de l’application finale
La fenêtre est une grille 2 lignes/2colonnes ou la première ligne contient les textes titres. La deuxième ligne contient des images contenant les flux vidéos (flux couleur à gauche et flux profondeur à droite).
Chacune des images voit leur source bindée sur des BitmapSource du KinectViewModel que nous verrons plus tard.
Le ViewModel est créé et lié au DataContext directement dans le fichier XAML.
1 <Window x:Class="KinectWpf.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:vm="clr-namespace:KinectWpf.ViewModel" 5 Title="Kinect Demo 1" Width="800" Height="600" > 6 <Window.DataContext> 7 <vm:MainWindowViewModel /> 8 </Window.DataContext> 9 <Grid> 10 <Grid.RowDefinitions> 11 <RowDefinition Height="Auto" /> 12 <RowDefinition Height="*" /> 13 </Grid.RowDefinitions> 14 <Grid.ColumnDefinitions> 15 <ColumnDefinition Width="*" /> 16 <ColumnDefinition Width="*" /> 17 </Grid.ColumnDefinitions> 18 <TextBlock Text="Color Stream" FontWeight="Bold" FontSize="15" VerticalAlignment="Center" Margin="5,0,0,0" /> 19 <TextBlock Text="Depth Stream" Grid.Column="1" FontWeight="Bold" FontSize="15" VerticalAlignment="Center" Margin="5,0,0,0" /> 20 <Image Grid.Row="1" Grid.Column="0" Source="{Binding Kinect.ColorStreamImageSource}" /> 21 <Image Grid.Row="1" Grid.Column="1" Source="{Binding Kinect.DepthStreamImageSource}"/> 22 </Grid> 23 </Window> 24
Le ViewModel de la fenêtre (MainWindowViewModel.cs)
1 public class MainWindowViewModel : INotifyPropertyChanged 2 { 3 public KinectViewModel _kinect; 4 public KinectViewModel Kinect 5 { 6 get { return _kinect; } 7 set 8 { 9 this._kinect = value; 10 OnPropertyChanged("Kinect"); 11 } 12 } 13 14 public MainWindowViewModel() 15 { 16 Application.Current.Exit += (sender, args) => { Clean(); }; 17 Kinect = new KinectViewModel(); 18 Kinect.Start(RuntimeOptions.UseColor | RuntimeOptions.UseDepthAndPlayerIndex); 19 } 20 21 public void Clean() 22 { 23 Kinect.Release(); 24 } 25 26 #region INotifyPropertyChanged Interface 27 28 public event PropertyChangedEventHandler PropertyChanged; 29 public void OnPropertyChanged(string name) 30 { 31 if (this.PropertyChanged != null) 32 this.PropertyChanged(this, new PropertyChangedEventArgs(name)); 33 } 34 35 #endregion 36 }
Comme la plupart des ViewModel, cette classe implémente INotifyPropertyChanged afin de pouvoir remonter les changements au moteur de binding de WPF. Nous ajoutons une propriété KinectViewModel afin qu’il soit accessible directement par binding à la vue MainWindow.
Deux méthodes du KinectViewModel seront utilisées ici. La première est la méthode Start qui est chargée d’initialiser et de lancer la capture des flux vidéos (les paramètres de cette méthode seront détaillés dans la suite). La seconde est la méthode Release() charge de libérer les ressources utilisant Kinect.
Le ViewModel Kinect (KinectViewModel.cs)
1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 private Runtime KinectRuntime; 4 5 public KinectViewModel(int id = 0) 6 { 7 if (Runtime.Kinects == null || Runtime.Kinects.Count <= id) 8 throw new InvalidOperationException("No kinect runtime found for id="+ id+"."); 9 this.KinectRuntime = Runtime.Kinects[id]; 10 } 11 [...] 12 }
Cette partie concerne l’initialisation du Runtime Kinect. La classe Runtime (provenant de Microsoft.Research.Kinect) référence, dans sa propriété Kinects sous la forme de Kinect[], l’ensemble des Kinects connectés à l’ordinateur. Notre ViewModel va permettre de sélectionner le Kinect souhaite grâce au paramètre id. Dans la plupart des cas, un seul Kinect est branché, on mettra une valeur par défaut id = 0. Une fois l’objet Kinect trouvé, on le conserve dans une variable KinectRuntime que nous utiliserons dorénavant.
1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 public void Start(RuntimeOptions options) 5 { 6 this.KinectRuntime.Initialize(options); 7 8 if ((options & RuntimeOptions.UseColor) == RuntimeOptions.UseColor) 9 InitColorStream(); 10 if ((options & RuntimeOptions.UseDepth) == RuntimeOptions.UseDepth) 11 InitDepthStream(); 12 if ((options & RuntimeOptions.UseDepthAndPlayerIndex) == RuntimeOptions.UseDepthAndPlayerIndex) 13 InitDepthAndPlayerIndexStream(); 14 if ((options & RuntimeOptions.UseSkeletalTracking) == RuntimeOptions.UseSkeletalTracking) 15 InitSkeletonTracking(); 16 } 17 [...] 18 }
Une fois le KinectVIewModel crée, il faut initialiser puis démarrer les flux vidéos. Pour cela, il est nécessaire de fournir à Kinect le type des flux que l’on souhaite utiliser. On utilise pour ça l’énumération RuntimeOptions définie par le SDK Kinect comme suit :
1 namespace Microsoft.Research.Kinect.Nui 2 { 3 [Flags] 4 public enum RuntimeOptions 5 { 6 UseDepthAndPlayerIndex = 1, 7 UseColor = 2, 8 UseSkeletalTracking = 8, 9 UseDepth = 32, 10 } 11 }
On initialise le Kinect en appelant la méthode Initialize tout en fournissant cette énumération (cette énumération ayant un attribut [Flag], il est possible de les combiner en utilisant les opérateurs binaires). La série de conditions qui suivent la méthode Initialize vont scannes ces RuntimeOptions afin de déterminer les types de flux voulus. Cela va nous permettre d’appeler des méthodes d’initialisation spécifiques à ces flux.
1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 #region Color Stream 5 6 public BitmapSource _colorStreamImageSource; 7 public BitmapSource ColorStreamImageSource 8 { 9 get { return _colorStreamImageSource; } 10 set 11 { 12 this._colorStreamImageSource = value; 13 OnPropertyChanged("ColorStreamImageSource"); 14 } 15 } 16 17 private void InitColorStream() 18 { 19 this.KinectRuntime.VideoFrameReady += KinectRuntime_VideoFrameReady; 20 this.KinectRuntime.VideoStream.Open(ImageStreamType.Video, 21 2, 22 ImageResolution.Resolution640x480, 23 ImageType.Color); 24 } 25 26 private void KinectRuntime_VideoFrameReady(object sender, ImageFrameReadyEventArgs e) 27 { 28 this.ColorStreamImageSource = e.ImageFrame.ToBitmapSource(); 29 } 30 31 #endregion 32 [...] 33 }
La partie gérant le flux vidéo couleur comprend 3 parties.
Une propriété bindable ColorStreamImageSource de type BitmapSource lie a un control Image de la MainWindow (<Image Grid.Row="1" Grid.Column="0" Source="{Binding Kinect.ColorStreamImageSource}" />).
Si la méthode Start, décrite ci-dessus, a une option RuntimeOptions.UseColor, alors la méthode InitColorStream ci-dessus sera appelée. Nous commençons par nous abonner à l’événement VideoFrameReady pour récupérer une image lorsque celle-ci est disponible. Une fois récupérer, il suffit de la passer a la méthode d’extension ToBitmapSource() (provenant du Coding4Fun Kinect Toolkit) pour créer le BitmapSource adéquat. Lorsque ce BitmapSource est assigné à la propriété ColorStreamImageSource, le databinding se déclenche pour mettre à jour l’image.
Le lancement du flux vidéo se fait grâce à la propriété VideoStream du KinectRuntime. En appelant la méthode Open avec les paramètres suivants, on lance la récupération des images.
Paramètres de la méthode Open()
Nom du parametre | Type | Description |
streamType | ImageStreamType | Trois valeurs possibles sur ImageStreamType (Invalid,Video,Depth). Ce paramètre décrit le type de flux recuperer. Dans notre cas, on utilise la valeur Video. |
poolSize | int |
Nombre d’images gardées en buffer par Kinect. 2 est une valeur correcte pour la plupart des applications. |
resolution | ImageResolution |
Résolution de l’image. Invalid Resolution320x240 : capteur de profondeur uniquement – 15 images/s. Resolution640x480 : caméra vidéo uniquement – 30 images/s. Resolution1280x1024 : caméra vidéo uniquement – 15 images/s – en réalité, il s’agit d’une résolution 1280×960. En cas de valeurs erronées, la méthode Open() remonte une Exception avec le HRESULT=0x80070057 |
image | ImageType |
Type d’images/formatage des pixels de l’image. DepthAndPlayerIndex : Formatage spécifique pour profondeur et player index (16 bits par pixel… 13 bits de profondeur/3 bits pour l’id du joueur. Nous en parlerons dans un prochain article.) ColorYuv : Flux video 32 bits en YUV (voir “YUV Video” sur la MSDN pour plus d’informations) ColorYuvRaw : Flux video 16 bits en YUV Depth : Formatage spécifique pour la profondeur (16 bits par pixel) |
1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 #region Depth/PLayerIndex Stream 5 6 public BitmapSource _depthStreamImageSource; 7 public BitmapSource DepthStreamImageSource 8 { 9 get { return _depthStreamImageSource; } 10 set 11 { 12 this._depthStreamImageSource = value; 13 OnPropertyChanged("DepthStreamImageSource"); 14 } 15 } 16 17 private void InitDepthStream() 18 { 19 this.KinectRuntime.DepthFrameReady += KinectRuntime_DepthFrameReady; 20 this.KinectRuntime.DepthStream.Open(ImageStreamType.Depth, 21 2, 22 ImageResolution.Resolution320x240, 23 ImageType.Depth); 24 } 25 26 private void InitDepthAndPlayerIndexStream() 27 { 28 this.KinectRuntime.DepthFrameReady += KinectRuntime_DepthFrameReady; 29 this.KinectRuntime.DepthStream.Open(ImageStreamType.Depth, 30 2, 31 ImageResolution.Resolution320x240, 32 ImageType.DepthAndPlayerIndex); 33 } 34 35 private void KinectRuntime_DepthFrameReady(object sender, ImageFrameReadyEventArgs e) 36 { 37 this.DepthStreamImageSource = e.ImageFrame.ToBitmapSource(); 38 } 39 40 #endregion 41 [...] 42 }
On réalise la même opération pour la profondeur que pour la vidéo couleur. La seule différence résulte dans le fait que le flux vidéo peut renvoyer juste la profondeur ou la profondeur + le player Index. Chacun des deux modes s’initialisant de façon différente, on utilise deux méthodes pour lancer la récupération des images. Les deux s’abonnent au même événements et l’image, dans les deux cas, sera récupéré dans la méthode KinectRuntime_DepthFrameReady.
1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 #region Skeleton tracking 5 6 private void InitSkeletonTracking() 7 { 8 throw new NotImplementedException(); 9 } 10 11 #endregion 12 13 #region Cleaning 14 15 public void Release() 16 { 17 this.KinectRuntime.Uninitialize(); 18 } 19 20 #endregion 21 22 #region INotifyPropertyChanged Interface 23 24 public event PropertyChangedEventHandler PropertyChanged; 25 public void OnPropertyChanged(string name) 26 { 27 if (this.PropertyChanged != null) 28 this.PropertyChanged(this, new PropertyChangedEventArgs(name)); 29 } 30 31 #endregion 32 33 }
La méthode InitSkeletonTracking() sera décrite dans un prochain article.
La méthode Release permet de libérer les ressources utilisées par Kinect. Pour cela, il est nécessaire, lors de la fermeture du programme ou lorsque l’on ne souhaite plus utiliser Kinect, il est nécessaire d’appeler la méthode Uninitialize() du KinectRuntime.
Conclusion
Vous savez maintenant comment afficher des flux vidéos couleur et profondeurs provenant de Kinect ainsi que détecter si des joueurs sont visibles (dans ce cas, ils apparaissent colorés dans l’image du capteur de profondeur).
D’autres articles sont prévus sur le traitement d’images ainsi que le tracking des joueurs.
Exemple de l’application de test Kinect finale avec vidéo couleur (haut gauche)/profondeur (bas gauche)/traitement d’images (Détecteur de contour Sobel en haut à droite)/tracking de joueurs en bas à droite).
Un grand merci aux deux modèles (Gilles Peron/@GillesPeron et Kim Macrez). On se souviendra de votre sacrifice .