Lors de la journée du Lab sur les Cognitives Services de Microsoft, nous avons développé Make A Face, une application web ASP.NET MVC hébergée sur Azure. Cette application utilise l’Emotion API et propose à l’utilisateur de reproduire une des huit émotions suivantes : la colère, le bonheur, la surprise, le mépris, le dégoût, la peur ou la neutralité. Après que l’utilisateur ait soumis une photo, Make A Face lui attribue une note de 0 à 100 et intègre le score dans un classement. Dans l’ensemble, le site fonctionne plutôt bien !

Mais alors que nous avions bien avancé sur le projet, j’ai pensé qu’une application mobile fournirait un moyen plus facile à l’utilisateur de soumettre une photo. C’est pourquoi je me suis lancé pendant quelques heures dans le développement d’un POC avec Xamarin.Forms. Petit challenge technique à l’horizon !

Emotions détectés sur l'application mobile Make A Face

Accès au compte Cognitives Services et à l’Emotion API

À ce jour, il existe deux façons d’obtenir une clef de l’API Cognitives Services :

Récupérez votre clef API, puis dans votre projet PCL de votre application, ajoutez le package NuGet Microsoft.ProjectOxford.Emotion. Nous contacterons l’API directement depuis l’application mobile. Dans un “vrai” projet, il y aurait eu un service web intermédiaire qui s’occuperait de l’accès à l’Emotion API pour plus de sécurité.

La clef de l’Emotion API doit être passée au constructeur du client :

var emotionApi = new EmotionServiceClient("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

compare-azure-cognitive-websites

Détection des émotions depuis une photo

Il est très facile d’utiliser L’Emotion API. Vous verrez que quelques lignes de code suffisent à soumettre une photo et récupérer les coordonnées des visages et les émotions associées.

La photo peut être transmise sous forme d’un tableau d’octets, d’une URL ou d’un stream.

Stream imageStream = // ...

// Call Emotion API
var emotionApi = new EmotionServiceClient(Constants.AzureEmotionApiKey);
var emotions = await emotionApi.RecognizeAsync(imageStream);

foreach (var emotion in emotions)
{
    var top = emotion.FaceRectangle.Top; // Left, Width, Height in pixels
    var anger = emotions[0].Scores.Anger; // Happiness, etc. from 0.0 to 1.0)
}

Prise de la photo

L’accès à l’appareil photo et la manipulation d’images sont des fonctionnalités qui ne peuvent pas être implémentées avec Xamarin.Forms. Il faut donc les développer pour chacune des plateforme cibles. Dans cet article, nous allons nous concentrer sur Android uniquement.

Le composant Xamarin.Mobile fournit un accès rapide aux fonctionnalités natives du téléphone (caméra, géolocalisation, agenda). La création d’une abstraction via l’utilitaire DependencyService permettrait de faire communiquer le projet natif et le projet cross-platform.

// Retrieve the platform-specific implementation that uses Xamarin.Mobile
var camera = DependencyService.Get<ICameraService>();

using (var media = await camera.TakePhotoAsync())
{
    var imageStream = media.GetStream();
    // ...
}

Mon implémentation Android de ICameraService effectue les opérations ci-dessous. On retrouve une logique similaire dans la librairie MvvmCross. Il faut donc :

  • Créer une activité  qui ouvre l’application appareil photo grâce au composant Xamarin.Mobile
  • Attendre un appel de la méthode OnActivityResult avec les informations sur la photo
  • Transmettre ces informations sous forme d’évènement à l’implémentation Android de ICameraService
  • Dans cette implémentation, capter l’évènement et le transformer en résultat de tâche grâce à la classe TaskCompletionSource
  • Dans l’ensemble, garantir la synchronisation des threads avec la classe Interlocked

Xamarin.Mobile, une librairie multi-plateforme pour accéder aux fonctionnalités natives du téléphone

Intégration des résultats sur la photo

Pour ce POC, l’affichage du résultat consiste à montrer la photo de l’utilisateur avec les visages détectés encadrés. Dans ce cadre, on peut lire le nom de l’émotion la plus fortement ressentie.

Puisqu’il s’agit de manipulation d’image et que chaque plateforme (Android, iOS, Windows Phone) fournit une façon différente de travailler avec des images, il faut encore passer par une abstraction et plusieurs implémentations spécifiques.

// Interface
byte[] ApplyEmotionsToImage(byte[] imageBytes, IEnumerable<EmotionRectangleModel> emotions);

// Android implementation
public byte[] ApplyEmotionsToImage(byte[] imageBytes, IEnumerable<EmotionRectangleModel> emotions)
{
    var lineStyle = // new Paint(...
    var textStyle = // new Paint(...
    
    // Using Android specific classes for image manipulation
    using (var readonlyImage = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length))
    using (var mutableImage = readonlyImage.Copy(Bitmap.Config.Argb8888, true))
    using (var canvas = new Canvas(mutableImage))
    {
        canvas.DrawBitmap(mutableImage, 0, 0, lineStyle);

        // Draw the face rectangles and strongest emotion names
        foreach (var emo in emotions)
        {
            var emoName = emo.GetStrongestEmotionName();

            canvas.DrawRect(emo.Left, emo.Top, emo.Right, emo.Bottom, lineStyle);
            canvas.DrawText(emoName, emo.Left + 25, emo.Top + 60, textStyle);
        }

        // Return modified picture as 90% quality JPEG bytes
        using (var ms = new MemoryStream())
        {
            mutableImage.Compress(Bitmap.CompressFormat.Jpeg, 90, ms);
            return ms.ToArray();
        }
    }
}

Affichage du résultat et conclusion

Maintenant qu’on a récupéré l’image modifiée dans le projet Xamarin.Forms, il suffit de l’afficher.

// ResultImage's type is a Xamarin.Forms.Image
ResultImage.Source = ImageSource.FromStream(() => new MemoryStream(resultImageBytes));

En quelques heures, nous avons créé une application mobile Xamarin.Forms qui permet de reconnaitre les émotions de personnes figurant sur une photo. Elle est loin d’être parfaite, mais le résultat est assez impressionant.

Ne ratez plus aucunes actualités avec la newsletter mensuelle de SoftFluent