画像やビデオでの顔の検出

このトピックでは、FaceDetector を使って画像内の顔を検出する方法について説明します。 FaceTracker は、ビデオ フレームのシーケンスで顔を経時的に追跡するように最適化されています。

FaceDetectionEffect を使った顔を追跡する別の方法については、「メディア キャプチャのシーン分析」をご覧ください。

この記事のコードは、基本的な顔検出基本的な顔追跡のサンプルを基にしています。 これらのサンプルをダウンロードし、該当するコンテキストで使われているコードを確認することも、サンプルを独自のアプリの開始点として使うこともできます。

1 つの画像内の顔を検出する

FaceDetector クラスを使うと、静止画像内の 1 つまたは複数の顔を検出できます。

この例では、次の名前空間の 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 を呼び出すことで FaceDetector オブジェクトをインスタンス化したら、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 が作成され、ソースが、ソース画像を表す 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);
        }
    }
}

フレームのシーケンスで顔を追跡する

ビデオ内の顔を検出する場合は、FaceDetector よりも FaceTracker クラスを使う方が効率的です。実装手順はほとんど同じです。 FaceTracker では、前に処理したフレームに関する情報を使って検出処理を最適化します。

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

FaceTracker オブジェクトのクラス変数を宣言します。 この例では、ThreadPoolTimer を使って、定義した間隔で顔追跡を開始します。 SemaphoreSlim を使って、同時に 1 つの顔追跡処理のみが実行されるようにします。

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