Comment développer avec le SDK Kinect en C++ Part III

 

Télécharger le code source.

Dans mes deux précédant billets,

Comment développer avec le SDK Kinect en C++ Part I

Comment développer avec le SDK Kinect en C++ Part II

Nous avons vu comment capturer et afficher les flux images et profondeurs de la Kinect.

Dans ce billet, nous allons voir comment capturer et traquer les informations du squelette de la Kinect.

La kinect est capable de traquer 20 Points du corps, comme illustré sur l’image suivante

clip_image002

Pour pouvoir traquer ces points, il faut :

  • Préciser le drapeau NUI_INITIALIZE_FLAG_USES_SKELETON lors de l’initialisation du runtime de la Kinect lors de l’appel à la méthode NuiInitialize (comme vu dans le billet Comment développer avec SDK Kinect en C++ Part I)
  • Déclarer et créer un manual reset event, qui servira au runtime Kinect pour nous signaler la présence d’une nouvelle trame que nous utiliserons par la suite en conjonction avec l’API WaitForSingleObject
    HANDLE _skeleton;
    _skeleton = CreateEvent( NULL, TRUE, FALSE, NULL );
  • Ensuite nous démarrons la détection du Squelette avec l’API NuiSkeletonTrackingEnable, en lui passant comme paramètre le HANDLE de l’évènement précédemment crée.
    hr = NuiSkeletonTrackingEnable( _skeleton, 0 );
    Le second paramètre est ignoré dans la version actuelle du runtime
    Pour désactiver la détection du squelette, il suffit d’utiliser l’API NuiSkeletonTrackingDisable

Pour capturer une trame du squelette, nous utiliserons l’API NuiSkeletonGetNextFrame en mode polling dans une boucle de la manière suivante :

  • NuiSkeletonGetNexFrame, prend un pointeur sur la structure NUI_SKELETON_FRAME, structure qui contiendra les données de la trame envoyée par Kinect.
    C’est à partir de cette structure que nous allons pouvoir déterminer si un ou plusieurs squelettes ont été détectés comme nous le verrons par la suite.
    NUI_SKELETON_FRAME SkeletonFrame;
    hr = NuiSkeletonGetNextFrame( 0, &SkeletonFrame );
    Le 1er paramètre est un délai d’attente exprimé en milliseconde avant le retour sans trame de la fonction.

  • Ce qui nous intéresse dans notre exemple avec la structure NUI_SKELETON_FRAME, c’est qu’elle contient un tableau de structures nommé SkeletonData de NUI_SKELETON_DATA réglé au nombre de NUI_SKELETON_COUNT (Constante définie à 6 squelettes).
    typedef struct _NUI_SKELETON_FRAME
    {
    LARGE_INTEGER liTimeStamp;
    DWORD dwFrameNumber;
    DWORD dwFlags;
    Vector4 vFloorClipPlane;
    Vector4 vNormalToGravity;
    NUI_SKELETON_DATA SkeletonData[NUI_SKELETON_COUNT];
    } NUI_SKELETON_FRAME;
    C’est ce tableau de structures que nous allons manipuler afin de retrouver l’index ou les index des squelettes détectés par kinect. Pour ce faire, il suffit de boucler sur le nombre NUI_SKELETON_COUNT et tester si l’état du squelette eTrackingState est détecté ou pas.
    for ( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
    {
    if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED )
    {
    //code omis pour plus de clarté
    }
    }

  • Une fois détecté, il est possible d’appliquer un filtre à la trame avec l’API NuiTransformSmooth, afin de lisser les effets du squelette et éviter les phénomènes de « gigue » du squelette lorsqu’un point n’est pas entièrement détecté et que Kinect soit obligée de faire une « Prediction » de la position de ce point. A vous de jouer sur ces valeurs pour trouver le meilleur compromis, mais les valeurs par défaut étant calibrées manifestement correctement, vous pouvez passer NULL dans un 1er temps.
    NUI_TRANSFORM_SMOOTH_PARAMETERS smooth;
    smooth.fCorrection=0.5f;
    smooth.fSmoothing=0.5f;
    smooth.fPrediction =0.5f;
    smooth.fJitterRadius =0.05f;
    smooth.fMaxDeviationRadius=0.04f;
    hr=NuiTransformSmooth(&SkeletonFrame,&smooth);

  • Une fois que le squelette est détecté, nous allons traquer, les 20 points ou jointures du squelette. La structure NUI_SKELETON_DATA contient elle-même un tableau SkeletonPositions de Vector4 de NUI_SKELETON_POSITION_COUNT (Constante définie sur 20)
    typedef struct _NUI_SKELETON_DATA
    {
    NUI_SKELETON_TRACKING_STATE eTrackingState;
    DWORD dwTrackingID;
    DWORD dwEnrollmentIndex;
    DWORD dwUserIndex;
    Vector4 Position;
    Vector4 SkeletonPositions[NUI_SKELETON_POSITION_COUNT];
    NUI_SKELETON_POSITION_TRACKING_STATE eSkeletonPositionTrackingState[NUI_SKELETON_POSITION_COUNT];
    DWORD dwQualityFlags;
    } NUI_SKELETON_DATA;
    Par exemple si nous souhaitons traquer les mains il suffit d’utiliser les index NUI_SKELETON_POSITION_HAND_RIGHT etNUI_SKELETON_POSITION_HAND_LEFT

    for( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
    {
    if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED )
    {
    Vector4 mainDroite=SkeletonFrame.SkeletonData[i].SkeletonPositions
    [
    NUI_SKELETON_POSITION_HAND_RIGHT] Vector4 mainGauche = SkeletonFrame.SkeletonData[i].SkeletonPositions
    [
    NUI_SKELETON_POSITION_HAND_LEFT]
    etc……
    }
    }

    et ainsi de suite pour les autres Points.
    A partir de la vous pouvez imaginer tous les gestes possible, a vous de vous remettre à la trigo.
    Note : Le SDK Kinect dans sa version actuelle, ne gère pas les gestes. C’est-à-dire que pour calculer un déplacement, par exemple un mouvement de la main, qui simule une action, comme sur la XBOX avec la Kinect, il vous faudra réinventer la roue, en attendant qu’une nouvelle version du SDK voie le jour.
    Néanmoins David Catuhe mon collègue, propose un toolkit Kinect https://kinecttoolkit.codeplex.com/ en C# qui peut sans doute vous être utile et vous donner des pistes quant à la gestion des gestes.

  • Néanmoins, ce n’est pas suffisant pour afficher correctement le point, car il faut transposer les informations 3D en 2D. En effet la ligne suivante SkeletonFrame.SkeletonData[i].SkeletonPositions[ NUI_SKELETON_POSITION_HAND_RIGHT]
    retourne la position du point dans une variable de type Vector4.
    qui indique la position dans un espace 3D (x,y,z) en mètre par rapport au centre de la Kinect.
    clip_image004

  • Nous allons donc dans un premier temps, transformer ces informations 3D dans un espace 2D à l’aide de l’API NuiTransformSkeletonToDepthImageF
    float fx=0,fy=0;
    Vector4 pointDeContact=squelette->SkeletonPositions[
    NUI_SKELETON_POSITION_HAND_RIGHT ];
    NuiTransformSkeletonToDepthImageF( pointDeContact, &fx, &fy );

  • Ensuite il faut mettre à l’échelle les points à afficher en fonction de la zone client.
    RECT rct;
    GetClientRect(hWnd, &rct);
    int width = rct.right;
    int height = rct.bottom;
    float scalefx= fx * width;
    float scalefy= fy * height;

    scalefx et scalefy représentent désormais des coordonnées d’un point qui peut être affiché à l’écran.

  • Nous n’avons plus qu’à afficher ce point sous forme d’une ellipse Direct2D.
    D2D1_ELLIPSE pointContact;
    pointContact.point =D2D1::Point2F (scalefx,scalefy);
    pointContact.radiusX =5.0F;
    pointContact.radiusY =5.0F;
    _pcontexteRenduD2D1->FillEllipse (pointContact,&brosseVerte);
    Note :
    J’ai utilisé Direct2D, pour ces capacités à profiter de l’accélération Hardware, vous pouvez tout à fait utiliser vos propres routine d’affichage GDI/GDI++, Direct3D, OpenGL etc..

Télécharger le code source.

Eric Vernié