Detectar rostos em imagens ou vídeos

Este tópico mostra como usar o FaceDetector para detectar rostos em uma imagem. O FaceTracker é otimizado para rastrear rostos ao longo do tempo em uma sequência de quadros de vídeo.

Para obter um método alternativo de rastreamento de rostos usando o FaceDetectionEffect, consulte Análise de cena para captura de mídia.

O código neste artigo foi adaptado dos exemplos de Detecção Facial Básica e Rastreamento Facial Básico. Você pode baixar esses exemplos para ver o código usado no contexto ou para usar o exemplo como ponto de partida para seu próprio aplicativo.

Detecte rostos em uma única imagem

A classe FaceDetector permite detectar um ou mais rostos em uma imagem estática.

Este exemplo usa APIs dos namespaces a seguir.

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;

Declare uma variável de membro de classe para o objeto FaceDetector e para a lista de objetos DetectedFace que serão detectados na imagem.

FaceDetector faceDetector;
IList<DetectedFace> detectedFaces;

A detecção facial opera em um objeto SoftwareBitmap que pode ser criado de várias maneiras. Neste exemplo, um FileOpenPicker é usado para permitir que o usuário escolha um arquivo de imagem no qual os rostos serão detectados. Para obter mais informações sobre como trabalhar com bitmaps de software, consulte Geração de imagens.

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

Use a classe BitmapDecoder para decodificar o arquivo de imagem em um SoftwareBitmap. O processo de detecção de rosto é mais rápido com uma imagem menor e, portanto, você pode querer reduzir a imagem de origem para um tamanho menor. Isso pode ser executado durante a decodificação criando um objeto BitmapTransform, definindo as propriedades ScaledWidth e ScaledHeight e passando-o para a chamada para GetSoftwareBitmapAsync, que retorna o SoftwareBitmap decodificado e dimensionado.

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

Na versão atual, a classe FaceDetector só dá suporte a imagens em Gray8 ou Nv12. A classe SoftwareBitmap fornece o método Convert , que converte um bitmap de um formato para outro. Este exemplo converte a imagem de origem no formato de pixel Gray8 se ela ainda não estiver nesse formato. Se desejar, você pode usar os métodos GetSupportedBitmapPixelFormats e IsBitmapPixelFormatSupported para determinar em runtime se há suporte para um formato de pixel, caso o conjunto de formatos com suporte seja expandido em versões futuras.

// 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;
}

Instancie o objeto FaceDetector chamando CreateAsync e, em seguida, chamando DetectFacesAsync, passando o bitmap que foi dimensionado para um tamanho razoável e convertido em um formato de pixel com suporte. Esse método retorna uma lista de objetos DetectedFace . ShowDetectedFaces é um método auxiliar, mostrado abaixo, que desenha quadrados ao redor das faces na imagem.

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

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

Certifique-se de descartar os objetos que foram criados durante o processo de detecção facial.

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

Para exibir a imagem e desenhar caixas ao redor dos rostos detectados, adicione um elemento Canvas à sua página XAML.

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

Defina algumas variáveis de membro para estilizar os quadrados que serão desenhados.

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

No método auxiliar ShowDetectedFaces, um novo ImageBrush é criado e a origem é definida como um SoftwareBitmapSource criado a partir do SoftwareBitmap que representa a imagem de origem. O plano de fundo do controle XAML Canvas é definido como o pincel de imagem.

Se a lista de rostos passados para o método auxiliar não estiver vazia, percorra cada rosto na lista e use a propriedade FaceBox da classe DetectedFace para determinar a posição e o tamanho do retângulo dentro da imagem que contém o rosto. Como é muito provável que o controle Canvas tenha um tamanho diferente da imagem de origem, você deve multiplicar as coordenadas X e Y e a largura e a altura do FaceBox por um valor de dimensionamento que é a proporção do tamanho da imagem de origem para o tamanho real do controle 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);
        }
    }
}

Rastrear rostos em uma sequência de quadros

Se você deseja detectar rostos em vídeo, é mais eficiente usar a classe FaceTracker em vez da classe FaceDetector , embora as etapas de implementação sejam muito semelhantes. O FaceTracker usa informações sobre quadros processados anteriormente para otimizar o processo de detecção.

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

Declare uma variável de classe para o objeto FaceTracker . Este exemplo usa um ThreadPoolTimer para iniciar o acompanhamento facial em um intervalo definido. Um SemaphoreSlim é usado para garantir que apenas uma operação de rastreamento facial esteja em execução por vez.

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

Para inicializar a operação de acompanhamento facial, crie um novo objeto FaceTracker chamando CreateAsync. Inicialize o intervalo de temporizador desejado e crie o temporizador. O método auxiliar ProcessCurrentVideoFrame será chamado sempre que o intervalo especificado decorrer.

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

O auxiliar ProcessCurrentVideoFrame é chamado de forma assíncrona pelo temporizador, portanto, o método primeiro chama o método Wait do semáforo para ver se uma operação de rastreamento está em andamento e, se estiver, o método retorna sem tentar detectar rostos. No final desse método, o método Release do semáforo é chamado, o que permite que a chamada subsequente para ProcessCurrentVideoFrame continue.

A classe FaceTracker opera em objetos VideoFrame. Há várias maneiras de obter um VideoFrame, incluindo a captura de um quadro de visualização de um objeto MediaCapture em execução ou implementando o método ProcessFrame do IBasicVideoEffect. Este exemplo usa um método auxiliar indefinido que retorna um quadro de vídeo, GetLatestFrame, como um espaço reservado para essa operação. Para obter informações sobre como obter quadros de vídeo do fluxo de visualização de um dispositivo de captura de mídia em execução, consulte Obter um quadro de visualização.

Assim como no FaceDetector, o FaceTracker suporta um conjunto limitado de formatos de pixel. Este exemplo abandonará a detecção facial se o quadro fornecido não estiver no formato Nv12.

Chame ProcessNextFrameAsync para recuperar uma lista de objetos DetectedFace que representam as faces no quadro. Depois de ter a lista de rostos, você pode exibi-los da mesma maneira descrita acima para detecção de rosto. Observe que, como o método auxiliar de acompanhamento facial não é chamado no thread da interface do usuário, você deve fazer atualizações da interface do usuário em uma chamada 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();
}