UWP: Détecter les visages dans des images ou des vidéos

In Mobile Development by Christian HissibiniLeave a Comment

Dans cet article, nous verrons comment utiliser FaceDetector pour détecter des visages dans une image. FaceTracker est optimisé pour suivre les visages au fil du temps dans une séquence d’images vidéo.

Pour une autre méthode de suivi des visages à l’aide de FaceDetectionEffect, voir Analyse de scène pour la capture multimédia.

Le code contenu dans cet article a été adapté à partir des exemples Détection des visages de base et Suivi des visages de base. Vous pouvez télécharger ces exemples pour voir le code utilisé en contexte ou pour vous en servir comme point de départ pour votre propre application.

Détecter des visages dans une seule image

La classe FaceDetector vous permet de détecter un ou plusieurs visages dans une image fixe.

Cet exemple utilise des API à partir des espaces de noms suivants.C#Copier

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Windows.Media.FaceAnalysis;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Shapes;

Déclarez une variable de membre de classe pour l’objet FaceDetector et la liste d’objets DetectedFace qui seront détectés dans l’image.C#Copier

FaceDetector faceDetector;
IList<DetectedFace> detectedFaces;

La détection des visages fonctionne sur un objet SoftwareBitmap qui peut être créé de diverses manières. Dans cet exemple un élément FileOpenPicker est utilisé pour permettre à l’utilisateur de choisir un fichier image dans lequel des visages sont détectés.Pour plus d’informations sur l’utilisation d’images bitmap de logiciel, voir Acquisition d’images.C#Copier

FileOpenPicker photoPicker = new FileOpenPicker();
photoPicker.ViewMode = PickerViewMode.Thumbnail;
photoPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
photoPicker.FileTypeFilter.Add(".jpg");
photoPicker.FileTypeFilter.Add(".jpeg");
photoPicker.FileTypeFilter.Add(".png");
photoPicker.FileTypeFilter.Add(".bmp");

StorageFile photoFile = await photoPicker.PickSingleFileAsync();
if (photoFile == null)
{
    return;
}

Utilisez la classe BitmapDecoder pour décoder le fichier image dans un SoftwareBitmap. Le processus de détection des visages est plus rapide avec une image plus petite, c’est pourquoi vous voudrez peut-être réduire la taille de l’image source. Cette opération peut être réalisée lors du décodage en créant un objet BitmapTransform, en définissant les propriétés ScaledWidth et ScaledHeightet en les transmettant à l’appel à GetSoftwareBitmapAsync, qui renvoie l’élément SoftwareBitmap décodé et mis à l’échelle.C#Copier

IRandomAccessStream fileStream = await photoFile.OpenAsync(FileAccessMode.Read);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);

BitmapTransform transform = new BitmapTransform();
const float sourceImageHeightLimit = 1280;

if (decoder.PixelHeight > sourceImageHeightLimit)
{
    float scalingFactor = (float)sourceImageHeightLimit / (float)decoder.PixelHeight;
    transform.ScaledWidth = (uint)Math.Floor(decoder.PixelWidth * scalingFactor);
    transform.ScaledHeight = (uint)Math.Floor(decoder.PixelHeight * scalingFactor);
}

SoftwareBitmap sourceBitmap = await decoder.GetSoftwareBitmapAsync(decoder.BitmapPixelFormat, BitmapAlphaMode.Premultiplied, transform, ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);

Dans la version actuelle, la classe FaceDetector prend uniquement en charge les images dans Gray8 ou Nv12. La classe SoftwareBitmap fournit la méthode Convert, qui convertit une image bitmap d’un format vers un autre. Cet exemple convertit l’image source au format de pixel Gray8 si elle n’est pas déjà dans ce format. Si vous le souhaitez, vous pouvez utiliser les méthodes GetSupportedBitmapPixelFormats et IsBitmapPixelFormatSupported pour déterminer lors de l’exécution si un format de pixel est pris en charge, au cas où l’ensemble des formats pris en charge est étendu dans les prochaines versions.C#Copier

// Use FaceDetector.GetSupportedBitmapPixelFormats and IsBitmapPixelFormatSupported to dynamically
// determine supported formats
const BitmapPixelFormat faceDetectionPixelFormat = BitmapPixelFormat.Gray8;

SoftwareBitmap convertedBitmap;

if (sourceBitmap.BitmapPixelFormat != faceDetectionPixelFormat)
{
    convertedBitmap = SoftwareBitmap.Convert(sourceBitmap, faceDetectionPixelFormat);
}
else
{
    convertedBitmap = sourceBitmap;
}

Instanciez l’objet FaceDetector en appelant CreateAsync, puis appelez DetectFacesAsync, en transmettant l’image bitmap mise à l’échelle à une taille raisonnable et convertie en un format de pixel pris en charge. Cette méthode renvoie une liste d’objets DetectedFaceShowDetectedFaces est une méthode d’assistance, indiquée ci-dessous, qui dessine des cadres autour des visages de l’image.C#Copier

if (faceDetector == null)
{
    faceDetector = await FaceDetector.CreateAsync();
}

detectedFaces = await faceDetector.DetectFacesAsync(convertedBitmap);
ShowDetectedFaces(sourceBitmap, detectedFaces);

Veillez à supprimer les objets qui ont été créés au cours du processus de détection des visages.C#Copier

sourceBitmap.Dispose();
fileStream.Dispose();
convertedBitmap.Dispose();

Pour afficher l’image et dessiner des cadres autour des visages détectés, ajoutez un élément Canvas à votre page XAML.XMLCopier

<Canvas x:Name="VisualizationCanvas" Visibility="Visible" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

Définissez des variables de membre pour styliser les cadres dessinés.C#Copier

private readonly SolidColorBrush lineBrush = new SolidColorBrush(Windows.UI.Colors.Yellow);
private readonly double lineThickness = 2.0;
private readonly SolidColorBrush fillBrush = new SolidColorBrush(Windows.UI.Colors.Transparent);

Dans la méthode d’assistance ShowDetectedFaces, un nouvel élément ImageBrush est créé et la source est définie sur un élément SoftwareBitmapSource créé à partir de SoftwareBitmap représentant l’image source. L’arrière-plan du contrôle Canvas XAML est défini sur le pinceau image.

Si la liste des visages transmise à la méthode d’assistance n’est pas vide, passez en boucle sur chaque visage de la liste et utilisez la propriété FaceBox de la classe DetectedFace pour déterminer la position et la taille du rectangle au sein de l’image qui contient le visage. Dans la mesure où la taille du contrôle Canvas est très probablement différente de celle de l’image source, vous devez multiplier les coordonnées X et Y, ainsi que la largeur et la hauteur de FaceBox par une valeur de mise à l’échelle qui est le rapport entre la taille de l’image source et la taille réelle du contrôle Canvas.C#Copier

private async void ShowDetectedFaces(SoftwareBitmap sourceBitmap, IList<DetectedFace> faces)
{
    ImageBrush brush = new ImageBrush();
    SoftwareBitmapSource bitmapSource = new SoftwareBitmapSource();
    await bitmapSource.SetBitmapAsync(sourceBitmap);
    brush.ImageSource = bitmapSource;
    brush.Stretch = Stretch.Fill;
    this.VisualizationCanvas.Background = brush;

    if (detectedFaces != null)
    {
        double widthScale = sourceBitmap.PixelWidth / this.VisualizationCanvas.ActualWidth;
        double heightScale = sourceBitmap.PixelHeight / this.VisualizationCanvas.ActualHeight;

        foreach (DetectedFace face in detectedFaces)
        {
            // Create a rectangle element for displaying the face box but since we're using a Canvas
            // we must scale the rectangles according to the image’s actual size.
            // The original FaceBox values are saved in the Rectangle's Tag field so we can update the
            // boxes when the Canvas is resized.
            Rectangle box = new Rectangle();
            box.Tag = face.FaceBox;
            box.Width = (uint)(face.FaceBox.Width / widthScale);
            box.Height = (uint)(face.FaceBox.Height / heightScale);
            box.Fill = this.fillBrush;
            box.Stroke = this.lineBrush;
            box.StrokeThickness = this.lineThickness;
            box.Margin = new Thickness((uint)(face.FaceBox.X / widthScale), (uint)(face.FaceBox.Y / heightScale), 0, 0);

            this.VisualizationCanvas.Children.Add(box);
        }
    }
}

Suivre les visages dans une séquence d’images

Si vous voulez détecter les visages d’une vidéo, il est plus efficace d’utiliser la classe FaceTracker que la classe FaceDetector, bien que les étapes d’implémentation soient très similaires. FaceTracker utilise les informations d’images traitées précédemment pour optimiser le processus de détection.C#Copier

using Windows.Media;
using System.Threading;
using Windows.System.Threading;

Déclarez une variable de classe pour l’objet FaceTracker. Cet exemple utilise un élément ThreadPoolTimer pour lancer un suivi des visages sur un intervalle défini. SemaphoreSlim est utilisé pour s’assurer qu’une seule opération de détection des visages s’exécute à la fois.C#Copier

private FaceTracker faceTracker;
private ThreadPoolTimer frameProcessingTimer;
private SemaphoreSlim frameProcessingSemaphore = new SemaphoreSlim(1);

Pour initialiser l’opération de suivi des visages, créez un nouvel objet FaceTracker en appelant CreateAsync. Initialisez l’intervalle de minuterie souhaité, puis créez le minuteur. La méthode d’assistance ProcessCurrentVideoFrame est appelée dès que l’intervalle spécifié est écoulé.C#Copier

this.faceTracker = await FaceTracker.CreateAsync();
TimeSpan timerInterval = TimeSpan.FromMilliseconds(66); // 15 fps
this.frameProcessingTimer = Windows.System.Threading.ThreadPoolTimer.CreatePeriodicTimer(new Windows.System.Threading.TimerElapsedHandler(ProcessCurrentVideoFrame), timerInterval);

L’application d’assistance ProcessCurrentVideoFrame est appelée de manière asynchrone par le minuteur, si bien qu’elle appelle d’abord la méthode Wait du sémaphore pour voir si une opération de suivi est en cours, et si tel est le cas, la méthode revient sans tenter de détecter les visages. À la fin de cette méthode, la méthode Release du sémaphore est appelée, ce qui permet la poursuite de l’appel consécutif à ProcessCurrentVideoFrame.

La classe FaceTracker fonctionne sur les objets VideoFrame. Vous pouvez obtenir un élément VideoFrame en capturant une image d’aperçu à partir d’un objet MediaCapture en cours d’exécution ou en implémentant la méthode ProcessFrame de IBasicVideoEffect.Cet exemple utilise une méthode d’assistance non définie qui renvoie une image vidéo, GetLatestFrame, sous la forme d’un espace réservé pour cette opération. Pour en savoir plus sur l’obtention d’images vidéo à partir du flux d’aperçu d’un appareil de capture multimédia en cours d’exécution, voir Obtenir une image d’aperçu.

Comme avec FaceDetectorFaceTracker prend en charge un ensemble limité de formats de pixels. Cet exemple abandonne la détection des visages si l’image fournie n’est pas au format Nv12.

Appelez ProcessNextFrameAsync pour récupérer une liste d’objets DetectedFace représentant les visages dans l’image. Une fois que vous disposez de la liste des visages, vous pouvez les afficher de la même manière que celle décrite ci-dessus pour la détection des visages. Notez que dans la mesure où la méthode d’assistance de suivi des visages n’est pas appelée sur le thread d’interface utilisateur, vous devez effectuer les mises à jour de l’interface utilisateur dans une méthode CoredDispatcher.RunAsync d’appel.C#Copier

public async void ProcessCurrentVideoFrame(ThreadPoolTimer timer)
{
    if (!frameProcessingSemaphore.Wait(0))
    {
        return;
    }

    VideoFrame currentFrame = await GetLatestFrame();

    // Use FaceDetector.GetSupportedBitmapPixelFormats and IsBitmapPixelFormatSupported to dynamically
    // determine supported formats
    const BitmapPixelFormat faceDetectionPixelFormat = BitmapPixelFormat.Nv12;

    if (currentFrame.SoftwareBitmap.BitmapPixelFormat != faceDetectionPixelFormat)
    {
        return;
    }

    try
    {
        IList<DetectedFace> detectedFaces = await faceTracker.ProcessNextFrameAsync(currentFrame);

        var previewFrameSize = new Windows.Foundation.Size(currentFrame.SoftwareBitmap.PixelWidth, currentFrame.SoftwareBitmap.PixelHeight);
        var ignored = this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            this.SetupVisualization(previewFrameSize, detectedFaces);
        });
    }
    catch (Exception e)
    {
        // Face tracking failed
    }
    finally
    {
        frameProcessingSemaphore.Release();
    }

    currentFrame.Dispose();
}

Leave a Comment