偵測影像或影片中的臉部
本主題說明如何使用 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 控制項的大小很可能與來源影像不同,因此您應該將 FaceBox 的 X 和 Y 座標以及寬度和高度乘以縮放值,該值是來源影像大小與 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();
}