检测图像或视频中的人脸
本主题介绍如何使用 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;
}
通过依次调用 CreateAsync 和 DetectFacesAsync 来实例化 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 对象捕获预览帧,或通过实现 IBasicVideoEffect 的 ProcessFrame 方法。 此示例使用未定义的帮助程序方法,该方法返回视频帧 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();
}