Loupe

[Kinect 2] Utiliser la Color Camera

Pour faire suite à l’article précédent Kinect 2 : Commencer à développer, voici un nouvel article dont le but cette fois sera d’expliquer le fonctionnement d’une des caméras de la Kinect 2 : la caméra couleur.

Cette caméra, ou « Color Camera » dans le jargon de l’API, possède une résolution de 1920x1080 et permet de retranscrire fidèlement l’environnement de l’utilisateur, le tout sans latence notable puisque son frame rate est de 30 fps.

ColorFrameSource

L’usage de la Color Camera se fait au travers de la propriété ColorFrameSource de l’objet KinectSensor. Une façon d’ouvrir la connexion à la caméra est la suivante :

var sensor = KinectSensor.GetDefault();
sensor.Open();
// vérifier la disponibilité et attendre si besoin grâce à sensor.IsAvailable et sensor.IsAvailableChanged
using (var reader = sensor.ColorFrameSource.OpenReader())
{
    // ...
}

A partir de là, il suffit de se placer dans le using et d’utiliser la variable reader. Pour commencer, il convient de déterminer le type de format dans lequel on souhaitera récupérer les frames de la caméra, ainsi que leur description (en termes de taille notamment) :

var format = ColorImageFormat.Bgra;
var frameDesc = reader.ColorFrameSource.CreateFrameDescription(format);
var frameWidth = (uint)frameDesc.Width;
var frameHeight = (uint)frameDesc.Height;

Selon le mode employé pour effectuer le rendu, le code qui suit pourra différer. Une technique consiste à effectuer le rendu dans un élément Image en inscrivant les frames dans un bitmap. Cette technique étant largement utilisée dans les exemples fournis à l’installation du SDK, nous utiliserons un MediaElement en effectuant un rendu dans un MediaSample afin de montrer quelque chose de moins commun. Il faudra donc créer une MediaStreamSource dont on définira l’encodage de la vidéo (Bgra8 puisque nous avons choisi le format Bgra pour le format des frames que nous retournera la Color Camera) :

var encoding = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, frameWidth, frameHeight);
var descriptor = new VideoStreamDescriptor(encoding);
var mediaStreamSource = new MediaStreamSource(descriptor);

Il sera ensuite nécessaire de déterminer la durée d’une frame (sachant que la camera tourné à 30 fps en temps normal) et un avancement dans la vidéo (où le temps écoulé en terme de frame) :

var timeOffset = new TimeSpan(0);
var sampleDuration = new TimeSpan(0, 0, 0, 0, 1000 / 30);

Afin de récupérer les frames de la caméra, un IBuffer sera nécessaire, ainsi qu’une variable dont la valeur indiquera si le buffer est vide ou non :

var buffer = WindowsRuntimeBuffer.Create((int)(frameDesc.LengthInPixels * frameDesc.BytesPerPixel));
var bufferIsEmpty = true;

Il suffira ensuite d’assigner la MediaStreamSource au MediaElement comme suit :

MediaElement.SetMediaStreamSource(mediaStreamSource);

Puis de s’abonner à l’évènement SampleRequested de la MediaStreamSource. Dans le handler de cet évènement, nous fourniront au MediaElement une frame à afficher, récupérée de la connexion au ColorFrameSource ouverte au préalable :

mediaStreamSource.SampleRequested += (s, a) =>
{
    //...
};

Nous pourrons récupérer un objet deferral dont nous nous servirons pour notifier le MediaElement que le MediaSample (la frame) est prêt. Pour récupérer la frame et en copier les données dans le buffer :

using (var frame = reader.AcquireLatestFrame())
{
    if (frame == null && bufferIsEmpty)
    {
        //...
    }

    if (frame != null)
    {
        frame.CopyConvertedFrameDataToBuffer(buffer, format);
        bufferIsEmpty = false;
    }

    DefineSample(a.Request, buffer, timeOffset, sampleDuration);
}

Nous verrons par la suite comment gérer le cas où la frame retournée par l’API est null et que le buffer est vide. Dans tous les autres cas, si la frame est null, il suffit de réutiliser le buffer qui contiendra toujours les données de la précédente frame. Pour obtenir ces données, la méthode CopyConvertedFrameDataToBuffer permet de récupérer les données de la frame, encodées dans le format adéquat (ici nous avions spécifié Bgra) et de les copier dans le buffer. Nous utilisons ensuite la méthode DefineSample, dont voici l’implémentation :

private static void DefineSample(MediaStreamSourceSampleRequest request, IBuffer buffer, TimeSpan timeOffset, TimeSpan sampleDuration)
{
    var sample = MediaStreamSample.CreateFromBuffer(buffer, timeOffset);
    sample.KeyFrame = true;
    sample.Duration = sampleDuration;
    request.Sample = sample;
}

Cette méthode va définir un objet sample contenant les données du buffer, en spécifiant l’avancement courant dans la vidéo (timeOffset) et la durée de la frame (sampleDuration).

Il suffira ensuite de faire avancer le timeOffset pour la prochaine frame et de notifier la MediaStreamSource que la frame est prête via le deferral :

timeOffset = timeOffset.Add(sampleDuration);
deferral.Complete();

Attendre qu’une ColorFrame soit disponible

Le cas précèdent non géré arrive notamment au démarrage. En effet, le MediaElement peut vouloir récupérer une frame alors que la caméra n’est pas encore prête et ne retourne que des frames vides. Pour remédier à ce problème, il ne faut pas se contenter d’appeler le complete du defferal (et donc de renvoyer un Sample null) car alors le MediaElement se placera dans un état indéterminé. Impossible aussi de simplement faire un return et quitter la fonction en cours, car alors le MediaElement attendra indéfiniment une frame qui ne lui sera jamais fournie. Il est donc nécessaire d’attendre la prochaine frame de la caméra non nulle en s’abonnant à l’évènement FrameArrived du reader de la ColorFrameSource :

if (frame == null && bufferIsEmpty)
{
    TypedEventHandler<ColorFrameReader, ColorFrameArrivedEventArgs> nextFrameArrived = null;
    nextFrameArrived = (fs, fa) =>
    {
        using (var fframe = fa.FrameReference.AcquireFrame())
        {
            if (fframe == null) return;
            fframe.CopyConvertedFrameDataToBuffer(buffer, format);
            bufferIsEmpty = false;
            DefineSample(a.Request, buffer, timeOffset, sampleDuration);
            timeOffset = timeOffset.Add(sampleDuration);
            deferral.Complete();
        }
        reader.FrameArrived -= nextFrameArrived;
    };
    reader.FrameArrived += nextFrameArrived;
    return;
}

Une fois la frame obtenue, il ne faut pas oublier de se désabonner de l’évènement.

Avec ce procédé, il est donc possible d’utiliser un MediaElement dans une page d’une application WinRT et d’en spécifier la source dans le code behind comme vue précédemment :

<MediaElement x:Name="MediaElement"
                      Width="800"
                      Height="600" />

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus