La sortie du SDK Kinect pour Windows en bêta m’a finalement décidé à m’en procurer un et je vous propose de vous faire partager mes premiers pas avec ce SDK aux possibilités très intéressantes.

Il vous faudra bien évidemment tout d’abord télécharger le SDK Kinect ICI. Une fois installé, votre Kinect devrait être correctement reconnu par Windows. Pour les applications audio notamment celles des samples du SDK, il vous faudra aussi installer l’outil de reconnaissance vocale Microsoft Speech ( le SDK et le runtime )

Le SDK se décompose en 2 parties: la partie Audio et la partie NUI (pour Natural User Interface).
Je n’ai pas encore étudié la partie audio elle fera donc l’objet d’un prochain article. Concentrons nous sur le fonctionnement de la partie NUI.

Le SDK nous offre en définitive 3 flux différents qui nous permettent d’exploiter les données reçues de la caméra:

  • Le flux vidéo: il représente l’image captée par la caméra à la manière d’une simple webcam. La résolution peut être de 640×480 à 30FPS ou de 1280×1204 à 15FPS.
  • Le buffer de profondeur: il représente les profondeurs des différents points. A ce niveau, les points sont déjà associés à une personne détectée par le système. Il nous sert donc à savoir pour chaque point de l’image si il correspond à une personne devant l’écran et à laquelle il correspond si plusieurs personnes sont présentes. Ce buffer est un tableau d’entiers 16 bits (13 bits pour la profondeur et 3 bits pour l’indice de la personne détectée)
  • Le traqueur de squelette: il analyse les personnes présentes devant l’objectif et retourne les informations de squelette correspondantes. On a ainsi directement accès à la position de chacun des 20 points (joints) qui composent le squelette.

Nous allons essayer de faire une petite application très simple: une simple fenêtre WPF contenant juste un canvas et une image à l’intérieur prenant toute la surface de la fenêtre:

<Window x:Class="PremiersPasKinect.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Loaded="Window_Loaded"
        Title="MainWindow" Height="480" Width="640">
    <Canvas Name="root">
        <Image x:Name="video" Height="480" Width="640" HorizontalAlignment="Left" Margin="0,0,0,0" Stretch="Fill" VerticalAlignment="Top" />
    </Canvas>
</Window>

Dans la méthode Window_Loaded, initialisons le runtime en lui précisant qu’on aura besoin de l’image video, du buffer de profondeur et du traqueur de squelette:

kinectRuntime.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);

Dans un premier temps, affichons la sortie video du Kinect dans l’image. Pour ça, nous allons nous ouvrir le flux et nous abonner à l’évènement VideoFrameReady du runtime:

kinectRuntime.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
kinectRuntime.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinectRuntime_SkeletonFrameReady);
kinectRuntime.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(kinectRuntime_VideoFrameReady);

Le gestionnaire d’évènements est le suivant:

void kinectRuntime_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
    PlanarImage Image = e.ImageFrame.Image;
    BitmapSource ColorBitmap;
    ColorBitmap = BitmapSource.Create(Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, null, Image.Bits, Image.Width * Image.BytesPerPixel);
    this.video.Source = ColorBitmap;
}

On récupère simplement les pixels envoyés dans le flux vidéo du Kinect et oin s’en sert pour créer un BitmapSource qu’on affiche ensuite dans notre contrôle Image.
Si on lance l’application à ce stade, on verra une fenêtre affichant la sortie vidéo du Kinect.

Augmentons un peu cette réalité 🙂
On va ajouter en haut à gauche de notre Canvas un rectangle tout simple:

<Rectangle x:Name="vignette" Height="100" Width="100" Fill="Blue" HorizontalAlignment="Left" Margin="10,10,0,0" Stretch="Fill" VerticalAlignment="Top" />

On va maintenant essayer de pouvoir “prendre” ce rectangle et le déplacer avec la main gauche.
Au lancement de l’application, le rectangle n’est pas attaché. On va donc créer un booleén qui nous permettra de savoir à quelle moment la main aura pris le rectangle:

bool vignetteAttached = false;

Pour suivre les mouvements de la main, il va nous falloir gérer un squelette. Pour cela, nous allons ouvrir le flux du buffer de profondeur et nous abonner à l’évènement SkeletonFrameReady du runtime:

kinectRuntime.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
kinectRuntime.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinectRuntime_SkeletonFrameReady);

Le gestionnaire d’évènement sera le suivant:

void kinectRuntime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
    SkeletonFrame skeletonFrame = e.SkeletonFrame;
    Microsoft.Research.Kinect.Nui.Vector leftHandPosition = new Microsoft.Research.Kinect.Nui.Vector();
    SkeletonData data = skeletonFrame.Skeletons.Where(o => o.TrackingState == SkeletonTrackingState.Tracked).FirstOrDefault();
    if (data != null)
    {
        foreach (Joint joint in data.Joints)
        {
            if (joint.ID == JointID.HandLeft && joint.Position.W > 0.6f)
            {
                leftHandPosition = joint.Position;
                break;
            }
        }
        UpdateVignette(leftHandPosition);
    }
}

Ce code est assez simple on récupère tout d’abord le premier squelette traqué, et ensuite on boucle dans les joints pour récupérer la position de la main gauche. La coordonnée W représente un indice de qualité on flitre donc les résultats pour ne sortir que des points qui ont un indice de qualité suffisant.

Il reste à gérer la position de notre vignette:

private void UpdateVignette(Microsoft.Research.Kinect.Nui.Vector leftHandPosition)
{
    if (vignetteAttached)
    {
        Point pos = GetDisplayPoint(leftHandPosition, video);
        Canvas.SetTop(vignette, pos.Y);
        Canvas.SetLeft(vignette, pos.X);
    }
    else
    {
        GeneralTransform transform = vignette.TransformToAncestor(this);
        Point vignettePos = transform.Transform(new Point(0, 0));
        Point pos = GetDisplayPoint(leftHandPosition, video);
        if (pos.X > vignettePos.X && pos.X < vignettePos.X + vignette.Width
            && pos.Y > vignettePos.Y && pos.Y < vignettePos.Y + vignette.Height)
            vignetteAttached = true;
    }
}

Si la vignette n’est pas attachée, on vérifie la position de la main gauche et si elle correspond à la position de la vignette on considère que la personne a “pris” la vignette en main et on l’attache. Si elle est attachée, on met à jour sa position avec celle de la main.

Les positions retournées par le squelette ne se trouvent pas dans le même système de coordonnées que celles du canvas. Il faut donc convertir ces coordonnées grâce à la méthode GetDisplayPoint:

private Point GetDisplayPoint(Microsoft.Research.Kinect.Nui.Vector jointPosition, FrameworkElement displaySurface)
{
    float depthX, depthY;
    kinectRuntime.SkeletonEngine.SkeletonToDepthImage(jointPosition, out depthX, out depthY);
    depthX = Math.Max(0, Math.Min(depthX * 320, 320));  //convert to 320, 240 space
    depthY = Math.Max(0, Math.Min(depthY * 240, 240));  //convert to 320, 240 space
    int colorX, colorY;
    ImageViewArea iv = new ImageViewArea();
    // only ImageResolution.Resolution640x480 is supported at this point
    kinectRuntime.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(ImageResolution.Resolution640x480, iv, (int)depthX, (int)depthY, (short)0, out colorX, out colorY);

    // map back to skeleton.Width & skeleton.Height
    Point pos = new Point((int)(displaySurface.Width * colorX / 640.0), (int)(displaySurface.Height * colorY / 480));
    return pos;
}

Cette méthode est reprise d’un sample du SDK. Elle converti tout d’abord le point en coordonnées sur le buffer de profondeurs, puis appelle la méthode NuiCamera.GetColorPixelCoordinatesFromDepthPixel qui permet de transformer les coordonnées d’un point du buffer de profondeur en celles d’un point de la sortie vidéo brute. Il suffit ensuite d’appliquer un ratio pour remettre ce point dans le systèmes de coordonnées de notre Canvas. (même si c’est inutil ici car notre Canvas à la même taille que le flux vidéo)

Voilà ce que ça donne en vidéo démontré par mon beau fils qui est très heureux que le SDK soit sorti ce qui lui a permis d’avoir un Kinect à la maison 🙂
Lien Vimeo

Le code source de l’application de démo est disponible ICI.

Cette application très simple montre les bases du travail avec Kinect et les squelettes. Pour des interactions avancées et plus intéressantes, il faudra cependant prendre en compte les gestures qui ne sont pas gérées nativement par le SDK. Je me pencherai là dessus dans un prochain article.

Enjoy…


Show CommentsClose Comments

1 Comment

Comments are closed.