Loupe

Mise en place de la reconnaissance faciale grâce aux Cognitive Services

Personnellement, j'ai toujours été fan des films dans lesquels un acteur positionne sa tête devant une caméra et hop, comme par magie, le sytème le reconnait automatiquement et lui permet d'entrer dans la pièce secrète.

Ou, sans parler de films, ce que propose depuis quelques temps Microsoft grâce à la fonctionnalité Windows Hello qui permet à un utilisateur d'être automatiquement reconnu et loggé sur sa session grâce à son visage:

Il s'avère que la mise en place d'une telle fonctionnalité dans une application est relativement simple grâce à l'utilisation des Cognitive Services (dont nous avons déjà parlé ici, ou encore ) et c'est justement ce que nous allons voir au travers de cet article, grâce à la Face API!

 

 Enregistrement des utilisateurs

Après avoir rajouté une référence au package Nuget Microsoft.ProjectOxford.Face, l'étape suivante consiste à enregistrer les utilisateurs avec, pour chacun, une liste de photos qui permettra au moteur de s'entrainer à les reconnaitre.

 Pour cela, nous allons créer un groupe qui contiendra l'ensemble des personnes que nous voulons autoriser:

Le code permettant de créer un groupe est simple grâce à l'API proposée par Microsoft:

string RegisteredUserGroupId = "registeredusers";

var personGroup = await _faceServiceClient.ListPersonGroupsAsync();
if (personGroup == null || personGroup.All(pg => pg.PersonGroupId != RegisteredUserGroupId))
{
     // Create the person group if it does not exist
     await _faceServiceClient.CreatePersonGroupAsync(RegisteredUserGroupId, "Registered users");
}

Le groupe étant créé, nous pouvons maintenant y ajouter des utilisateurs:

// Create a new registered user
var currentUser = "Tom";
_registeredUserAddedResults = await _faceServiceClient.CreatePersonAsync(RegisteredUserGroupId, currentUser);

L'utilisateur étant à présent créé, nous devons lui ajouter des "faces", autrement dit des images de visage correspondant à la personne en question. Ces images seront utilisées par le moteur du service pour s'entrainer à détecter l'utilisateur sur de nouvelles images (sachant que, plus vous ajoutez d'images au modèle, plus vous maximisez vos chances que votre modèle soit bien entrainé et arrive à détecter la personne sur différents types de photos):

var currentBitmap = Preview.GetCurrentImage();
using (var imageStream = new MemoryStream())
{
    currentBitmap.Save(imageStream, ImageFormat.Jpeg);

    if (imageStream.CanSeek)
    {
        imageStream.Seek(0, SeekOrigin.Begin);
    }
                    
    // Ajout la "face" au modèle, pour la personne créée précédemment
    await _faceServiceClient.AddPersonFaceAsync(RegisteredUserGroupId, _registeredUserAddedResults.PersonId, imageStream);
}

 

Entrainement du modèle

A chaque fois que vous ajoutez ou supprimez des personnes (ou des images de reconnaissance de ces personnes), il est nécessaire d'entrainer le moteur pour qu'il puisse se mettre à jour. Cela se fait via un appel à la méthode TrainPersonGroupAsync:

await _faceServiceClient.TrainPersonGroupAsync(RegisteredUserGroupId);

En fonction de la "complexité" de votre modèle, son entrainement peut prendre du temps du coup, vous pouvez suivre son étant d'avancement via la méthode GetPersonGroupTrainingStatusAsync:

await Task.Factory.StartNew(async () =>
{
    while (true)
    {
        var trainingStatus = await _faceServiceClient.GetPersonGroupTrainingStatusAsync(RegisteredUserGroupId);

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

        await Task.Delay(1000);
    }
});

 

Identification des utilisateurs

Une fois les utilisateurs ajoutés puis le modèle entrainé, il est très simple d'identifier les utilisateurs via la méthode DetectAsync qui permet de retourner la liste des "faces" détecter sur une image. Dans notre cas, nous voulons rajouter une compléxité car nous voulons faire cette identification non pas à partir d'une image simple mais à partir du flux vidéo d'une webcam/caméra. 

Après quelques rapides recherches, je suis tombé sur un GitHub des Cognitive Services qui explique comment analyser les trames vidéos en (presque) temps-réel, grâce à une bibliothèque nommée VideoFrameAnalyzer (qui s'appuie sur OpenCV).

Son utilisation est relativement simple. En effet, d'abord, il faut s'abonner à l'évènement NewFrameProvided qui est déclenché chaque fois qu'une trame vidéo est reconnue (dans notre cas, c'est utilisé pour récupérer une image à partir de la trame vidéo, afin de l'afficher dans l'application):

var grabber = new FrameGrabber<Face[]>();

grabber.NewFrameProvided += (s, e) =>
{
    Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
    {
         ResultImage.Source = e.Frame.Image.ToBitmapSource();
    }));
};

Ensuite, il faut fournir une AnalysisFunction, à savoir une méthode qui permettra, sur chaque trame vidéo, de détecter les différents visages:

grabber.AnalysisFunction = async frame =>
{
    return await _faceServiceClient.DetectAsync(frame.Image.ToMemoryStream(".jpg"));
};

Enfin, nous nous abonnons à l'évènement NewResultAvailable qui est déclenché à chaque fois que des résultats sont retournés par l'API ou par la fonction d'analyse. C'est ici que le gros du travail est effectué car on va:

  • Récupérer la liste des visages retournés par la méthode d'analyse
  • Appeler l'API permettant d'identifier les visages retournés précédemment
  • Récupérer les infos (dont le nom) de la personne reconnue
  • Afficher le résultat en encadrant le visage de la personne et en y ajoutant son nom (cette opération utilisant l'image générée à partir de la trame vidéo, la position du visage et le nom de la personne)

 Voici, d'un point de vue "code", ce que cela donne:

grabber.NewResultAvailable += async (s, e) =>
{
    await grabber.StopProcessingAsync();

    if (e.TimedOut)
    {
        Trace.WriteLine("API call timed out.");
    }
    else if (e.Exception != null)
    {
        Trace.WriteLine("API call threw an exception.");
    }
    else
    {
        if (e.Analysis != null && e.Analysis.Any())
        {
            var faces = e.Analysis;
            var faceIds = faces.Select(face => face.FaceId).ToArray();

            await Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
            {
                AnalysisResultImage.Source = VisualizeResult(e.Frame);
            }));

            var results = await _faceServiceClient.IdentifyAsync(RegisteredUserGroupId, faceIds);
            foreach (var identifyResult in results)
            {
                if (identifyResult.Candidates.Length == 0)
                {
                    await Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                    {
                        this.StatusText.Text = "Identification failed!";

                        AnalysisResultImage.Source = VisualizeResult(e.Frame, faces);
                    }));
                }
                else
                {
                    var candidateId = identifyResult.Candidates[0].PersonId;
                    var person = await _faceServiceClient.GetPersonAsync(RegisteredUserGroupId, candidateId);

                    await Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                    {
                        this.StatusText.Text = "Identification ended!";

                        AnalysisResultImage.Source = VisualizeResult(e.Frame, faces, new[] {person.Name});
                    }));
                }
            }
        }
        else
        {
            await Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
            {
                this.StatusText.Text = "No faces detected!";
            }));
        }
    }
};

private BitmapSource VisualizeResult(VideoFrame frame, Face[] faces = null, string[] registeredUsers = null)
{
    // Draw any results on top of the image. 
    BitmapSource visImage = frame.Image.ToBitmapSource();
            
    if (faces != null)
    {
        visImage = Visualization.DrawFaces(visImage, faces, registeredUsers);
    }

    return visImage;
}

Et le tour est joué! Il ne vous reste plus qu'à lancer l'application, ajouter des utilisateurs, entrainer votre modèle puis détecter les personnes, comme vous pouvez le voir sur la vidéo ci-dessous:

De cette manière, vous avez tous les éléments pour intégrer cette fonctionnalité dans votre application (le complément idéal dans un contexte IoT par exemple)

Pour en savoir plus sur la Face API, vous pouvez consulter les liens suivants:

 

Happy coding! :)

 

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus