이미지 또는 동영상에서 얼굴 감지
이 항목에서는 FaceDetector를 사용하여 이미지에서 얼굴을 감지하는 방법을 보여줍니다. FaceTracker는 비디오 프레임 시퀀스에서 시간 경과에 따른 얼굴 추적에 최적화되어 있습니다.
FaceDetectionEffect를 사용하여 얼굴을 추적하는 다른 방법은 미디어 캡처에 대한 장면 분석을 참조하세요.
이 문서의 코드는 기본 얼굴 감지 및 기본 얼굴 추적 샘플에서 적용되었습니다. 이러한 샘플을 다운로드하여 컨텍스트에서 사용된 코드를 보거나 사용자 고유의 앱의 시작점으로 사용할 수 있습니다.
단일 이미지에서 얼굴 감지
FaceDetector 클래스를 사용하면 스틸 이미지에서 하나 이상의 얼굴을 감지할 수 있습니다.
이 예제에서는 다음 네임스페이스의 APIs를 사용합니다.
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 속성을 설정하고 디코딩되고 크기가 조정된 SoftwareBitmap을 반환하는 GetSoftwareBitmapAsync 호출에 전달하여 디코딩하는 동안 수행할 수 있습니다.
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();
감지된 얼굴 주위에 이미지를 표시하고 상자를 그리려면 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 컨트롤은 원본 이미지와 크기가 다를 가능성이 높으므로 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);
}
}
}
프레임 시퀀스에서 얼굴 추적
비디오에서 얼굴을 감지하려는 경우 구현 단계가 매우 유사하지만 FaceDetectorFaceDetector 클래스보다는 FaceTracker 클래스를 사용하는 것이 더 효율적입니다. 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개체에서 작동합니다. 실행 중인 MediaCapture 개체에서 미리 보기 프레임을 캡처하거나 IBasicVideoEffect의 ProcessFrame 메서드를 구현하는 등 VideoFrameVideoFrame을 가져올 수 있는 여러 가지 방법이 있습니다. 이 예제에서는 비디오 프레임인 GetLatestFrame을 반환하는 정의되지 않은 도우미 메서드를 이 작업의 자리 표시자로 사용합니다. 실행 중인 미디어 캡처 디바이스의 미리 보기 스트림에서 비디오 프레임을 가져오는 방법에 대한 자세한 내용은 미리 보기 프레임 가져오기를 참조하세요.
FaceDetector와 마찬가지로 FaceTracker는 제한된 픽셀 형식 집합을 지원합니다. 이 예제에서는 제공된 프레임이 Nv12 형식이 아닌 경우 얼굴 감지를 중단합니다.
ProcessNextFrameAsync를 호출하여 프레임의 얼굴을 나타내는 DetectedFace 개체 목록을 검색합니다. 얼굴 목록이 있으면 얼굴 감지를 위해 위에서 설명한 것과 동일한 방식으로 표시할 수 있습니다. 얼굴 추적 도우미 메서드는 UI 스레드에서 호출되지 않으므로 모든 UI 업데이트는 CoreDispatcher.RunAsync 호출 내에서 해야 합니다.
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();
}