Обнаружение лиц на изображениях или в видео

В этом разделе показано, как использовать FaceDetector для обнаружения лиц на изображении. Инструмент FaceTracker оптимизирован для отслеживания лиц с течением времени в последовательности видеокадров.

Сведения об альтернативном методе отслеживания лиц с помощью FaceDetectionEffect см. в разделе Анализ сцен для захвата мультимедиа.

В этой статье используется код, адаптированный из примеров Базовое обнаружение лиц и Базовое отслеживание лиц. Вы можете скачать эти примеры, чтобы просмотреть код в контексте или использовать пример как отправную точку для настройки вашего приложения.

Обнаружение лиц на одном изображении

Класс FaceDetector позволяет обнаружить одно или несколько лиц на неподвижном изображении.

В этом примере используются API из следующих пространств имен.

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;

Объявите переменную-член класса для объекта FaceDetector и для списка объектов DetectedFace, которые будут обнаружены на изображении.

FaceDetector faceDetector;
IList<DetectedFace> detectedFaces;

Обнаружение лиц выполняется в объекте SoftwareBitmap, который можно создать различными способами. В этом примере FileOpenPicker используется, чтобы пользователь мог выбрать файл изображения, в котором будут обнаруживаться лица. Дополнительные сведения о работе с точечными рисунками программного обеспечения см. в разделе Обработка изображений.

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

Используйте класс BitmapDecoder для декодирования файла изображения в SoftwareBitmap. Процесс обнаружения лиц проходит быстрее на меньшем изображении, поэтому вы, возможно, захотите масштабировать исходное изображение до меньшего размера. Это можно выполнить во время декодирования, создав объект BitmapTransform, задав свойства ScaledWidth и ScaledHeight и передав его вызову GetSoftwareBitmapAsync, который возвращает декодированный и масштабированный SoftwareBitmap.

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

В текущей версии класс FaceDetector поддерживает только изображения в формате Gray8 или Nv12. Класс SoftwareBitmap предоставляет метод Convert, который преобразует точечный рисунок из одного формата в другой. В этом примере исходное изображение преобразовывается в формат пикселей Gray8, если оно еще не в этом формате. При желании для того, чтобы определить во время выполнения, поддерживается ли формат пикселей, можно использовать методы GetSupportedBitmapPixelFormats и IsBitmapPixelFormatSupported на случай, если набор поддерживаемых форматов будет расширяться в будущих версиях.

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

Создайте экземпляр объекта FaceDetector, вызвав CreateAsync, а затем вызовите DetectFacesAsync, передав точечный рисунок, который был масштабирован до оптимального размера и преобразован в поддерживаемый формат пикселей. Этот метод возвращает список объектов DetectedFace. ShowDetectedFaces — это вспомогательный метод, показанный ниже, который рисует квадраты вокруг лиц на изображении.

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

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

Не забудьте удалить объекты, созданные во время процесса обнаружения лиц.

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

Чтобы отобразить изображение и нарисовать рамки вокруг обнаруженных лиц, добавьте на страницу XAML элемент Canvas.

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

Определите несколько переменных-членов, чтобы оформить квадраты, которые будут нарисованы.

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

Во вспомогательном методе ShowDetectedFaces создается новый элемент ImageBrush, а источник задается как SoftwareBitmapSource, созданный из SoftwareBitmap, который представляет исходное изображение. Фон элемента управления XAML Canvas задается как кисть изображения.

Если список лиц, переданный во вспомогательный метод, не пуст, пройдите по всем лицам в списке и используйте свойство FaceBox класса DetectedFace, чтобы определить положение и размер прямоугольника в изображении, которое содержит лицо. Так как размер элемента управления Canvas, вероятно, будет отличаться от размера исходного изображения, необходимо умножить координаты X и Y, а также ширину и высоту FaceBox на значение масштабирования, которое представляет собой соотношение размера исходного изображения к фактическому размеру элемента управления 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);
        }
    }
}

Отслеживание лиц в последовательности кадров

Если вы хотите обнаружить лица в видео, эффективней использовать класс FaceTracker вместо класса FaceDetector, хотя действия по реализации очень похожи. FaceTracker использует сведения о ранее обработанных кадрах для оптимизации процесса обнаружения.

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

Объявите переменную класса для объекта FaceTracker. В этом примере для запуска отслеживания лиц на определенный интервал используется ThreadPoolTimer. SemaphoreSlim используется для обеспечения того, что в определенный момент выполняется только одна операция отслеживания лиц.

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

Чтобы инициализировать операцию отслеживания лиц, создайте новый объект FaceTracker, вызвав CreateAsync. Инициализируйте нужный интервал таймера, а затем создайте таймер. Вспомогательный метод ProcessCurrentVideoFrame будет вызываться каждый раз по истечении заданного интервала.

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

Вспомогательный метод ProcessCurrentVideoFrame вызывается таймером асинхронно, поэтому метод сначала вызывает метод Wait семафора, чтобы убедиться, что операция отслеживания выполняется, и если это так, метод возвращает значение, не пытаясь обнаружить лица. В конце этого метода вызывается метод Release семафора, который делает возможным последующий вызов ProcessCurrentVideoFrame для продолжения.

Класс FaceTracker выполняется в объектах VideoFrame. Существует несколько способов получить VideoFrame, в том числе захватив кадр предварительного просмотра из запущенного объекта MediaCapture или реализовав метод ProcessFrame элемента IBasicVideoEffect. В этом примере используется неопределенный вспомогательный метод, который возвращает видеокадр, GetLatestFrame, в качестве заполнителя для этой операции. Подробнее о получении видеокадров из потока предварительного просмотра на запущенном устройстве захвата мультимедиа см. в разделе Получение кадра предварительного просмотра.

Как и FaceDetector, FaceTracker поддерживает ограниченный набор форматов пикселей. В этом примере обнаружение лиц отменяется, если формат предоставленного кадра — не Nv12.

Вызовите ProcessNextFrameAsync, чтобы извлечь список объектов DetectedFace, представляющих лица в кадре. Когда у вас будет список лиц, их можно отображать тем же образом, который описан выше для обнаружения лиц. Обратите внимание, что, так как вспомогательный метод отслеживания лиц не вызывается в потоке пользовательского интерфейса, необходимо внести все обновления пользовательского интерфейса в вызове 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();
}