偵測影像或影片中的臉部

本主題說明如何使用 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;
}

透過呼叫 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 物件擷取預覽畫面或透過實作 IBasicVideoEffectProcessFrame 方法。 此範例會使用未定義的協助程式方法,這個方法會傳回視訊畫面 GetLatestFrame 做為這項作業的預留位置。 如需從執行中媒體擷取裝置預覽串流取得視訊畫面的相關資訊,請參閱取得預覽畫面

如同 FaceDetectorFaceTracker 支援一組有限的像素格式。 如果提供的畫面不是 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();
}