Loupe

Détection de visages sur une vidéo avec Azure Media Services et FFMPEG

Dans mon précédent article, je vous ai expliqué comment concaténer 2 vidéos avec Azure Media Services. Nous allons maintenant voir comment utiliser cet outil et FFMPEG pour faire de la détection de visages sur une vidéo.

La première étape consiste à obtenir les informations concernant la détection des visages, à savoir leur position et leur taille. Pour cela, vous pouvez utiliser OpenCV ou bien Azure Media Services, que ce soit de manière programmatique ou bien via le portail Azure :

Portal.PNG

Une fois le job Media Services terminé, vous obtenez un fichier JSON :

File.PNG

Ce fichier, une fois exposé via un "locator", peut être téléchargé afin de visualiser son contenu qui expose la durée de la vidéo, sa hauteur, sa largeur ainsi que tout les moments où un visage a été détecté :

json.PNG

A partir de ces informations, nous sommes en mesure d'utilsier FFMPEG pour appliquer, sur la vidéo, un calque permettant d'entourer les visages. Pour cela, il convient de télécharger le fichier généré par Azure Media Services puis de l'utiliser pour générer la chaîne de caractères qui sera passée à la ligne de commande de FFMPEG pour appliquer les overlays. 

Cette chaine de caractères est relativement simple à comprendre (avec la documentation) et en voici un aperçu :

drawbox=enable='eq(n,15)' : x=414 : y=268 : w=105 : h=105 : color=red

Cette ligne permet de dire: "Dessine un rectangle sur la frame n°15, aux coordonnées 414,268, de taille 105x105 et de couleur rouge". Vous l'aurez donc compris, l'idée est de développer un petit algorithme qui va récupérer les informations généré par Media Services pour appliquer, sur chaque frame nécessaire, l'overlay que nous aurons définit :

var drawBoxArgs = string.Empty;

using (var httpClient = new HttpClient())
{
    var fileContent = await httpClient.GetStringAsync("MY_URL");
    var rootObject = JsonConvert.DeserializeObject<RootObject>(fileContent);

    foreach (var fragment in rootObject.Fragments)
    {
        if (fragment.Events == null)
            continue;

        for (int i = 0; i <= fragment.Events.Length - 1; i++)
        {
            var videoEvent = fragment.Events.ElementAt(i);
            if (!videoEvent.Any())
                continue;

            if (!string.IsNullOrWhiteSpace(drawBoxArgs))
            {
                drawBoxArgs = string.Concat(drawBoxArgs, ",");
            }

            // Get image framerate
            // Formula: (Start/Timescale) * Framerate
            // where Start = Start + eventIndex * Interval
            // Source: https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-face-and-emotion-detection
            double imageFrame = ((fragment.Start + (i * fragment.Interval)) / Convert.ToDouble(rootObject.Timescale)) * rootObject.Framerate;
            var x = Math.Round(videoEvent[0].X * rootObject.Width, 0, MidpointRounding.ToEven);
            var width = Math.Round(videoEvent[0].Width * rootObject.Width, 0, MidpointRounding.ToEven);
            var y = Math.Round(videoEvent[0].Y * rootObject.Height, 0, MidpointRounding.ToEven);
            var height = Math.Round(videoEvent[0].Height * rootObject.Height, 0, MidpointRounding.ToEven);

            drawBoxArgs = string.Concat(drawBoxArgs, $"drawbox=enable='eq(n,{imageFrame})' : x={x} : y={y} : w={width} : h={height} : color=red");
        }
    }
}

Comme vous pouvez le constater, la formule pour récupérer la frame d'une image est assez "tordue" car, dans le fichier généré, ce ne sont pas des secondes qui sont utilisés mais des "ticks", la valeur de la propriété timescale représentant le nombre de ticks dans une seconde.

A partir de maintenant, il ne reste plus qu'à générer un fichier (nécessaire quand la ligne de commande passé à FFMPEG est trop grande) et utilisée l'option filter_complex_script :

var drawBoxArgsFile = string.Concat(Guid.NewGuid().ToString(), ".txt");
if (!string.IsNullOrWhiteSpace(drawBoxArgs))
{
    File.WriteAllText(string.Concat(context.FunctionAppDirectory, "/", drawBoxArgsFile), drawBoxArgs);
}

ffmpegProcessStartInfo.Arguments = "-loglevel error -y -i " + copyFileName + " -filter_complex_script " + drawBoxArgsFile + " " + transformedFileName;

Et voilà ! Une fois le traitement lancé, vous obtenez une vidéo qui est aggrémentée d'overlays rouges, sur chaque frame où un visage a été détecté par Azure Media Services :

On remarque qu'il y a quelques coquilles par moment, l'algorithme de positionnement des overlays a besoin d'être optimisé mais cela vous donne un bon aperçu !

 

Happy coding :)

 

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus