Share via


Detectar rostos em imagens ou vídeos

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

Para um método alternativo de acompanhamento facial que usa FaceDetectionEffect, consulte Análise de cena para captura de mídia.

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

Detectar rostos em uma única imagem

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

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

Declarar 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 de rostos opera em um objeto SoftwareBitmap que pode ser criado de diversas maneiras. Neste exemplo, FileOpenPicker é usado para permitir que o usuário selecione um arquivo de imagem no qual 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 SoftwareBitmap. O processo de detecção de rostos é mais rápido com uma imagem menor, portanto você pode reduzir verticalmente a imagem de origem para um tamanho menor. Você pode fazer isso durante a decodificação criando um objeto BitmapTransform, definindo as propriedades ScaledWidth e ScaledHeight passando-o para a chamada a 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 dá suporte somente a imagens em Gray8 ou Nv12. A classe SoftwareBitmap fornece o método Convert, que converte um bitmap de um formato em outro. Este exemplo converterá a imagem de origem no formato de pixel Gray8 caso ainda não esteja nesse formato. Se você quiser, poderá usar os métodos GetSupportedBitmapPixelFormats e IsBitmapPixelFormatSupported para determinar, em tempo de execução, se há suporte para um formato de pixel, caso o conjunto de formatos com suporte seja expandido em futuras versões.

// 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 chame 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 dos rostos 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 de rostos.

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

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

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

Defina algumas variáveis de membro para definir o estilo dos 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 SoftwareBitmapSource criada com base no SoftwareBitmap que representa a imagem de origem. A tela de fundo do controle XAML Canvas é definida como o pincel de imagem.

Se a lista de rostos passada para o método auxiliar não estiver vazia, execute um loop de 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. Uma vez que o controle Canvas muito provavelmente terá um tamanho diferente da imagem de origem, você deve multiplicar as coordenadas X e Y e a largura e a altura de FaceBox por um valor de escala que seja a proporção do tamanho da imagem de origem e 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);
        }
    }
}

Acompanhar rostos em uma sequência de quadros

Se você quiser detectar rostos em vídeos, é mais eficiente usar a classe FaceTracker em vez da classe FaceDetector, embora as etapas de implementação sejam muito semelhantes. 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 ThreadPoolTimer para iniciar o acompanhamento facial em um intervalo definido. SemaphoreSlim é usado para garantir que somente uma operação de acompanhamento facial seja executada por vez.

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

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

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 método auxiliar ProcessCurrentVideoFrame é chamado de forma assíncrona pelo temporizador, portanto o método chama primeiro o método Wait do semáforo para verificar se uma operação de acompanhamento está em andamento e se o método é retornado sem tentar detectar rostos. Ao final desse método, o método Release do semáforo é chamado, o que permite que a chamada subsequente a ProcessCurrentVideoFrame continue.

A classe FaceTracker opera em objetos VideoFrame. Há várias maneiras de obter VideoFrame, incluindo a captura de um quadro de visualização de um objeto MediaCapture em execução ou a implementação do método ProcessFrame de 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 acontece com FaceDetector, FaceTracker dá suporte a um conjunto limitado de formatos de pixel. Este exemplo abandonará a detecção de rostos se o quadro fornecido não estiver no formato Nv12.

Chame ProcessNextFrameAsync para recuperar uma lista de objetos DetectedFace que representam os rostos no quadro. Depois que você tiver a lista de rostos, poderá exibi-los da mesma maneira descrita acima para detecção de rostos. 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 de interface do usuário em 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();
}