www.bewise.fr

Recherche

Hand Tracking avec Kinect et XNA

Par Adrien Debesson, posté le 14/05/2012

Profil : Développeur | Niveau : Découverte (100)

Tags : C#, XNA, Kinect | Partager : Partager sur Delicious Partager sur Facebook Partager sur Twitter

Afin de faire nos premières armes avec le capteur Kinect de Microsoft, nous allons réaliser une petite démo affichant l'image filmée avec la main de l'utilisateur marquée.

Comprendre Kinect

Ce que l'on appelle Kinect est en réalité l'agrégat de deux technologies :

· Le « Kinect Sensor », une webcam agrémentée de deux caméras infrarouges, d'un micro et d'un socle motorisé.

· La « NUI API », partie applicative qui nous permet de traiter les informations fournies par le capteur.

KinectSDKArchitecture

Créer le Projet

Kinect peut être utilisé avec un vaste panel de technologies. Pour cette démo, nous utiliserons le framework XNA pour sa simplicité.

On va utiliser le template Windows Game fournis avec XNA :

clip_image001

Pour utiliser le SDK Kinect, il suffit d'ajouter une référence à Microsoft.Kinect :

image

 

Initialiser le capteur

Il est possible de connecter et d'utiliser plusieurs capteurs Kinect sur une même machine. Chaque capteur sera géré grâce à une instance de la classe KinectSensor :

   1: KinectSensor sensor = KinectSensor.KinectSensors[0];

Les flux d'images renvoyés par la webcam (ColorStream) et les caméras IR (DepthStream) sont disponibles dans plusieurs formats à plusieurs fréquences.

Dans notre cas nous allons utiliser du 640 x 480 à 30 images par seconde car il s'agit du seul format partagé par les deux types de flux :

   1: sensor.DepthStream.Enable ( DepthImageFormat.Resolution640x480Fps30 );
   2: sensor.ColorStream.Enable ( ColorImageFormat.RgbResolution640x480Fps30 );

Nous allons également activer le tracking du squelette de l'utilisateur :

   1: sensor.SkeletonStream.Enable ();

Et démarrer le capteur :

   1: sensor.Start ();

Récupérer les frames

Notre capteur va maintenant nous envoyer des images 30 fois par secondes. Comme nous avons spécifié la même fréquence pour les deux flux, elles arriveront en même temps et nous pouvons donc les récupérer en même temps en nous abonnant à l'évènement AllFramesReady.

   1: sensor.AllFramesReady += new EventHandler ( OnFramesReady );
   1: private void OnFramesReady ( object sender , AllFramesReadyEventArgs e )
   2: {
   3:     // Get the frames from Kinect
   4:     ColorImageFrame imageFrame = e.OpenColorImageFrame ();
   5:     SkeletonFrame skeletonFrame = e.OpenSkeletonFrame ();
   6:     DepthImageFrame depthFrame = e.OpenDepthImageFrame ();
   7:     if ( ( imageFrame != null ) && ( skeletonFrame != null ) && ( depthFrame != null ) )
   8:     {
   9:     // A venir.
  10:     }
  11:  
  12: // Dispose of the frames
  13:     if ( imageFrame != null ) imageFrame.Dispose ();
  14:     if ( skeletonFrame != null ) skeletonFrame.Dispose ();
  15:     if ( depthFrame != null ) depthFrame.Dispose ();
  16: }

Ces frames ne sont pas directement exploitables, il va falloir les copier dans nos propres tableaux :

   1: private byte[] _pixelData = null;
   2: private short[] _depthData = null;
   3: private Skeleton[] _skeletons = new Skeleton[6];

On va ensuite s'assurer qu'ils soient bien alloués lors de la réception des frames et copier les données :

   1: // Allocate the buffers
   2: if ( _pixelData == null )
   3: {
   4:     _pixelData = new byte[imageFrame.PixelDataLength];
   5:     _depthData = new short[depthFrame.PixelDataLength];
   6: }
   7:  
   8: // Retreive the data
   9: imageFrame.CopyPixelDataTo ( _pixelData );
  10: depthFrame.CopyPixelDataTo ( _depthData );
  11: skeletonFrame.CopySkeletonDataTo ( _skeletons );

Afficher l'image de fond

Ces données ne nous sont que de peu d'utilité si on ne les exploite pas. On va commencer par afficher l'image du flux couleur dans notre application.

Pour cela, on va avoir besoin de deux objets : un buffer contenant des pixels et une texture.

   1: private Texture2D _cameraTexture = null;
   2: private Color[] _colorData = new Color[640 * 480];

On instancie la texture dans LoadContent :

   1: _cameraTexture = new Texture2D ( GraphicsDevice , 640 , 480 , false , SurfaceFormat.Color );

Le capteur Kinect nous envoie le flux d'image au format BGR, il va falloir le convertir au format RGBA utilisé par XNA :

   1: // Convert the color data to the proper format
   2: for ( int i = 0 ; i < ( 640 * 480 ) ; ++i )
   3: {
   4:     int b = _pixelData[( i * 4 )];
   5:     int g = _pixelData[( i * 4 ) + 1];
   6:     int r = _pixelData[( i * 4 ) + 2];
   7:     _colorData[i] = new Color ( r , g , b , 255 );
   8: }
   9:  
  10: // Copy it inside the texture
  11: _cameraTexture.SetData ( _colorData );

Enfin, dans la méthode Draw nous allons simplement afficher la texture de fond :

   1: spriteBatch.Begin ();
   2: spriteBatch.Draw ( _cameraTexture , Vector2.Zero , Color.White );
   3: spriteBatch.End ();
   4:  
   5: graphics.GraphicsDevice.Textures[0] = null;

La dernière ligne s'assure que la texture n'est plus associée avec le GraphicsDevice au moment de modifier son contenu, autrement nous aurions une exception.

Skeleton Tracking

Kinect étant bien plus qu'une webcam, voyons comment récupérer la position de la main droite de l'utilisateur.

L'API NUI est chargée de reconstituer le squelette des utilisateurs en fonction du flux de profondeur fournis par les deux caméras IR.

   1: Skeleton skeleton = ( from s in _Skeletons where s.TrackingState == SkeletonTrackingState.Tracked select s ).FirstOrDefault ();
   2: if ( skeleton != null )
   3: {
   4:     Joint joint = skeleton.Joints[JointType.HandRight];                
   5: }

Notre application ne prévoit qu'un seul utilisateur, mais il est possible de traquer jusqu'à 6 squelettes avec un seul capteur Kinect.

Chaque squelette est composé de plusieurs articulations (joints) identifiées comme suit :

KinectJoints

Dans notre cas, nous récupérons la position de la main droite de l'utilisateur. Toutefois cette position est tridimensionnelle et relative au squelette. Si on veut marquer l'emplacement de la main droite sur l'image filmée, il va falloir faire correspondre les deux :

   1: ColorImagePoint pt = sensor.MapSkeletonPointToColor ( joint.Position , imageFrame.Format );
   2: _handPosition = new Vector2 ( pt.X , pt.Y );

Cette position 2D correspond à la position de la main droite sur l'image récupérée par le capteur. Pour vérifier que cela fonctionne, on va dessiner un carré rouge à cet endroit.

Dans LoadContent, on va créer une texture d'un pixel :

   1: _pixelTexture = new Texture2D ( GraphicsDevice , 1 , 1 , false , SurfaceFormat.Color );
   2: _pixelTexture.SetData ( new Color[] { Color.White } );

Et dans Draw, on va dessiner notre carré :

   1: spriteBatch.Draw ( _pixelTexture , new Rectangle ( (int)_handPosition.X - 8 , (int)_handPosition.Y - 8 , 16 , 16 ) , Color.Red );

On peut désormais tester le résultat :

KinectDemo

Vous savez désormais repérer la position de la main de l'utilisateur qui peut servir de curseur dans vos applications. Vous pouvez également modifier le code de l'application pour vous entrainer et suivre plusieurs parties du corps voir même plusieurs utilisateurs.

Télécharger les sources de la démo


Commentaires