Compartir a través de


Detectar rostros en imágenes o vídeos

En este tema se muestra cómo usar FaceDetector para detectar caras en una imagen. FaceTracker está optimizado para el seguimiento de caras a lo largo del tiempo en una secuencia de fotogramas de vídeo.

Para obtener un método alternativo de seguimiento de caras mediante FaceDetectionEffect, consulte Análisis de escenas para la captura multimedia.

El código de este artículo se adaptó a partir de los ejemplos básico de detección de caras y seguimiento facial básico. Puede descargar estos ejemplos para ver el código usado en contexto o para usar el ejemplo como punto de partida para su propia aplicación.

Detección de caras en una sola imagen

La clase FaceDetector permite detectar una o varias caras en una imagen fija.

En este ejemplo se usan las API de los siguientes espacios de nombres.

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 una variable de miembro de clase para el objeto FaceDetector y para la lista de objetos DetectedFace que se detectarán en la imagen.

FaceDetector faceDetector;
IList<DetectedFace> detectedFaces;

La detección de caras funciona en un objeto SoftwareBitmap que se puede crear de varias maneras. En este ejemplo, se usa fileOpenPicker para permitir al usuario elegir un archivo de imagen en el que se detectarán caras. Para obtener más información sobre cómo trabajar con mapas de bits de software, consulte 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;
}

Use la clase BitmapDecoder para descodificar el archivo de imagen en un objeto SoftwareBitmap. El proceso de detección de caras es más rápido con una imagen más pequeña, por lo que es posible que quiera reducir verticalmente la imagen de origen a un tamaño menor. Esto se puede realizar durante la descodificación creando un objeto BitmapTransform, estableciendo las propiedades ScaledWidth y ScaledHeight y pasandolas a la llamada a GetSoftwareBitmapAsync, que devuelve el softwarebitmap descodificado y escalado.

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

En la versión actual, la clase FaceDetector solo admite imágenes en Gray8 o Nv12. La clase SoftwareBitmap proporciona el método Convert , que convierte un mapa de bits de un formato a otro. En este ejemplo se convierte la imagen de origen en el formato de píxel Gray8 si aún no está en ese formato. Si lo desea, puede usar los métodos GetSupportedBitmapPixelFormats e IsBitmapPixelFormatSupported para determinar en tiempo de ejecución si se admite un formato de píxel, en caso de que el conjunto de formatos admitidos se expanda en versiones 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;
}

Cree una instancia del objeto FaceDetector llamando a CreateAsync y, a continuación, llamando a DetectFacesAsync, pasando el mapa de bits que se ha escalado a un tamaño razonable y convertido en un formato de píxel admitido. Este método devuelve una lista de objetos DetectedFace. ShowDetectedFaces es un método auxiliar, que se muestra a continuación, que dibuja cuadrados alrededor de las caras de la imagen.

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

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

Asegúrese de eliminar los objetos que se crearon durante el proceso de detección de caras.

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

Para mostrar la imagen y dibujar cuadros alrededor de las caras detectadas, agregue un elemento Canvas a la página XAML.

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

Defina algunas variables de miembro para aplicar estilo a los cuadrados que se dibujarán.

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

En el método auxiliar ShowDetectedFaces, se crea un nuevo ImageBrush y el origen se establece en un SoftwareBitmapSource creado a partir de SoftwareBitmapmap que representa la imagen de origen. El fondo del control Lienzo XAML se establece en el pincel de la imagen.

Si la lista de caras pasadas al método auxiliar no está vacía, recorra cada cara de la lista y use la propiedad FaceBox de la clase DetectedFace para determinar la posición y el tamaño del rectángulo dentro de la imagen que contiene la cara. Dado que es muy probable que el control Canvas sea un tamaño diferente al de la imagen de origen, debe multiplicar las coordenadas X e Y y el ancho y el alto de FaceBox por un valor de escalado que sea la relación del tamaño de imagen de origen con el tamaño real del control 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);
        }
    }
}

Seguimiento de caras en una secuencia de fotogramas

Si quiere detectar caras en vídeo, es más eficaz usar la clase FaceTracker en lugar de la clase FaceDetector, aunque los pasos de implementación son muy similares. FaceTracker usa información sobre fotogramas procesados previamente para optimizar el proceso de detección.

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

Declare una variable de clase para el objeto FaceTracker . En este ejemplo se usa threadPoolTimer para iniciar el seguimiento facial en un intervalo definido. SemaphoreSlim se usa para asegurarse de que solo se ejecuta una operación de seguimiento facial a la vez.

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

Para inicializar la operación de seguimiento facial, cree un nuevo objeto FaceTracker llamando a CreateAsync. Inicialice el intervalo de temporizador deseado y, a continuación, cree el temporizador. Se llamará al método auxiliar ProcessCurrentVideoFrame cada vez que transcurre el intervalo especificado.

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

El temporizador llama al asistente ProcessCurrentVideoFrame de forma asincrónica, por lo que el método llama primero al método Wait del semáforo para ver si una operación de seguimiento está en curso y si es el método devuelve sin intentar detectar caras. Al final de este método, se llama al método Release del semáforo, que permite que la llamada posterior a ProcessCurrentVideoFrame continúe.

La clase FaceTracker funciona en objetos VideoFrame. Hay varias maneras de obtener un VideoFrame, incluida la captura de un fotograma de vista previa desde un objeto MediaCapture en ejecución o mediante la implementación del método ProcessFrame del IBasicVideoEffect. En este ejemplo se usa un método auxiliar indefinido que devuelve un fotograma de vídeo, GetLatestFrame, como marcador de posición para esta operación. Para obtener información sobre cómo obtener fotogramas de vídeo de la secuencia de vista previa de un dispositivo de captura multimedia en ejecución, consulte Obtención de un fotograma de vista previa.

Al igual que con FaceDetector, FaceTracker admite un conjunto limitado de formatos de píxeles. En este ejemplo se abandona la detección de caras si el marco proporcionado no está en formato Nv12.

Llame a ProcessNextFrameAsync para recuperar una lista de objetos DetectedFace que representan las caras del marco. Después de tener la lista de caras, puede mostrarlas de la misma manera descrita anteriormente para la detección de caras. Tenga en cuenta que, dado que no se llama al método auxiliar de seguimiento facial en el subproceso de la interfaz de usuario, debe realizar actualizaciones de la interfaz de usuario en dentro de una llamada a 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();
}