Il y a quelques temps, INEAT a publié un très bon article sur l’API AWS Rekognition que nous vous conseillons de lire ici. Et comme INEAT c’est aussi un partenariat étroit avec Redmond, on s’est demandé si la plateforme Azure ne pouvait pas répondre au même défi.

Microsoft ayant réorganisé son offre “Intelligence Artificielle” il y a peu, c’est également l’occasion d’explorer ce catalogue remis à jour.

La reconnaissance faciale est-elle donc facilement à notre portée chez Microsoft ? Plus que jamais ! Comme précisé dans l’article précédent, on trouve deux grandes familles de solutions : embarquées dans les périphériques et les solutions SaaS.

Azure nous propose les deux approches ; nous détaillerons ici la solution SaaS qui est comparable à AWS Rekognition. Pour explorer les solutions embarquées, nous vous conseillons de rechercher « Azure IoT Edge » et l’article d’introduction d’Emmanuel BERTRAND.

 

Le set d’API Vision permet d’analyser une image et d’en tirer un flux de données qui décrit globalement l’image. On y trouve les fonctions suivantes :

  • Identifier des composants visuels d’une image à l’aide d’un catalogue de plus de 2000 objets, êtres vivants…
  • Catégoriser une image dans son ensemble à travers 86 catégories pré-entrainées (animal, building, abstrait…)
  • Description d’image
  • Détection de visages
  • Détection du type d’image (photo, dessin…)
  • Détection de couleurs
  • Génération de miniatures en zoomant sur la zone d’intérêt
  • Extraction de texte
  • Modération de contenu en détectant les contenus pour adultes ou choquants
  • Détection dans un domaine spécifique : ici il est proposé de détecter des célébrités (Bill Gates entre autres) ou des points de repères (la Tour Eiffel par exemple)

Et comme une vidéo n’est qu’une suite d’images avec une bande son, globalement vous retrouverez tout ce panel de fonctions pour de la vidéo. Nous vous conseillons de faire un tour sur un outil/démo de Microsoft pour vous faire une idée ici (les APIs que cet outil exploite dépassent de loin l’article présent, mais n’hésitez pas à nous contacter sur twitter si ça vous intéresse).

Donc reprenons le besoin de l’article précédent : Un objet capable de reconnaitre des personnes dans un environnement restreint, avec un excellent compromis coût / performance (coût du hardware, de la solution software, latence raisonnable, bonne efficacité…) !

Le hardware sélectionné dans l’article précédent était un smartphone sous Android. De notre côté, nous allons développer un service que vous pourrez réutiliser dans vos applications Xamarin, Console, Web API…

 

Obtention de la clé

Avant de coder, nous allons devoir récupérer la sainte API KEY sans laquelle nous n’irons pas loin.

Il y a de nombreux liens du portail Cognitive Services qui vous mènerons à cette page.

C’est gratuit au moment où nous écrivons ces lignes pour un essai de 7 jours, puis si vous souhaitez upgrader vous avez le choix d’utiliser une souscription Azure classique.

Cliquez sur la (ou les) API de votre choix :

Pour notre article nous utiliserons l’API Visage (un sous-domaine de l’API Vision spécialisé dans le domaine de la détection, l’analyse… des visages).

 

Et vous devriez arriver ici :

On note donc les points de terminaison et les clés ainsi obtenues et enfin on code 😊.

Notre application

Comme précisé précédemment, nous allons développer un service réutilisable.

Ouvrez donc Visual Studio et créez un nouveau projet de type « Bibliothèque de classe », puis ajouter le projet de test associé.

Commençons par les tests maintenant, notre premier objectif sera de savoir si une image contient au moins un visage, on parle ici de détection de visage.

Pour faire simple et interropérable, nous allons utiliser un stream pour définir l’image. Dans les tests nous allons les télécharger depuis internet.

[TestClass]
    public class UnitTest1
    {
        IFaceRecognition _service;

        [TestInitialize()]
        public void TestInitialize()
        {
            var configuration = new Dictionary<string, string>();
            configuration.Add("AZURE_SUBSCRIPTION_KEY", "7e3eb1665f664ebcc1a944c3c61xxx");
            configuration.Add("AZURE_ENDPOINT", "https://westcentralus.api.cognitive.microsoft.com");

            _service = new AzureVisionFaceRecognition(configuration);
        }
        
        [TestMethod]
        public void ImageShouldContainFaceFromImageWithOneFace()
        {
            var containFace = _service.ContainFaceAsync(GetStreamImageFromUri("https://www.statnews.com/wp-content/uploads/2018/04/BillGates_STAT_01-645x645.jpg"));

            Assert.IsTrue(containFace.Result, "L'image doit contenir un visage");
        }

        [TestMethod]
        public void ImageShouldContainFaceFromImageWithFaces()
        {
            var containFace = _service.ContainFaceAsync(GetStreamImageFromUri("https://fortnews.files.wordpress.com/2016/10/wp-1475424126991.jpg"));

            Assert.IsTrue(containFace.Result, "L'image doit contenir un visage");
        }

        [TestMethod]
        public void ImageShouldNotContainFaceFromImageWithNoFace()
        {
            var containFace = _service.ContainFaceAsync(GetStreamImageFromUri("https://animal.cheloniophilie.com/ImagesAnimal/Lapin/Lapin-court.jpg"));

            Assert.IsFalse(containFace.Result, "L'image ne doit pas contenir de visage.");
        }

        public Stream GetStreamImageFromUri(string imageUrl)
        {
            using (WebClient webClient = new WebClient())
            {
                var watch = System.Diagnostics.Stopwatch.StartNew();
                byte[] data = webClient.DownloadData(imageUrl);
                return new MemoryStream(data);
            }
        }

    }

Passons à l’implémentation, et ajoutons maintenant la dépendance au SDK de notre API via Nuget (pour info le SDK est toujours en preview, donc cochez « Inclure la version préliminaire » si vous utiliser le GUI.

PM > Install-Package Microsoft.Azure.CognitiveServices.Vision.Face -Version 2.2.0-preview

Commençons par l’interface de notre service : IFaceRecognition.

    public interface IFaceRecognition
    {

        Task ContainFaceAsync(Stream imageStream);

    }

Maintenant, l’implémentation de ce service pour Azure.

    public class AzureVisionFaceRecognition : IFaceRecognition
    {
        IDictionary<string, string> _configuration;
        IFaceClient _client;
        const string AZURE_SUBSCRIPTION_KEY = "AZURE_SUBSCRIPTION_KEY";
        const string AZURE_ENDPOINT = "AZURE_ENDPOINT";

        public AzureVisionFaceRecognition(IDictionary<string, string> configuration)
        {
            _configuration = configuration;
            var cognitiveServicesCredentials = new ApiKeyServiceClientCredentials(_configuration[AZURE_SUBSCRIPTION_KEY]);
            _client = new FaceClient(cognitiveServicesCredentials);
            _client.Endpoint = configuration[AZURE_ENDPOINT];
        }

        public async Task ContainFaceAsync(Stream imageStream)
        {
            IList faceList = await _client.Face.DetectWithStreamAsync(imageStream, true, false);

            return (faceList != null && faceList.Count > 0);
        }
    }

Et finalement les tests :

Ensuite, nous voudrons savoir si un visage de notre image appartient à une personne en particulier, on parle maintenant d’identification de visage. Pour reconnaître une personne, il faut déjà l’avoir vu ; les IA ne font pas exception à la règle : nous devons donc apprendre à l’API qui est qui.

Pour se faire, nous allons :

  • créer un groupe de personnes
  • ajouter des personnes à ce groupe
  • ajouter des visages à ces personnes (plusieurs par personne)

Cette opération réalisée, nous allons demander à l’API de lancer l’apprentissage sur ces visages et attendre la fin de cet apprentissage pour ensuite lancer des tests d’identification.

        [TestMethod]
        public void CreateOrUpdateGroupeShouldOk()
        {
            _service.CreateOrUpdateGroup(PrepareGroup()).Wait();

            Assert.IsTrue(true);
        }

        [TestMethod]
        public void ImageShouldContainFaceFromGroup()
        {
            var streamLarryPage = GetStreamImageFromUri("https://cdn1.thr.com/sites/default/files/imagecache/portrait_300x450/2015/07/thr_jeff_bezos.jpg");
            var group = PrepareGroup();

            _service.CreateOrUpdateGroup(group).Wait();

            var identifyTask = _service.Identify(group, streamLarryPage);
            Assert.IsNotNull(identifyTask.Result);
            Assert.IsTrue(identifyTask.Result.Count > 0);
            Assert.IsTrue(identifyTask.Result.Any(_ => _.Name == "jeff-bezos"));
        }

Le modèle de données maintenant :

    public class Group
    {

        public string Id { get; set; }

        public List Persons { get; set; }

        public Group()
        {
            Persons = new List();
        }

    }

    public class Person
    {

        public string Name { get; set; }

        public string[] PersonalPictures { get; set; }

    }
    public interface IFaceRecognition
    {

        Task ContainFaceAsync(Stream imageStream);

        Task CreateOrUpdateGroup(Models.Group group);

        Task<List> Identify(Models.Group group, Stream imageStream);
    }

Notre jeu de données pour les tests :

        public Group PrepareGroup()
        {
            var group = new Group();
            group.Id = "gafaceos";

            Person SatyaNadella = new Person {
                Name = "satya-nadella",
                PersonalPictures = new Stream[] {
                    GetStreamImageFromUri("https://img-0.journaldunet.com/pOS_gPNb9SbxN58Yb5rFZfsLkJw=/1280x/smart/ee4eeba6710f41eb9c6ce76626ec3840/ccmcms-jdn/10406460.jpg"),
                    GetStreamImageFromUri("https://assets.bwbx.io/images/users/iqjWHBFdfxIU/iOFF7WwX7RJc/v1/800x-1.jpg"),
                    GetStreamImageFromUri("https://cdn.geekwire.com/wp-content/uploads/2016/04/20160404_Envision_93-630x421.jpg"),
                    GetStreamImageFromUri("https://formiche.net/files/2014/02/0130-satya-nadella-630x420.jpg"),
                    GetStreamImageFromUri("https://winphonemetro.com/files/2014/02/Satya-nadella.jpg")} };
            group.Persons.Add(SatyaNadella);

            Person JeffBezos = new Person
            {
                Name = "jeff-bezos",
                PersonalPictures = new Stream[] {
                    GetStreamImageFromUri("https://specials-images.forbesimg.com/imageserve/5b8eb1b3a7ea434b99d54b36/960x0.jpg?fit=scale"),
                    GetStreamImageFromUri("https://img-0.journaldunet.com/eTpbQzqqrT08eEIUzTHsad-PGvM=/1000x/smart/a4bc2e947c554f20a8381594698206f5/ccmcms-jdn/10742332.jpg"),
                    GetStreamImageFromUri("https://cdn-images-1.medium.com/max/1200/1*3loFzSBz8coL-NNKqGuSvQ.jpeg"),
                    GetStreamImageFromUri("http://d279m997dpfwgl.cloudfront.net/wp/2013/08/0806_bezos-amazon-1000x592.jpg"),
                    GetStreamImageFromUri("https://hbr.org/resources/images/article_assets/2014/10/R1411B_BEZOS.png")}
            };
            group.Persons.Add(JeffBezos);

            Person LarryPage = new Person
            {
                Name = "larry-page",
                PersonalPictures = new Stream[] {
                    GetStreamImageFromUri("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Larry_Page_in_the_European_Parliament%2C_17.06.2009_%28cropped%29.jpg/220px-Larry_Page_in_the_European_Parliament%2C_17.06.2009_%28cropped%29.jpg"),
                    GetStreamImageFromUri("https://timedotcom.files.wordpress.com/2016/06/gettyimages-495426754.jpg"),
                    GetStreamImageFromUri("http://itiran.com/sites/default/files/larry-page-net-worth.jpg"),
                    GetStreamImageFromUri("https://cdn.businessinsider.nl/wp-content/uploads/2017/01/larry-page-google-alphabet-400x320.jpg")}
            };
            group.Persons.Add(LarryPage);

            Person MarkZuckerberg = new Person
            {
                Name = "mark-zuckerberg",
                PersonalPictures = new Stream[] {
                    GetStreamImageFromUri("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/Mark_Zuckerberg_F8_2018_Keynote_%28cropped%29.jpg/220px-Mark_Zuckerberg_F8_2018_Keynote_%28cropped%29.jpg"),
                    GetStreamImageFromUri("https://dyw7ncnq1en5l.cloudfront.net/optim/news/74/74303/mark-zuckerberg.jpg"),
                    GetStreamImageFromUri("http://www.speeli.com/question/56/2015-08-03_945Untitled-1.jpg"),
                    GetStreamImageFromUri("https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Mark_Zuckerberg_em_setembro_de_2014.jpg/260px-Mark_Zuckerberg_em_setembro_de_2014.jpg"),
                    GetStreamImageFromUri("https://s.yimg.com/ny/api/res/1.2/epCJG9ZjitDWii69tYppLw--~A/YXBwaWQ9aGlnaGxhbmRlcjtzbT0xO3c9ODAw/http://media.zenfs.com/en-US/homerun/businessinsider.com/2c06d6672db7961fc3333a6816542fa7")}
            };
            group.Persons.Add(MarkZuckerberg);

            return group;
        }

Nous pouvons maintenant créer notre groupe et lancer l’entrainement :

        public async Task CreateOrUpdateGroup(Group group)
        {
            try
            {
                // Suppression d'un éventuel groupe précédent
                await _client.PersonGroup.DeleteAsync(group.Id);
            }
            catch (Exception)
            {

            }
            
            // Création du groupe
            await _client.PersonGroup.CreateAsync(group.Id, group.Id, group.Id);
            
            foreach (var person in group.Persons)
            {
                // Création d'une personne "vide" dans le groupe
                var currentPerson = await _client.PersonGroupPerson.CreateAsync(group.Id,
                    // Nom de la personne
                    person.Name
                );

                foreach (var personalPictureStream in person.PersonalPictures)
                {
                    await _client.PersonGroupPerson.AddFaceFromStreamAsync(group.Id, currentPerson.PersonId, personalPictureStream);
                }
                
            }

            // Training du modèle
            await _client.PersonGroup.TrainAsync(group.Id);

            // Attente que le modèle soit entrainé
            TrainingStatus trainingStatus = null;
            while (true)
            {
                trainingStatus = await _client.PersonGroup.GetTrainingStatusAsync(group.Id);

                if (trainingStatus.Status != TrainingStatusType.Running)
                {
                    break;
                }

                await Task.Delay(1000);
            }
        }

Et ce n’est que maintenant que nous pouvons lancer une identification sur la base de ce groupe.

        public async Task<List> Identify(Group group, Stream imageStream)
        {
            var persons = new List();
            var faceList = await _client.Face.DetectWithStreamAsync(imageStream, true, false);
            var facesId = faceList.Select(_ => _.FaceId.GetValueOrDefault()).ToArray();

            // Identification des visage détectés précédemment depuis le groupe
            var results = await _client.Face.IdentifyAsync(facesId, group.Id);

            foreach (var identifyResult in results)
            {
                foreach (var identifiedPerson in identifyResult.Candidates.Where(_ => _.Confidence > 0.5))
                {
                    var azureApiPerson = await _client.PersonGroupPerson.GetAsync(group.Id, identifiedPerson.PersonId);
                    var localGroupPerson = group.Persons.FirstOrDefault(_ => _.Name == azureApiPerson.Name);
                    persons.Add(localGroupPerson);
                }
            }

            return persons;
        }

Et voilà le résultat : tous nos tests sont au vert et l’API est certaine à 85% que c’est bien Larry Page sur notre image de test.

En conclusion

Nous avons vu comment utiliser les Azure Cognitives Services pour détecter des visages et reconnaître des visages, mais nous n’avons qu’effleurer la surface des possibilités offertes.

Les informations que les différents services retournent sont bien plus complètes que les simples booléens que nous avons manipulés et les services bien plus fournis que la simple identification. On peut par exemple s’exempter d’apprentissage pour juste regrouper les personnes dans un panel de photographies ou encore récupérer des données sur les personnes (âge, humeur…), nous vous invitons à découvrir les données retournées au runtime du code et les possibilités en étendant notre petit projet de test.

 

Pour aller plus loin deux directions :

  • Plus d’API en SaaS, dans ce cas nous vous conseillons de :
    • Explorer par le code : pas besoin de monter un projet mobile ou d’avoir des photos en local, un simple projet Bibliothèque de classe et un projet de test sont suffisant pour explorer
    • Lire la documentation à votre disposition. Notez que les API évoluent tellement vite que certaines documentations de MSDN sont dépassées (par exemple pour rédiger cet article, nous n’avons pas trouvé de doc “pas à pas” à jour à l’heure où nous écrivons ces lignes).
  • Plus d’embarqué ? Regardez du côté d’Azure IoT Edge et surveillez ce blog (on ne sait jamais 😊)

 

PS : si l’article vous a plu, si vous souhaitez en savoir plus, si vous voulez être accompagné ou simplement avoir accès au code source complet : contactez nous sur Twitter.