Ça code partout en Belgique : les dates à ne pas manquer !

1. June 2012 13:06 by Renaud in   //  Tags:   //   Comments (0)

Si certains pensent encore qu'il ne se passe pas grand chose en Belgique, les voilà servis ! ^^

Cet été, il y a déjà au moins quatre événements majeurs à ne pas manquer ! Dans l'ordre, on retrouve:

  • Le Microsoft Community Day 2012, le 21 juin : l'événement qui regroupe toutes les communautés et users groups de Belgique ! Une journée gratuite pour apprendre plein de trucs cools: http://www.communityday.be/. Attention d'après un tweet récent (3 juin), il ne reste que 50 places ! Donc courrez vous inscrire ;)
  • BeMyApp, du 29 juin au 1 juillet : 48h de pour coder une killer app sur l'environnement de son choix (Windows Phone, iOS, Android, web) et défendre la Belgique dans une compétition mondiale !  Toutes les infos sont disponibles ici: http://be.bemyapp.com/. Ca ne s'adresse pas uniquement aux développeurs, mais aussi aux porteurs d'idées, aux designers, et aux supers touristes ! :D L'entrée est gratuite pour tout le monde. Donc sauf cas de force majeure, il n'y a pas vraiment à hésiter!
  • Windows 8 Summer App-a-thon, le 12 juillet : Coding on the beach, parce que c'est pas vrai qu'on a peur du soleil ! Une journée de dev à la mer du nord sous le soleil de Belgique. Faudra penser à apporter de quoi bien s'hydrater (ce n'est pas @svidouse qui me contredira !). La location exacte n'est pas encore annoncée, mais vous pouvez rester au courant en vous inscrivant déjà sur facebook: https://www.facebook.com/events/316680961739310
  • Windows 8 Summer App-a-thon , le 8 août : pour ceux qui n'auront pas eu la chance de participer à la première édition, ou ceux qui sont très motivés, voici la version Metropolitaine qui aura lieu du côté d'Anvers, dans un endroit encore tenu secret ! Vous pouvez suivre les nouvelles à propos de cet event ici: https://www.facebook.com/events/283129108449164/

Enfin, notez aussi ces quelques dates :

Sessions gratuites d'introduction au SDK Kinect 1.5  :

6 juin : @micbelgique, sur Mons : http://events.mic-belgique.be/event/kinect-sdk-hands-on--3 13 juin : @MICBrussels : http://micbru.fikket.com/event/kinect-sdk-hands-on--5 3 juillet : @MICBrussels : http://micbru.fikket.com/event/kinect-sdk-hands-on--6

Visionnage en live de la session #MeetAzure présentée par Scott Guthrie :

7 juin : @micbelgique, sur Mons : http://mic.fikket.com/event/meet-windows-azure-live


Il y a certainement encore beaucoup de choses à venir ! N'hésitez pas à me suivre sur twitter (@DumontRenaud) pour être tenus au courant !

Vous pouvez également suivre @DavidHernie et @micbelgique pour être les premiers informés ! ;)


Update 14/06 : Les inscriptions pour le Summer App-a-thon à la côté belge sont ouvertes !

Kinect SDK 1.5 - Comprendre les différents flux de données

31. May 2012 17:05 by Renaud in   //  Tags:   //   Comments (0)

Dans cet article on va se représenter de manière simple les données brutes que l’on peut utiliser lorsque l’on travaille avec le SDK Kinect pour Windows (à l’exception du flux audio que l’on va un peu mettre de côté pour le moment).

Il est intéressant de parler de ces différents flux et des données qu’ils transportent pour bien pouvoir utiliser la Kinect.

Les 3 flux

La Kinect possède (pour faire simple et toujours en excluant la partie audio) deux sources de données :

  • Un capteur de couleur.
  • Un capteur infrarouge.

Ces capteurs vont permettre de retourner 3 flux de données, dont deux sont plutôt similaires :

Les deux premiers fournissent des images de la scène (j’appelle scène ce qui se trouve dans le champ de vision de la Kinect), décrites de différentes manières :

  • ColorImageStream : chaque pixel de l’image est défini par sa couleur
  • DepthImageStream : chaque pixel de l’image est défini par sa profondeur

Le dernier flux est un peu différent puisqu’il est généré à partir des données dont la Kinect dispose. Il s’agit du :

  • SkeletonStream : fournit des infos sur les utilisateurs détectés par la Kinect

Les 3 photographies (frames)

Chacun de ces flux va nous permettre d’obtenir des photographies de la scène à un instant T. Dans le SDK Kinect, on parle alors de frames. Sans surprise, on va trouver un type de frame correspondant à chaque flux précédemment cité :

  • ColorImageFrame
  • DepthImageFrame
  • SkeletonFrame

SkeletonFrame

On va commencer par le SkeletonFrame, qui est sans doute le plus simple à expliquer. Un SkeletonFrame possède quelques propriétés parmi lesquelles Timestamp qui indique sa date de création, FrameNumber qui l’identifie par un entier. On a également une indication de où se trouve le sol dans un espace à 3 dimensions, le TrackingMode utilisé (debout ou assis). Et pour finir, une propriété SkeletonArrayLength retourne le nombre maximal d’utilisateurs qui peuvent être détectés en même temps. Pour le moment, cette valeur sera en fait toujours égale à 6. La Kinect est en effet capable de détecter jusqu’à six personnes en même temps (mais seulement deux d’entre elles seront détaillées).

Cette dernière propriété n’est pas inutile, parce qu’elle nous renseigne en fait la quantité de données qui est contenue dans une frame. Et on va en avoir besoin pour pouvoir utiliser l’unique méthode propre à la classe SkeletonFrame :

public void CopySkeletonDataTo ( Skeleton[] skeletonData )

Cette méthode est la clé à cette étape du développement. Elle va permettre de copier les données contenues dans l’objet SkeletonFrame vers un tableau de Skeleton. Un tableau de Skeleton (skeletonData) doit être passé à la méthode pour être rempli avec les données, et ce tableau doit être initialisé à la bonne taille !

Une fois les données copiées, on pourra libérer l’objet SkeletonFrame et travailler sur la copie des données que nous venons de faire.

ColorImageFrame et DepthImageFrame

Ces deux-là, encore une fois, sont similaires. Et pour cause, ils héritent de la même classe de base : ImageFrame. Comme évoqué précédemment, ces deux frames vont définir un ensemble de pixels. Dans la classe ImageFrame, on retrouve des propriétés telles que Width (largeur de l’image), Height (hauteur de l’image), BytesPerPixel (nombre d’octets utilisés pour décrire un pixel).

Les classes ColorImageFrame et DepthImageFrame possèdent toutes deux une propriété Format respectivement de type ColorImageFormat et DepthImageFormat. Ces types sont en fait des énumérations, qui spécifient les différents formats. Les noms des différentes valeurs sont assez explicites :

ColorImageFormat.RawYuvResolution640x480Fps15 
 RgbResolution1280x960Fps12 
 RgbResolution640x480Fps30 
 YuvResolution640x480Fps15 
 Undefined

Le ColorImageFormat spécifie l’encodage (ex : Rgb), la résolution (ex :  640x480), et la fréquence (ex : Fps30).

DepthImageFormat.Resolution320x240Fps30
 Resolution640x480Fps30
 Resolution80x60Fps30
 Undefined

Par contre, le DepthImageFormat spécifie uniquement la résolution et la fréquence.

Ces propriétés décrivent donc ce que représentent les données contenues dans chacune des frames.

De la même manière que pour le SkeletonFrame, il existe pour ColorImageFrame et DepthImageFrame une méthode permettant de copier les données contenues vers un tableau afin de les utiliser par la suite, ainsi qu’une propriété indiquant la taille de ce tableau : PixelDataLength.

ColorImageFrame.CopyPixelDataTo (byte[] colorPixelData)

Pour le ColorImageFrame, on va copier les données dans un tableau de byte. Chaque pixel sera représenté par plusieurs bytes en fonction de l’encodage, et le nombre total de pixel variera en fonction du format, et donc de la résolution choisie.Par exemple, le format Rgb utilise 4 bytes pour représenter un pixel. Avec une résolution de 640x480, on aura donc : 640 x 480 x 4 = 1228800 bytes.

On peut donc comprendre que Width * Height * BytesPerPixel = PixelDataLength.

DepthImageFrame.CopyPixelDataTo (short[] depthPixelData)

Dans le cas de DepthImageFrame, les données seront copiées dans un tableau de short (des entiers codés sur 16 bits). Dans ce cas-ci, un pixel équivaut à un short.

Les données brutes

Maintenant que nous avons des copies des données représentant la scène à un instant T, il serait intéressant de savoir quoi en faire. Et pour cela, il faut également savoir ce que représentent exactement ces données.

SkeletonStream : Des squelettes représentés par un tableau de Skeleton

Pour le SkeletonStream, c’est assez simple : on récupère toujours un tableau de 6 objets de type Skeleton. Chacun d’eux a un statut (SkeletonTrackingState) indiquant si l’objet représente un utilisateur détecté, et s’il est entièrement suivi (SkeletonTrackingState.Tracked) ou si l’on ne connait que sa position globale (SkeletonTrackingState.PositionOnly).

La quantité est donc toujours la même, mais leur état peut varier en fonction de la scène.

DepthImageStream : Des distances représentées par un tableau de short

Au niveau du DepthImageStream, c’est un peu plus complexe. En effet, en fonction de la résolution choisir pour le flux, la quantité de données sera différentes.

Vous savez déjà que chaque short du tableau représente un pixel dans la frame. La position d’un pixel dans la frame est définie par son abscisse et son ordonnée (X et Y). Le point d’origine est le coin supérieur gauche (0, 0). X représente la colonne, et Y la ligne.

Dans le tableau de short, les pixels sont représentés les uns à la suite des autres :

(0  , 0),
(1  , 0),
(2  , 0),
 ...    ,
(639, 0),
(0  , 1),
(1  , 1),
…

Pour avoir la valeur en short du pixel de position (X, Y), il faut donc calculer comme ceci :

depthPixelData[Y * width + X]

Il reste encore une petite astuce : la valeur du short ne correspond pas directement à la distance que vous attendez. En fait, sur les 16 bits utilisés par le short, seuls 13 (ceux de poids fort) sont utilisés pour représenter la distance !

Les 3 bits de poids faible servent à indiquer si le pixel en question représente un utilisateur et, si oui, l’indice de celui-ci (de 1 à 6). Notez que pour espérer obtenir une valeur ici, vous devez activer le SkeletonStream. Dans le cas contraire, la valeur des 3 bits sera toujours 0, ce qui signifie qu’aucun utilisateur n’a été détecté à cet endroit.

Vous pouvez très simplement récupérer les valeurs qui vous intéressent en manipulant les bits comme ceci :

short depth = (short)(depthPixelData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth);

short playerIndex = depthPixelData[i] & DepthImageFrame.PlayerIndexBitmask;

ColorImageStream : Des couleurs représentés par un tableau de bytes

Pour le ColorImageStream, le cas est encore un peu différent. Non seulement le nombre de pixels (et donc la quantité de données) peut varier comme pour le DepthImageStream, mais en plus la manière dont est représenté chaque pixel est différente en fonction du format !

Vous savez déjà que la propriété BytesPerPixel indique le nombre d’octets utilisés pour représenter un pixel. Encore faut-il savoir à quoi servent chacun de ces octets.

Dans une image de type Bgr, il faut compter 4 octets pour un pixel. Un octet pour le bleu, un octet pour le vert, et un octet pour le rouge. Le dernier octet est généralement utilisé pour le canal alpha, qui indique le niveau de transparence. On parle alors de BGRA. La Kinect ne gère pas la transparence, et ce dernier octet sera toujours à 0.

Pour le format RawYUV, chaque pixel est codé sur 2 octets. Pour une image ayant la même résolution, le tableau de bytes fera donc la moitié en taille par rapport à une frame encodée en Bgr !


J'espère que cet article vous aura permis d'y voir plus clair ! Et surtout n'hésitez pas à revenir ici pour parler des projets que vous réalisez ! :)

Meet Windows Azure - En live au MIC!

26. May 2012 19:05 by Renaud in   //  Tags:   //   Comments (0)

Si vous ne savez pas quoi faire le jeudi 7 juin au soir, rejoingnez-moi et d'autres développeurs au Microsoft Innovation Center à Mons pour suivre l'event sur un grand écran et en bonne compagnie ! C'est gratuit et juste pour le plaisir de partager et de rencontrer du monde. Inscrivez-vous ici: http://events.mic-belgique.be/event/meet-windows-azure-live

 

      Meet Windows Azure         

 Scott Guthrie        

 

Le jeudi 7 juin prochain, Scott "The Gu" Guthrie (l'un des piliers dans le monde des développeurs .NET) fera une session à San Francisco qui parlera de Windows Azure et des dernières technologies Cloud

Vous comptiez regarder la session seul chez vous?

Venez plutôt passer la soirée avec nous au MIC à Mons ! Ce sera une bonne occasion pour rencontrer d'autres développeurs, discuter des nouveautés, et grignoter ou boire un verre en regardant la session sur grand écran ;)

Plus d'info sur l'event en lui-même: http://www.meetwindowsazure.com/

Le streaming live débute à 22h (belgique), mais vous êtes les bienvenus avant !  

Kinect SDK 1.5 - What's new ?

23. May 2012 15:52 by Renaud in   //  Tags:   //   Comments (0)

The new Kinect SDK for Windows went out two days ago! If you didn't get it yet, you can download it here: http://www.microsoft.com/en-us/kinectforwindows/develop/developer-downloads.aspx.

The Kinect team made an amazing job for this release, and there is a lot of new stuff to discover !

Don't worry for your current projects: everything you've done so far and which is working with the 1.0 version will still work with the new release. Some processes are now more efficient, such as the mapping between depth and color data (for example if you want to create an app to remove the background behind a user). There are also new features: the seated mode which allows the Kinect to track only 10 joints of the superior part of the human body (head, arms, ...), tracking is available in near modespeech recognition in french, and you also have the possibility to use face tracking based on the Candide-3 model which allows you to get information about the shape of the user's face and the facial expressions.

I'll write more detailed blog posts about those new features in the coming days with code samples!

Kinect SDK 1.5 - Quoi de neuf?

23. May 2012 12:05 by Renaud in   //  Tags:   //   Comments (0)

La version 1.5 du SDK Kinect est sortie ce lundi 21 mai. Si vous ne l'avez pas encore, vous pouvez la télécharger ici: http://www.microsoft.com/en-us/kinectforwindows/develop/developer-downloads.aspx.

La team Kinect a fait un travail énorme pour cette release, et il y a beaucoup de nouveautés à découvrir !

Premièrement il faut savoir que tout ce qui a été fait avec le version 1.0 fonctionne toujours en 1.5 ! Certains process ont été améliorés comme par exemple le mapping entre les données de profondeurs et de couleur (qu'on utilise notamment si l'on veut isoler l'image d'un joueur, sans tenir compte du background). De nouvelles fonctionnalités sont apparues: le mode seated ("assis") qui permet de tracker seulement 10 joints de la partie supérieur du corps humain (la tête, le tronc et les bras), le tracking en near mode, la reconnaissance vocale en français, et également une reconnaissance faciale basée sur le modèle Candide-3 qui permet d'obtenir des infos sur la forme et les expressions du visage.

Je vous posterai des articles plus détaillés sur les nouveautés dans les jours qui viennent avec des exemples de code !

Kinect SDK 1.0 - Hands-on! (fr)

27. April 2012 23:04 by Renaud in   //  Tags:   //   Comments (0)

Je vous propose de lire cette série de 5 articles consacrés à la Kinect pour Windows! En les parcourant vous apprendrez à récupérer et utiliser les informations des différents capteurs, pour vous en servir dans vos applications.

Sommaire

 1. Introduction à l'API
 2. Utilisation du ColorImageStream
 3. Tracker le squelette avec le SkeletonStream
 4. Kinect en profondeur avec le DepthStream
 5. Reconnaissance vocale

Le MIC proposera également un training d'une demi-journée basé sur le contenu de ces articles. Pour cette occasion, nous mettrons à votre disposition des Kinect pour Windows afin que vous puissiez tester vos applications!

L'inscription est gratuite et la formation aura lieu dans les locaux du MIC ! Deux dates sont disponibles: le 29 mai ou le 6 juin, de 13h à 17h.

 

 

Kinect SDK 1.0 - Hands-on!

27. April 2012 15:56 by Renaud in   //  Tags:   //   Comments (0)

I'm pleased to propose you a set of 5 blogposts introducing the Kinect for Windows programming. If you want to know how to use the Microsoft Kinect sensor to empower your applications, take five minutes to read those posts!

What's on the table?

 1. Introducing the Kinect SDK API
 2. Deal with the ColorImageStream
 3. Track users with the SkeletonStream
 4. Kinect in depth! (with the DepthStream)
 5. Speech recognition

The MIC also organizes an half-day training based on the content of those posts. Come and learn how to start programming for Kinect for Windows! Registration is free and the event will take place at the Microsoft Innovation Center in Mons. Two dates are available:  May 29 or June 6, from 1pm to 5pm. 

 

Kinect SDK 1.0 - 5 - Speech Recognition

22. April 2012 21:04 by Renaud in   //  Tags:   //   Comments (6)
 1. Introduction to the API
 2. Use the ColorImageStream
 3. Track the users with the SkeletonStream
 4. Kinect in depth!
 5. Speech recognition

Here is the last post of this serie talking about the Kinect SDK 1.0. In the previous ones, we saw how to display the ColorImageStream video, how to track the Skeleton, and how to produce a 3D video with the DepthImageStream.

In this final post, we will see how to add speech recognition capabilities to your application!

Think "user experience"

When you develop an application using Kinect, you should also try to think as if you were a user. In which way are you going to use your Kinect? Do you want the user to take a specific posture or to accomplish a gesture in order to trigger an action? Is it a repetitive task?

Keep in mind that gestures aren't easy to recognize with a lot of confidence because everybody do it in a different way. Think also that the range of gestures is limited! You can easily discern a right-hand wave from a left-hand wave. But some others gestures aren't that easy. For example, to distinguish a fist and an open hand, you need to process the image stream by yourself!

Also, how would you trigger several actions at the same time?

If at the end, your gestures are too complicated, difficult to execute, your users will quickly be weary of it! And this result in bad UX.

SDK 1.0 and speech recognition

By installing the SDK 1.0, you also installed the Microsoft Kinect Speech Recognition Language Pack (en-Us). For the moment, this pack is only available in English, but more languages have been announced with the release 1.5, such as French, Italian, Japanese and Spanish.

You have to know that the speech recognition doesn't require a Kinect! You could do it with any microphone.

Hands-on

As usual, here is a simple project that you can use to follow this lab:

[caption id="attachment_126" align="aligncenter" width="92" caption="WpfKinect - 5 - Speech Recognition"][/caption]

For this example, you can use any of you existing project. I choose to used a previous project that draws the Skeleton a the two tracked users.  For each tracked user, we draw spheres for the hands and for the head.

To that basic application, we will add speech recognition capabilities to change the shape used to draw the skeletons (circles or squares), and the color of each user.

The goal is to be able to command the application with a phrase such as "Use red squares for player one!" to see an update of the player one.

1/ Initialize the Recognizer

First of all, you'll have to add a reference to the assembly: Microsoft.Speech.dll. Be careful to use that one and not System.Speech.dll. The second one doesn't give you access to the installed Kinect recognizer.

The SDK documentation gives us an helper method to retrieve the Kinect recognizer. We need that method because you probably have more than one recognizer installed and that we want to use the Kinect one.

This method creates a function which takes a RecognizerInfo as parameter, and which returns a Boolean. The RecognizerInfo is a object describing a speech recognition engine installed on your machine. This object has a Dictionary property which contains information about the recognizer, such as the supported cultures and languages, the name, the version number, ...

This function will return true if value of Kinect in the AdditionInfo dictionary is "True", and if the culture is "en-US" (which is the only one available for now).

Then, we will use that function in a Linq request so that for each installed recognizer, we will check if it fits the criteria, and return the first one that matches.

        private static RecognizerInfo GetKinectRecognizer()
        {
            Func<RecognizerInfo, bool> matchingFunc = r =>
            {
                string value;
                r.AdditionalInfo.TryGetValue("Kinect", out value);
                return "True".Equals(value, StringComparison.InvariantCultureIgnoreCase)
                    && "en-US".Equals(r.Culture.Name, StringComparison.InvariantCultureIgnoreCase);
            };
            return SpeechRecognitionEngine.InstalledRecognizers().Where(matchingFunc).FirstOrDefault();
        }

Here, we add a method to instantiate the SpeechRecognizerEngine. If an error occurs, we display a message box (this code also comes from the SDK documentation).

        private void InitializeSpeechRecognition()
        {
            RecognizerInfo ri = GetKinectRecognizer();

            if (ri == null)
            {
                MessageBox.Show(
                    @"There was a problem initializing Speech Recognition.
Ensure you have the Microsoft Speech SDK installed.",
                    "Failed to load Speech SDK",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                return;
            }

            try
            {
                speechRecognizer = new SpeechRecognitionEngine(ri.Id);
            }
            catch
            {
                MessageBox.Show(
                    @"There was a problem initializing Speech Recognition.
Ensure you have the Microsoft Speech SDK installed and configured.",
                    "Failed to load Speech SDK",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
            if (speechRecognizer == null)
                return;

            // Ajouter la suite ici!
        }
2/ Organize your keywords

To make it clearer, we will map some words to a significant value:

        #region Phrase mapping

        private Dictionary<string, Shape> Shapes = new Dictionary<string, Shape>
        {
            { "Circle", Shape.Circle },
            { "Square", Shape.Square },
        };

        private Dictionary<string, SolidColorBrush> BgrColors = new Dictionary<string, SolidColorBrush>
        {
            { "Yellow", Brushes.Yellow },
            { "Blue", Brushes.Blue },
            { "Red", Brushes.Red },
        };

        private Dictionary<string, int> PlayerIndexes = new Dictionary<string, int>
        {
            { "One", 0 },
            { "Two", 1 },
        };

        #endregion

The Shape enumeration contains all the supported shapes in the application. If we want to add more shape possibilities, they will appear here:

    public enum Shape
    {
        Circle,
        Square
    }
3/ Build the grammar

At the end of the InitializeSpeechRecognition method, we will add some code to build the phrase that we expect the user to tell. We will build Choices object that allows to expect different possibilities at a given position in the phrase.

And finally, we build the phrase based on "static" values and Choices. For example, the sentence should always start by "Use", followed by any of the possible colors, any of the possible shapes, and so on...

            // Create choices containing values of the lists
            var shapes = new Choices();
            foreach (string value in Shapes.Keys)
                shapes.Add(value);

            var colors = new Choices();
            foreach (string value in BgrColors.Keys)
                colors.Add(value);

            var playerIndexes = new Choices();
            foreach (string value in PlayerIndexes.Keys)
                playerIndexes.Add(value);

            // Describes how the phraze should look like
            var gb = new GrammarBuilder();
            //Specify the culture to match the recognizer in case we are running in a different culture.                                 
            gb.Culture = ri.Culture;
            // It should start with "Use"
            gb.Append("Use");
            // And then we should say any of the colors value
            gb.Append(colors);
            // Then one of the two possible shapes
            gb.Append(shapes);
            // then again the words "for player"
            gb.Append("for player");
            // and finally the player that we want to update
            gb.Append(playerIndexes);

            // Create the actual Grammar instance, and then load it into the speech recognizer.
            var g = new Grammar(gb);

            speechRecognizer.LoadGrammar(g);

We can easily build complex grammar! The whole GrammarBuilder could have been added to another Choices object as an alternative to another GrammarBuilder even more complex!

4/ Start the recognition

Then, we can subscribe to the speech recognizer events:

            speechRecognizer.SpeechRecognized += speechRecognizer_SpeechRecognized;
            speechRecognizer.SpeechHypothesized += speechRecognizer_SpeechHypothesized;
            speechRecognizer.SpeechRecognitionRejected += speechRecognizer_SpeechRecognitionRejected;

Then we start the Kinect audio stream, set that stream as the input source of the speech recognizer, and finally start the recognition!

            var audioSource = this.Kinect.AudioSource;
            audioSource.BeamAngleMode = BeamAngleMode.Adaptive;
            var kinectStream = audioSource.Start();

            speechRecognizer.SetInputToAudioStream(
                    kinectStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));
            speechRecognizer.RecognizeAsync(RecognizeMode.Multiple);

The RecognizeMode enumeration indicates whether you want the recognition to stop after a first recognition event (Single) or to continue until you stop it manually (Multiple).

5/ Process the results

Now the recognition has started, and we will start processing what the Kinect heard. The first two eventhandlers are fired respectively when a phrase is rejected (because the confidence is too low), and when a phrase is hypothesized which means that a phrase is recognized but is ambiguous because it matches different accepted results. In those case, we will just display the result text.

        void speechRecognizer_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            Console.WriteLine("Rejected: " + e.Result.Text);
        }

        void speechRecognizer_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            Console.WriteLine("Hypothesized: " + e.Result.Text);
        }

The event that we really need is the SpeechRecognized. It will give us a result with a Confidence property. The confidence indicates how likely the result is the right one compared to other possibilities. Those possibilities are stored in the Alternatives property, which contains a collection of RecognizedPhrase.

Th recognized phrase is stored as a string in the Text property or as a collection of RecognizedWordUnit in the Words property. Each one of those words has its on Confidence property.

In that last code sample, we will analyze the result and modify the settings of the corresponding user:

        void speechRecognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            // Confidence indicates the likelihood that a phrase is recognized correctly
            // compared to alternatives. 0.8 confidence doesn't mean 80% chance of being correct.
            if (e.Result.Confidence < 0.50)
            {
                Console.WriteLine("Rejected: " + e.Result.Text + ", " + e.Result.Confidence);
                if (e.Result.Alternates.Count > 0)
                {
                    // Print the alternatives
                    Console.WriteLine("Alternates available: " + e.Result.Alternates.Count);
                    foreach (RecognizedPhrase alternate in e.Result.Alternates)
                    {
                        Console.WriteLine("Alternate: " + alternate.Text + ", " + alternate.Confidence);
                    }
                }
                return;
            }
            Console.WriteLine("Recognized: " + e.Result.Text + ", " + e.Result.Confidence);

            var index = PlayerIndexes[e.Result.Words[5].Text];

            var playerConfig = playerConfigs[index];

            if (playerConfig != null)
            {
                playerConfig.Brush = BgrColors[e.Result.Words[1].Text];

                playerConfig.Shape = Shapes[e.Result.Words[2].Text];
            }
        }

Kinect SDK 1.0 - 5 - Reconnaissance vocale

22. April 2012 16:03 by Renaud in   //  Tags:   //   Comments (2)
 1. Introduction à l'API
 2. Utilisation du ColorImageStream
 3. Tracker le squelette avec le SkeletonStream
 4. Kinect en profondeur avec le DepthStream
 5. Reconnaissance vocale

Voici le dernier article de cette série consacrée au Kinect SDK 1.0. Dans les précédents articles on a vu comment afficher une vidéo avec le ColorImageStream, comment tracker les utilisateurs avec le SkeletonStream, comment faire une vidéo 3D avec le DepthImageStream.

Pour terminer et ajouter un petit plus à vos applications en termes de Natural User Interface, on va voir comment utiliser la reconnaissance vocale!

Pensez expérience utilisateur

Quand vous développez une application utilisant la Kinect, essayez de vous mettre à la place des utilisateurs. Quelle est l'utilisation que vous en faites? Est-ce que les utilisateurs devront prendre des postures spécifiques? Ou bien réaliser un certain mouvement pour déclencher une action? Est-ce que cette tâche sera répétitive?

Rappelez-vous que les gestures (mouvements) sont difficiles à détecter avec certitude dû à leur différentes interprétations par les utilisateurs. Rappelez-vous également que l'éventail de mouvement est limité! Vous pouvez facilement différencier un "salut" de la main droite, d'un de la main gauche. Une main en bas, ou en haut. Mais quid du reste? Une main fermée ou ouverte demande une analyse plus poussée des images!

Demandez-vous aussi, comment gérer le fait de devoir déclencher plusieurs actions simultanément?

Si vous deviez arriver au point de faire des mouvements trop complexes, ou difficiles à exécuter, les utilisateurs risqueraient vite d'être fatigués, ou lassés de votre application. Et dès lors, ils en auront une mauvaise expérience.

Dans ce contexte, l'utilisation de la reconnaissance vocale semble être une bonne idée!

SDK 1.0 et reconnaissance vocale

En installant le SDK 1.0, vous avez installé par la même occasion le Microsoft Kinect Speech Recognition Language Pack (en-Us). Et en effet pour le moment ce pack n'est disponible qu'en anglais. Toutefois, le français est annoncé dans la release 1.5 au côté d'autres langues telles que l'Italien, le Japonais, et l'Espagnol.

Même si le français n'est pas encore là, rien n'empêche de déjà y jeter un oeil! Ainsi vous serez prêt pour la suite.

De plus il faut savoir que la reconnaissance vocale n'est pas quelque chose de lié à la Kinect. Vous pourriez le faire avec n'importe quel autre micro!

Hands-on

Comme toujours, je vous propose de télécharger les sources directement pour suivre plus facilement:

WpfKinect - 5 - Speech Recognition

WpfKinect - 5 - Speech Recognition

Pour cet exemple, on va reprendre un projet dans lequel des sphères étaient dessinées pour représenter les joueurs: un cercle pour chaque main, et un plus grand pour la tête. On n'affiche que les deux joueurs trackés.

A ce programme de base, on va ajouter la possibilité de changer les formes affichées (des cercles ou des carrés), ainsi que la couleur des joueurs indépendamment l'un de l'autre.

Ainsi, le but c'est de pouvoir dire (en anglais bien sûr :) ) "Utiliser des carrés rouges pour le joueur 1" et de voir l'image se mettre à jour en conséquence.

1/ Initialiser le Recognizer

Pour commencer il va falloir ajouter une référence à la librairie Microsoft.Speech.dll. A ne pas confondre avec System.Speech.dll, qui ne vous donnera pas accès au recognizer de la Kinect. Les deux librairies sont très proches l'une de l'autre et sont destinées à ne faire qu'une prochainement.

La doc du SDK propose une méthode helper pour retrouver le recognizer de la Kinect. On a besoin de cette méthode parce que vous avez déjà d'autres SpeechRecognitionEngine, mais nous on veut celui du SDK Kinect.

Cette méthode, elle fait quoi: elle crée une fonction qui prends en paramètre un RecognizerInfo et qui retourne un booléen. Le RecognizerInfo c'est un objet qui va décrire un outil de reconnaissance vocale installé sur votre machine. Cet objet a notamment un attribut de type Dictionary qui contient tout un tas d'informations décrivant le recognizer comme par exemple les cultures et langues supportées, la version, le nom, ou tout autre propriété spécifique à ce recognizer.

Cette fonction va donc retourner true si la propriété Kinect du dictionnary AdditionalInfo contient true, et si la culture est "en-US" (la seule disponible pour le moment).

Et on va ensuite utiliser une requête Linq dans laquelle on va faire appel à cette fonction. Ainsi, on va récupérer la liste de tous les recognizers installés, et on prendra le premier parmi ceux répondant aux critères de la fonction.

        private static RecognizerInfo GetKinectRecognizer()
        {
            Func<RecognizerInfo, bool> matchingFunc = r =>
            {
                string value;
                r.AdditionalInfo.TryGetValue("Kinect", out value);
                return "True".Equals(value, StringComparison.InvariantCultureIgnoreCase)
                    && "en-US".Equals(r.Culture.Name, StringComparison.InvariantCultureIgnoreCase);
            };
            return SpeechRecognitionEngine.InstalledRecognizers().Where(matchingFunc).FirstOrDefault();
        }

On va ajouter une méthode pour instancier le SpeechRecognizerEngine. Si on ne trouve pas de RecognizerInfo, on affiche un message d'erreur. Pareil si on arrive pas à instancier un SpeechRecognitionEngine à partir du RecognizerInfo récupéré.

        private void InitializeSpeechRecognition()
        {
            RecognizerInfo ri = GetKinectRecognizer();

            if (ri == null)
            {
                MessageBox.Show(
                    @"There was a problem initializing Speech Recognition.
Ensure you have the Microsoft Speech SDK installed.",
                    "Failed to load Speech SDK",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                return;
            }

            try
            {
                speechRecognizer = new SpeechRecognitionEngine(ri.Id);
            }
            catch
            {
                MessageBox.Show(
                    @"There was a problem initializing Speech Recognition.
Ensure you have the Microsoft Speech SDK installed and configured.",
                    "Failed to load Speech SDK",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
            if (speechRecognizer == null)
                return;

            // Ajouter la suite ici!
        }
2/ Préparer les mots-clés

Pour nous aider dans la suite, on va associer un ensemble de termes sous forme de chaîne de caractère à des valeurs:

        #region Phrase mapping

        private Dictionary<string, Shape> Shapes = new Dictionary<string, Shape>
        {
            { "Circle", Shape.Circle },
            { "Square", Shape.Square },
        };

        private Dictionary<string, SolidColorBrush> BgrColors = new Dictionary<string, SolidColorBrush>
        {
            { "Yellow", Brushes.Yellow },
            { "Blue", Brushes.Blue },
            { "Red", Brushes.Red },
        };

        private Dictionary<string, int> PlayerIndexes = new Dictionary<string, int>
        {
            { "One", 0 },
            { "Two", 1 },
        };

        #endregion

Notez que l'énumération Shape a été créée pour l'occasion. Ce n'est pas nécessaire, mais ça permet d'y voir plus clair et d'ajouter facilement de nouvelles possibilités.

    public enum Shape
    {
        Circle,
        Square
    }
3/ Créer la sémantique

A la suite du code dans la méthode InitializeSpeechRecognition, on va construire la phrase telle qu'elle devrait être déclarée par l'utilisateur. On va par exemple créer des objets Choices, contenant l'ensemble des valeurs que l'on pourrait s'attendre à recevoir à une position dans la phrase.

A la fin, on construit concrètement la phrase à partir de valeurs fixes, et de choix: ainsi, la phrase commencera toujours par "Use" et pourra ensuite être suivie de n'importe laquelle des couleurs, ensuite de n'importe quelle forme parmi les possibilités données évidemment, etc...

            // Create choices containing values of the lists
            var shapes = new Choices();
            foreach (string value in Shapes.Keys)
                shapes.Add(value);

            var colors = new Choices();
            foreach (string value in BgrColors.Keys)
                colors.Add(value);

            var playerIndexes = new Choices();
            foreach (string value in PlayerIndexes.Keys)
                playerIndexes.Add(value);

            // Describes how the phraze should look like
            var gb = new GrammarBuilder();
            //Specify the culture to match the recognizer in case we are running in a different culture.                                 
            gb.Culture = ri.Culture;
            // It should start with "Use"
            gb.Append("Use");
            // And then we should say any of the colors value
            gb.Append(colors);
            // Then one of the two possible shapes
            gb.Append(shapes);
            // then again the words "for player"
            gb.Append("for player");
            // and finally the player that we want to update
            gb.Append(playerIndexes);

            // Create the actual Grammar instance, and then load it into the speech recognizer.
            var g = new Grammar(gb);

            speechRecognizer.LoadGrammar(g);

Au final, on peut construire des objets très complexes. L'ensemble de l'objet GrammarBuilder aurait pu être ajouté à un nouvelle objet Choices en face d'un second objet GrammarBuilder tout aussi complexe, ou d'un simple autre mot.

4/ Lancer la reconnaissance

Toujours à la suite du code précédent, on va s'abonner aux évènements de notre speech recognizer:

            speechRecognizer.SpeechRecognized += speechRecognizer_SpeechRecognized;
            speechRecognizer.SpeechHypothesized += speechRecognizer_SpeechHypothesized;
            speechRecognizer.SpeechRecognitionRejected += speechRecognizer_SpeechRecognitionRejected;

Et pour finir on va attribuer la source audio (celle de notre Kinect), démarrer cette source, et lancer effectivement la reconnaissance!

            var audioSource = this.Kinect.AudioSource;
            audioSource.BeamAngleMode = BeamAngleMode.Adaptive;
            var kinectStream = audioSource.Start();

            speechRecognizer.SetInputToAudioStream(
                    kinectStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));
            speechRecognizer.RecognizeAsync(RecognizeMode.Multiple);

L'énumération RecognizeMode indique que la reconnaissance doit s'arrêter directement après un premier élément reconnu (Single), ou qu'elle continue jusqu'à ce qu'on l'arrête (Multiple).

5/ Traiter les résultats

Finalement, on va décortiquer ce qu'on pense avoir entendu. Ces deux premiers eventhandlers ne nous intéressent pas vraiment. Le premier, rejected, indique simplement qu'une phrase n'a pas été reconnue (dans le sens où elle ne match pas avec une phrase attendue) avec assez d'assurance. Le second, Hypothesized, indique qu'un mot ou groupe de mots a été reconnu et qu'il correspond à plusieurs phrases possibles.

        void speechRecognizer_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            Console.WriteLine("Rejected: " + e.Result.Text);
        }

        void speechRecognizer_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            Console.WriteLine("Hypothesized: " + e.Result.Text);
        }

L'event qui va nous intéresser est le SpeechRecognized. Il va nous donner un résultat, caractérisé par une propriété Confidence. Cette propriété indique combien ce résultat semble être la bon résultat comparés à d'autres. Les autres résultats possibles peuvent être consultés via la propriété Alternatives, qui est une collection de RecognizedPhrase.

La phrase reconnue en elle-même est stockée dans la propriété Text sous forme d'une chaîne de caractère ou bien dans la propriété Words, sous la forme d'une collection de RecognizedWordUnit. Chacun de ces mots possède également une propriété Confidence.

Dans l'exemple ci-dessous, on va analyser la phrase et modifier les settings d'un joueur en fonction de son contenu.

        void speechRecognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            // Confidence indicates the likelihood that a phrase is recognized correctly
            // compared to alternatives. 0.8 confidence doesn't mean 80% chance of being correct.
            if (e.Result.Confidence < 0.50)
            {
                Console.WriteLine("Rejected: " + e.Result.Text + ", " + e.Result.Confidence);
                if (e.Result.Alternates.Count > 0)
                {
                    // Print the alternatives
                    Console.WriteLine("Alternates available: " + e.Result.Alternates.Count);
                    foreach (RecognizedPhrase alternate in e.Result.Alternates)
                    {
                        Console.WriteLine("Alternate: " + alternate.Text + ", " + alternate.Confidence);
                    }
                }
                return;
            }
            Console.WriteLine("Recognized: " + e.Result.Text + ", " + e.Result.Confidence);

            var index = PlayerIndexes[e.Result.Words[5].Text];

            var playerConfig = playerConfigs[index];

            if (playerConfig != null)
            {
                playerConfig.Brush = BgrColors[e.Result.Words[1].Text];

                playerConfig.Shape = Shapes[e.Result.Words[2].Text];
            }
        }

Kinect SDK 1.0 – 4 – Kinect en profondeur avec le DepthStream

20. April 2012 16:08 by Renaud in   //  Tags:   //   Comments (0)
 1. Introduction à l'API
 2. Utilisation du ColorImageStream
 3. Tracker le squelette avec le SkeletonStream
 4. Kinect en profondeur avec le DepthStream
 5. Reconnaissance vocale

Ce nouvel article va encore une fois mettre en avant une particularité de la Kinect, qui est sa capacité à avoir une vue en 3 dimensions de l'espace.

Cela peut sembler anodin à l'heure où on est inondés de films 3D, mais la technologie utilisée par la Kinect n'est pas du tout la même que celle utilisée dans le cinéma: pour tourner un film en 3D, on utilise de la 3D stéréoscopique. Dans ce cas, ils n'utilisent non pas une mais deux caméras espacées pour reproduire la vision des yeux et obtenir simplement une image pour l'oeil droit et une image pour l'oeil gauche. C'est ensuite votre cerveau qui traite les informations des deux images et apporte une notion de distance.

La Kinect par contre va utiliser un émetteur et un récepteur d'infrarouges qui vont permettre de calculer la distance des points de l'environnement. La Kinect se suffit donc à elle même!

Dans cet article on va voir comment obtenir un DepthImageFrame de la Kinect, et comment l'utiliser pour créer une vidéo en 3 dimension! Au passage on va également avoir un exemple de polling (je vous en avais parlé dans le premier article).

Vous pouvez immédiatement télécharger les sources du projet ici:

Pour les exemples de code qui vont suivre, on va créer un projet XNA! Alors si vous n'êtes pas très familier avec ce framework, ce n'est pas bien grave: moi non plus!

Ce qu'il faut juste savoir ici, c'est que notre application n'est pas event-driven mais est exécutée dans une boucle. Cela veut dire qu'on ne va pas attendre qu'un évènement soit déclenché pour faire une action. Au lieu de ça, à chaque passage dans la boucle on va aller demander de nouvelles informations à la Kinect!

Scène 3D: principe du projet

Comme expliqué plus haut, la Kinect peut donner la profondeur de chaque pixel. Dès lors, il est tout à fait imaginable de représenter l'image vue par la Kinect dans un espace en 3 dimensions, et de changer virtuellement la position de la caméra! On pourrait alors en quelque sorte se déplacer au milieu d'une scène et la voir sous différents angles alors qu'elle n'est pourtant filmée que d'un seul endroit!

Initialisation de la Kinect

On va donc simplement récupérer la Kinect connectée, et activer les différents flux dans le constructeur de Game1 :

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            // Use the first Connected Kinect
            Kinect = KinectSensor.KinectSensors.FirstOrDefault(k => k.Status == KinectStatus.Connected);
            if (Kinect != null)
            {
                // Activate the Near mode
                Kinect.DepthStream.Range = DepthRange.Near;
                // Enable the DepthStream
                Kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
                // ... and instantiate the needed arrays to store some data
                depthPixelData = new short[Kinect.DepthStream.FramePixelDataLength];
                colorCoordinates = new ColorImagePoint[depthPixelData.Length];
                vertexPositionColors = new VertexPositionColor[depthPixelData.Length * 2];

                // Enable the Color Stream
                Kinect.ColorStream.Enable(ColorImageFormat.RgbResolution1280x960Fps12);
                colorFramePixelData = new byte[Kinect.ColorStream.FramePixelDataLength];

                Kinect.Start();
            }
        }

Dans la méthode Update, on va demander explicitement des infos à la Kinect:

            if (Kinect != null)
            {
                // Ask for a DepthFrame
                using (DepthImageFrame depthFrame = Kinect.DepthStream.OpenNextFrame(0))
                {
                    if (depthFrame != null)
                    {
                        // Copy the data
                        depthFrame.CopyPixelDataTo(depthPixelData);

                        // And match each point of the depthframe to a position
                        // in the colorframe that we are about to receive
                        Kinect.MapDepthFrameToColorFrame(depthFrame.Format, depthPixelData, Kinect.ColorStream.Format, colorCoordinates);

                        using (ColorImageFrame colorFrame = Kinect.ColorStream.OpenNextFrame(0))
                        {
                            if (colorFrame != null)
                            {
                                // Copy the data
                                colorFrame.CopyPixelDataTo(colorFramePixelData);
                            }
                        }
                    }
                }
            }

Ici on va utiliser les méthodes OpenNextFrame directement sur les flux qui nous intéressent. La valeur passée en paramètre indique le temps à attendre avant un timeout. Dans ce cas-ci, si une frame n'est pas disponible immédiatement, on ne veut pas attendre.

Ensuite, dans la méthode Draw, on va mettre à jour les points à dessiner.

                // For each pixel index in the colorCoordinates array
                for (int i = 0; i < colorCoordinates.Length; i++)
                {
                    // If the coordinates are in the range of the colorstream frame size
                    if (colorCoordinates[i].X < Kinect.ColorStream.FrameWidth
                        && colorCoordinates[i].Y < Kinect.ColorStream.FrameHeight)
                    {
                        // calculate the X,Y coordinates of the pixel in the depthstream frame
                        var pixelY = (i) / Kinect.DepthStream.FrameHeight;
                        var pixelX = (i) % Kinect.DepthStream.FrameWidth;

                        // Find the corresponding value in the Skeleton referential
                        var skeletonPoint = Kinect.MapDepthToSkeletonPoint(Kinect.DepthStream.Format, pixelX, pixelY, depthPixelData[i]);

                        // Retrieve the first index of the fourth-bytes pixel in the ColorFrame.
                        var baseIndex = (colorCoordinates[i].Y * Kinect.ColorStream.FrameWidth + colorCoordinates[i].X) * 4;

                        // And finally we update the corresponding 
                        vertexPositionColors[i * 2].Color.R = colorFramePixelData[baseIndex + 2];
                        vertexPositionColors[i * 2].Color.G = colorFramePixelData[baseIndex + 1];
                        vertexPositionColors[i * 2].Color.B = colorFramePixelData[baseIndex + 0];
                        vertexPositionColors[i * 2].Position.X = skeletonPoint.X;
                        vertexPositionColors[i * 2].Position.Y = skeletonPoint.Y;
                        vertexPositionColors[i * 2].Position.Z = skeletonPoint.Z;

                        // Use another point right behind the first one
                        vertexPositionColors[i * 2 + 1] = vertexPositionColors[i * 2];
                        vertexPositionColors[i * 2 + 1].Position.Z = skeletonPoint.Z + 0.05f;
                    }
                }

Dans le code ci-dessus, pour chaque pixel que l'on veut imprimer, on récupère sa position réelle dans un référentiel en 3 dimensions grâce à la méthode MapDepthToSkeletonPoint.

Il faut savoir qu'avec XNA 4.0, il n'est pas possible de dessiner un point. Du coup on va dessiner une droite relativement petite à partir de deux points: un premier à la position exacte à laquelle il devrait être selon la Kinect, et un second 5 centimètres derrière.

                foreach (EffectPass effectPass in basicEffect.CurrentTechnique.Passes)
                {
                    effectPass.Apply();

                    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                        PrimitiveType.LineList,
                        vertexPositionColors,
                        0,
                        vertexPositionColors.Length / 2
                    );
                }

Pour terminer, on va simplement dessiner une liste de lignes en se basant sur les points que l'on vient de définir juste avant!

Bouger la caméra

On va d'abord initialiser la caméra au lancement de l'application:

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            basicEffect = new BasicEffect(GraphicsDevice);

            // The position of the camera
            cameraPosition = new Vector3(0, 0, -1);
            // creates the view based on the camera position, the target position, and the up orientation.
            viewMatrix = Matrix.CreateLookAt(cameraPosition, new Vector3(0, 0, 2), Vector3.Up);
            basicEffect.View = viewMatrix;
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f), GraphicsDevice.Viewport.AspectRatio, 1f, 1000f);
            basicEffect.VertexColorEnabled = true;
        }

Et pour faire en sorte de pouvoir bouger la position de la caméra au cours de l'application, on ajoute ceci dans la méthode Update:

           foreach (Keys key in Keyboard.GetState().GetPressedKeys())
            {
                if (key == Keys.D)
                {
                    cameraPosition.X -= 0.01f;
                }
                if (key == Keys.Q)
                {
                    cameraPosition.X += 0.01f;
                }
                if (key == Keys.Z)
                {
                    cameraPosition.Y += 0.01f;
                }
                if (key == Keys.S)
                {
                    cameraPosition.Y -= 0.01f;
                }
                if (key == Keys.E)
                {
                    cameraPosition.Z += 0.01f;
                }
                if (key == Keys.X)
                {
                    cameraPosition.Z -= 0.01f;
                }
            }
            viewMatrix = Matrix.CreateLookAt(cameraPosition, new Vector3(0, 0, 2), Vector3.Up);
            basicEffect.View = viewMatrix;

Ce code va modifier la position de la caméra si on appuie sur les touches indiquées. Par exemple, Z va faire monter la caméra sur l'axe Y, tandis que S va la faire descendre.

Voici une petite vidéo du rendu ;)

TextBox

About the author

I'm a developer, blog writer, and author, mainly focused on Microsoft technologies (but not only Smile). I'm Microsoft MVP Client Development since July 2013.

Microsoft Certified Professional

I'm currently working as an IT Evangelist with an awesome team at the Microsoft Innovation Center Belgique, where I spend time and energy helping people to develop their projects. I also give training to enthusiastic developers and organize afterworks with the help of the Belgian community.

MIC Belgique

Take a look at my first book (french only): Développez en HTML 5 pour Windows 8

Développez en HTML5 pour Windows 8

Membre de l'association Fier d'être développeur

TextBox

Month List