检测图像或视频中的人脸

本主题介绍如何使用 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 对象、设置 ScaledWidthScaledHeight 属性,并将其传递到对 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 像素格式(如果它尚未采用该格式)。 如果需要,可以使用 GetSupportedBitmapPixelFormatsIsBitmapPixelFormatSupported 方法在运行时确定某个像素格式是否受支持,以免在将来的版本中扩展受支持的格式集。

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

通过依次调用 CreateAsyncDetectFacesAsync 来实例化 FaceDetector,从而传入已缩放到合理大小并转换为受支持的像素格式的位图。 此方法返回 DetectedFace 对象的列表。 ShowDetectedFaces 是一个帮助程序方法(如下所示),用于在图像中的人脸周围绘制正方形。

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

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

确保处理在人脸检测过程中创建的对象。

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

若要显示图像并在检测到的人脸周围绘制方框,请将 Canvas 元素添加到 XAML 页面。

<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 并将源设置为从表示源图像的 SoftwareBitmap 创建的 SoftwareBitmapSource。 XAML Canvas 控件的背景设置为图像画笔。

如果传递到帮助程序方法中的人脸列表不为空,循环访问列表中的每张人脸,并使用 DetectedFace 类的 FaceBox 属性来确定图像内包含人脸的的矩形位置和大小。 由于 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);

若要初始化人脸跟踪操作,请通过调用 CreateAsync 创建新的 FaceTracker 对象。 初始化所需的计时器间隔,然后创建计时器。 每次指定的间隔到期时,都将调用 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 对象捕获预览帧,或通过实现 IBasicVideoEffectProcessFrame 方法。 此示例使用未定义的帮助程序方法,该方法返回视频帧 GetLatestFrame 作为此操作的占位符。 有关从正在运行的媒体捕获设备的预览流获取视频帧的信息,请参阅获取预览帧

FaceDetector 一样,FaceTracker 支持一组有限的像素格式。 如果所提供的帧未采用 Nv12 格式,则此示例将放弃人脸检测。

调用 ProcessNextFrameAsync 来检索表示该帧中的人脸的 DetectedFace 对象列表。 获得人脸列表后,你可以通过上述用于人脸检测的相同方式显示它们。 请注意,由于未在 UI 线程上调用人脸跟踪帮助程序方法,因此你必须在调用 CoreDispatcher.RunAsync 内进行任何 UI 更新。

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