Marc Silanus

Kinect Microsoft dans un projet Windows Form C#

Posted on 10 février 2017

L'utilisation de la Kinect de Microsoft dans un projet nécessitant une localisation dans l'espace d'une personne peut s'avérer très utile. Microsoft met a disposition des développeur un SDK avec plusieurs exemples de programmes dans différents langages. Toutefois, aucun exemple de programmation à partir d'un projet Windows Form n'est fourni. C'est pourtant ce type de projet que l'on va privilégier au niveau terminal S-SSI ou STI2D SIN.

Cet article est un tutoriel de mise en œuvre de la Kinect dans un projet Windows Form en C# avec Visual Studio.

L'intérêt de la Kinect en robotique est évident. Elle permet de doter vos robots d'une capacité de reconnaissance de la présente d'un humain dans son environnement, d'interpréter ses gestes et bientôt, d'interpréter son expression faciale et de faire de la reconnaissance vocale. Le capteur Microsoft Kinect est donc un capteur de choix pour vos robots.

Génération Robot

Installation des pilote et du SDK

Suivant la version de votre Kinect, il faut télécharger et installer le bon SDK (il contient les pilotes) et les outils de développement (Developper Toolkit). Pour moi, il s'agit de la version 1.8 (Kinect model 1414).

Télécharger puis installer le SDK, puis le Developper Toolkit sans Kinect connectée.

Connexion de la Kinect

  • Connecter la Kinect sur un port USB2 ou 3.
    Attention : tous les contrôleurs USB ne semblent pas être compatibles avec la Kinect. Sur un portable Lenovo ThinkPad W530, je n'ai jamais réussi à installer les drivers officiels Microsoft.
  • Laisser faire l'installation des pilotes.
  • Ouvrir Developper Toolkit Browser et exécuter Kinect Explorer-WPF situé dans le menu Tools.
  • Si tout c'est bien passé, on doit voir quelque chose comme :

Démarrer un projet Windows Form

  • Dans Visual Studio, démarrer un nouveau projet Windows Form appeler testKinect1
  • Pour utiliser la Kinect dans notre programme, nous devons lui inclure la référence à son SDK. Dans l'explorateur de solutions :
    • Clic droit sur Références.
    • Ajouter une référence...
    • Sélectionner Extensions puis sélectionner Microsoft.Kinect

  • Pour simplifier l'écriture du code faisant appel aux services de cette extension, nous allons ajouter l'espace de nom correspondant :
using Microsoft.Kinect;

 Vérifier la présence d'une Kinect

Cette vérification devrait être faite lors du chargement du formulaire :

  • Double-clic sur le formulaire pour générer l’événement   private void Form1_Load(object sender, EventArgs e)
  • Un peu plus haut dans le code, nous allons déclarer un objet de la classe KinectSensor :
private KinectSensor sensor;
  • Il ne reste plus qu'à tester si une Kinect est bien connectée à l'ordinateur. Dans ce cas, on la démarre sinon, on affiche un message d'erreur et on quitte le programme :
try
{
   sensor = KinectSensor.KinectSensors[0];  //Une seule Kinect connectée
   sensor.Start();
   MessageBox.Show("Kinect Démarrée !");
}
catch
{
   MessageBox.Show("Pas de kinect Détectée !");
   Application.Exit();
}

Obtenir les informations d'un squelette

La Kinect est capable de reconnaître les joueurs qui interagissent avec celle-ci. Le processus consiste à identifier le squelette du joueur qui sera fourni sous forme d'une collection de points 3D avec un index pour identifier le joueur (1 à 6).

Chaque squelette est composé de jointures :  HipCenterShoulderCenter, Spine, Head, ShoulderLeft, ShoulderRight, ElbowLeft, ElbowRightWristLeftWristRight, HandLeftHandRightHipLeftHipRightKneeLeftKneeRightAnkleLeftAnkleRight, FootLeft et FootRight.

 

L’événement permettant de récupérer les données reliées aux squelettes est SkeletetonFrameReadyEventArgs. Cet événement retourne un objet de type SkeletonFrame avec la méthode OpenSkeletonFrame.

Une fois la kinect démarrer, il faut activer la lecture des squelettes et créer le gestionnaire d'événement.

MessageBox.Show("Kinect Démarrée !");
sensor.SkeletonStream.Enable();
sensor.SkeletonFrameReady += SensorSkeletonFrameReady; //gestionnaire d'événement appelé 30x par seconde !

Il ne reste plus qu'a écrire le gestionnaire de l'événement, par exemple pour suivre le déplacement de la main droite du squelette détecté :

private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
    Skeleton[] skeletons = new Skeleton[0];

    using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
    {
        if (skeletonFrame != null)
        {
            skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
            skeletonFrame.CopySkeletonDataTo(skeletons);
        }

        foreach (Skeleton skeleton in skeletons)
        {
            if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
            {
                 Joint rightHand = skeleton.Joints[JointType.HandRight];
                 float rightX = rightHand.Position.X;
                 float rightY = rightHand.Position.Y;
                 float rightZ = rightHand.Position.Z;

                 lblX.Text = "X="+rightX.ToString();
                 lblY.Text = "Y="+rightY.ToString();
                 lblZ.Text = "Z="+rightZ.ToString();
             }
         }
    }
}

 Afficher le flux  vidéo

La kinect dispose d'une caméra RGB dont allons maintenant récupérer et afficher l'image.

Plusieurs solutions sont possibles. En voici une décrite ici en français : https://devonkinect.wordpress.com/2012/02/07/comment-afficher-la-video-de-kinect/

En voici une autre dont le principe est de lire les données couleurs issues de la Kinect pour créer une image bitmap qui sera afficher dans un composant pictureBox.

  • Ajouter un composant pictureBox sur le formulaire et le renommer pbKinectVideo
  • Dans le code, ajouter les espaces de nom suivant :
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
  • Ajouter les attributs privés suivant à la classe du formulaire (normalement Form1) :
private KinectSensor sensor;
private byte[] colorData = null;
private IntPtr colorPtr;
private Bitmap kinectVideoBitmap = null;
  • Dans le gestionnaire d'événement Form1_Load(), ajouter l'activation de la lecture du flux vidéo après l'activation de la détection des squelettes :
// enable color video stream
sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
sensor.ColorFrameReady += SensorColorFrameReady;
  • Ecrire le gestionnaire d'événement de disponibilité d'une nouvelle image couleur disponible SensorColorFrameReady() :
private void SensorColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
   using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
   {
      if (colorFrame == null) return;
      if (colorData == null) colorData = new byte[colorFrame.PixelDataLength];
      colorFrame.CopyPixelDataTo(colorData);

      Marshal.FreeHGlobal(colorPtr);
      colorPtr = Marshal.AllocHGlobal(colorData.Length);
      Marshal.Copy(colorData, 0, colorPtr, colorData.Length);

      kinectVideoBitmap = new Bitmap(colorFrame.Width,
                                     colorFrame.Height,
                                     colorFrame.Width * colorFrame.BytesPerPixel,
                                     PixelFormat.Format32bppRgb,
                                     colorPtr);

      pbKinectVideo.Image = kinectVideoBitmap;
   }
}

Matérialiser la jointure traquée

Le but ici est de matérialiser la jointure traquée (la main droite) à l'aide d'un cercle bleu. Il faut pour cela ajouter un élément de graphique à l'image affichée. l'idée consiste donc à créer un graphique contenant l'image issue de la Kinect et le dessin d'une ellipse contenue dans un rectangle centré sur la main droite de la personne dont le squelette est traqué.

  • Ajouter sur le formulaire les composants suivant et les nommer comme indiqué ci-dessous :

  • Modifier la propriété Visible du composant lblHorsChamps à false pour le rendre invisible.
  • Dans le code, déclarer un nouvel attribut privé à la classe du formulaire pour créer un objet de la classe Graphics.
private Graphics g;
  • En utilisant g dans le pictureBox pbKinectVideo, on essaye de faire :
    • Dessiner l'image dans le Graphics g
    • Si la checkBox cbTracking est cochée, dessiner une ellipse contenue dans un carré de 20x20 pixels et centrée sur les coordonnées X et Y de la main droite traquée :
//pbKinectVideo.Image = kinectVideoBitmap;
 
try
{
   using (g = pbKinectVideo.CreateGraphics())
   {
       g.DrawEllipse(p, 320*(1+rightX)-5, 240*(1-rightY)-5, 20, 20);
       if(cbTracking.Checked)
       {
           Pen p = new Pen(Color.Blue, 10.0f);
           g.DrawEllipse(p, rightX - 5, rightY - 5, 20, 20);
       }
   }
}
catch { }

Les coordonnées rightX et rightY on été déclarées dans le gestionnaire d'événement SensorSkeletonFrameReady() qui assure le tracking des squelettes. Ils ont donc une portée limitée à cette fonction. Il faut les rendre globaux. Nous allons les déclarer comme attributs privés du formulaire :

  • Ajouter les attributs privés rightX et rightY :
private float rightX;
private float rightY;
  • Modifier les déclarations de rightX et rightY dans le gestionnaire dévénement SensorSkeletonFrameReady()  :
//float rightX = rightHand.Position.X;
rightX = rightHand.Position.X; //Supprimer le type float
//float rightY = rightHand.Position.Y;
rightY = rightHand.Position.Y; //Supprimer le type float
  • Tester le programme. Visiblement, le cercle bleu n'est pas vraiment centré sur la main droite ! Il y a de toute évidence un problème de coordonnées rightX et rightY. En effet, les données brutes ne sont pas exploitable directement. Il faut tenir compte des dimensions de l'image (640x480) et de la profondeur. Dans le calcul suivant :
    • 320*(1+rightX)-5
    • 240*(1-rightY)-5

On tient compte du fait qu'au centre de l'image, on a rightX=0 et rightY=0 et que l'image fait 640 pixel en largeur et 480 pixel en hauteur. Mais on ne tiens pas compte de la profondeur. on peut aisément le remarquer en avançant ou reculant devant la Kinect. Les effets sont encore plus flagrants sur les bords de l'images.

Détection de la profondeur - correction du tracking

La Kinect se compose d'un émetteur laser infrarouge, d’une caméra infrarouge et d’une caméra RGB. La mesure de la profondeur se fait par un processus de triangulation. Pour en savoir plus sur son fonctionnement, lire le rapport de projet bibliographique de Bertrand PECUCHET.

Chaque trame du flux de données du capteur de profondeur est composée de pixels qui contiennent la distance (en millimètres) du plan de caméra à l'objet le plus proche. Pour commencer il faut activer le flux de données du capteur de profondeur, puis obtenir les coordonnées X et Y corrigées de la jointure souhaitée.

  • Activer le flux de données du capteur de profondeur :
// enable depth stream
sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
  • Pour afficher les coordonnées brutes et corrigées, ajouter les attributs privés qui recueilleront les coordonnées brutes :
private float JointRightX;
private float JointRightY;
private float JointRightZ;
  • Pour obtenir un point (X,Y) des coordonnées corrigées de la main droite. A jouter dans le gestionnaire d'événement de tracking des squelettes :
DepthImagePoint depthPoint;
depthPoint = sensor.CoordinateMapper.MapSkeletonPointToDepthPoint(rightHand.Position, DepthImageFormat.Resolution640x480Fps30);
  • Obtenir et afficher les coordonnées brutes et corrigées de la main droite.
JointRightX = rightHand.Position.X;
JointRightY = rightHand.Position.Y;
JointRightZ = rightHand.Position.Z;

rightX = depthPoint.X;
rightY = depthPoint.Y;
                      
lblX.Text = "X=" + JointRightX.ToString();
lblY.Text = "Y=" + JointRightY.ToString();
lblZ.Text = "Z=" + rightZ.ToString();

lblXcorr.Text = "X=" + rightX.ToString();
lblYcorr.Text = "Y=" + rightY.ToString();
  • Corriger la position du cercle qui matérialise le tracking :
g.DrawEllipse(p, rightX - 5, rightY - 5, 20, 20);
  • Détecter la sortie de la main du champs de vision de la caméra revient à s'assurer que ses coordonnées X et Y sont positives et comprises dans le format de l'image (640x480) :
if (rightX < 0 || rightX > 640 
 || rightY < 0 || rightY > 480) 
   lblHorsChamps.Visible = true;
else lblHorsChamps.Visible = false;

Le code sur github

Ici : https://github.com/msilanus/testKinect

Conclusion

Voila de quoi démarrer de nombreux projets nécessitant la détection et/ou la localisation d'un humain dans l'espace, mais aussi la reconnaissance faciale, la reconnaissance vocale et la reconnaissance de gestes (gesture).

Pour en savoir plus : https://msdn.microsoft.com/en-us/library/hh855347.aspx

 

Remplis sous: Divers Commentaires
Commentaires (0) Trackbacks (0)

Désolé, le formulaire de commentaire est fermé pour le moment

Trackbacks are disabled.