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

Cette rubrique montre comment utiliser FaceDetector pour détecter des visages dans une image. FaceTracker est optimisé pour le suivi des visages au fil du temps dans une séquence d’images vidéo.

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

Le code de cet article a été adapté à partir des exemples de détection de visage de base et de suivi des visages de base. Vous pouvez télécharger ces exemples pour voir le code utilisé dans le contexte ou pour utiliser l’exemple 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 toujours.

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

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 membre de classe pour l’objet FaceDetector et pour la liste des objets DetectedFace qui seront détectés dans l’image.

FaceDetector faceDetector;
IList<DetectedFace> detectedFaces;

La détection des visages fonctionne sur un objet SoftwareBitmap qui peut être créé de différentes manières. Dans cet exemple, un FileOpenPicker est utilisé pour permettre à l’utilisateur de choisir un fichier image dans lequel les visages seront détectés. Pour plus d’informations sur l’utilisation des bitmaps logicielles, consultez Imaging.

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 et vous pouvez donc mettre à l’échelle l’image source à une taille plus petite. Cela peut être effectué pendant le décodage en créant un objet BitmapTransform, en définissant les propriétés ScaledWidth et ScaledHeight et en les transmettant à l’appel à GetSoftwareBitmapAsync, qui retourne le SoftwareBitmap décodé et mis à l’échelle.

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 bitmap d’un format en un autre. Cet exemple convertit l’image source au format pixel Gray8 s’il n’est pas déjà dans ce format. Si vous le souhaitez, vous pouvez utiliser les méthodes GetSupportedBitmapPixelFormats et IsBitmapPixelFormatSupported pour déterminer au moment de l’exécution si un format de pixel est pris en charge, si l’ensemble des formats pris en charge est développé dans les versions ultérieures.

// 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 en appelant DetectFacesAsync, en passant la bitmap qui a été mise à l’échelle à une taille raisonnable et convertie en format de pixel pris en charge. Cette méthode retourne une liste d’objets DetectedFace . ShowDetectedFaces est une méthode d’assistance, illustrée ci-dessous, qui dessine des carrés autour des visages dans l’image.

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

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

Veillez à supprimer les objets créés pendant le processus de détection des visages.

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

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

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

Définissez certaines variables membres pour mettre en forme les carrés qui seront dessinés.

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, une nouvelle imageBrush est créée et la source est définie sur un SoftwareBitmapSource créé à partir du SoftwareBitmap représentant l’image source. L’arrière-plan du contrôle Canevas XAML est défini sur le pinceau d’image.

Si la liste des visages transmis à la méthode d’assistance n’est pas vide, parcourez chaque visage dans la liste et utilisez la propriété FaceBox de la classe DetectedFace pour déterminer la position et la taille du rectangle dans l’image qui contient le visage. Étant donné que le contrôle Canvas est très susceptible d’être d’une taille différente de celle de l’image source, vous devez multiplier les coordonnées X et Y et la largeur et la hauteur de FaceBox par une valeur de mise à l’échelle qui correspond au ratio de la taille de l’image source à la taille réelle du contrôle Canvas.

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 souhaitez détecter des visages dans la vidéo, il est plus efficace d’utiliser la classe FaceTracker plutôt que la classe FaceDetector, bien que les étapes d’implémentation soient très similaires. FaceTracker utilise des informations sur les images précédemment traitées pour optimiser le processus de détection.

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

Déclarez une variable de classe pour l’objet FaceTracker . Cet exemple utilise un ThreadPoolTimer pour lancer le suivi des visages à intervalle défini. Un sémaphoreSlim est utilisé pour s’assurer qu’une seule opération de suivi des visages est en cours d’exécution à la fois.

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

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

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’assistance ProcessCurrentVideoFrame est appelée de façon asynchrone par le minuteur. Par conséquent, la méthode appelle d’abord la méthode Wait du sémaphore pour voir si une opération de suivi est en cours et si elle est retournée par la méthode sans essayer de détecter les visages. À la fin de cette méthode, la méthode Release du sémaphore est appelée, ce qui permet à l’appel suivant à ProcessCurrentVideoFrame de continuer.

La classe FaceTracker fonctionne sur des objets VideoFrame. Il existe plusieurs façons d’obtenir un VideoFrame, y compris la capture d’une image d’aperçu à partir d’un objet MediaCapture en cours d’exécution ou en implémentant la méthode ProcessFrame de l’IBasicVideoEffect. Cet exemple utilise une méthode d’assistance non définie qui retourne une trame vidéo, GetLatestFrame, comme espace réservé pour cette opération. Pour plus d’informations 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, consultez Obtenir une image d’aperçu.

Comme avec FaceDetector, FaceTracker prend en charge un ensemble limité de formats de pixels. Cet exemple abandonne la détection des visages si le frame fourni n’est pas au format Nv12.

Appelez ProcessNextFrameAsync pour récupérer une liste d’objets DetectedFace représentant les visages dans le cadre. Une fois que vous avez 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, étant donné que la méthode d’assistance de suivi des visages n’est pas appelée sur le thread d’interface utilisateur, vous devez effectuer des mises à jour d’interface utilisateur dans un appel CoreDispatcher.RunAsync.

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();
}