☰
Windows-universal-samples 中的 BasicFaceDetection 示例:使用 FaceDetector 在 UWP 应用中实现静态人脸检测
2026/9/25 13:41:24 网站建设 项目流程
  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载

本指南以 Windows-universal-samples 仓库中的 BasicFaceDetection 示例为蓝本,系统讲解 UWP 应用中如何使用Windows.Media.FaceAnalysis.FaceDetector类在一张静态照片或单帧视频中定位人脸,并完成像素格式转换、检测结果可视化与界面缩放同步。读完本文,你将掌握FaceDetector的初始化与复用策略、SoftwareBitmap/VideoFrame两种输入路径的构建方法,以及如何用 XAMLCanvas绘制人脸框并在窗口缩放时保持位置准确。

示例概览与目录结构

示例位于 Samples/BasicFaceDetection,包含两个并列的实现语言目录与一个共享 UI 目录:

目录说明
csC# 实现(Scenario1_DetectInPhoto.xaml.cs、Scenario2_DetectInWebcam.xaml.cs、SampleConfiguration.cs)
cppwinrtC++/WinRT 实现(Scenario1_DetectInPhoto.cpp、Scenario2_DetectInWebcam.cpp、SampleConfiguration.cpp)
shared两个场景共享的 XAML 页面:Scenario1_DetectInPhoto.xaml与Scenario2_DetectInWebcam.xaml

两个场景通过 SampleConfiguration.cs 中的场景列表注册到示例框架:

  • Detect Faces in Photos:从文件选择器中挑选一张图片(jpg/jpeg/png/bmp),解码后执行人脸检测;
  • Detect Faces in Webcam:启动摄像头实时预览,抓取单帧快照后执行人脸检测。

从源码结构看,两个场景的差异仅在于"输入源":照片场景把解码后的SoftwareBitmap交给FaceDetector,网络摄像头场景则把MediaCapture抓取到的VideoFrame中的SoftwareBitmap交给同一个检测器,后续的检测调用与可视化逻辑完全一致。

FaceDetector 的初始化与复用

创建检测器实例

FaceDetector位于Windows.Media.FaceAnalysis命名空间,只能通过异步工厂方法创建:

FaceDetector detector = await FaceDetector.CreateAsync();

照片场景的源码(Scenario1_DetectInPhoto.xaml.cs)特别注释了这一点:

FaceDetector初始化可能耗时较长,在大多数情况下应当创建成员变量并复用该对象;本场景为了演示简洁性,每次点击按钮时新建实例。

因此实际项目中推荐的模式是:在页面或服务对象中保存一个FaceDetector字段,在首次使用时惰性创建,后续检测直接复用,避免反复承担初始化开销。

检测前的格式校验

FaceDetector只接受有限的像素格式,检测前必须用静态方法校验:

if (FaceDetector.IsBitmapPixelFormatSupported(InputPixelFormat)) { // 执行检测 }

示例在两个场景中分别校验了两种典型格式:

  • 照片场景使用BitmapPixelFormat.Gray8(8 位灰度),因为人脸检测本质上不需要彩色信息,灰度图体积小、检测更快;
  • 网络摄像头场景使用BitmapPixelFormat.Nv12,这是摄像头预览流常见的 YUV 格式,MediaCapture.GetPreviewFrameAsync会把原生帧转换为此格式。

校验失败时会通过rootPage.NotifyUser输出错误提示,例如 "PixelFormat 'Gray8' is not supported by FaceDetector"。

场景一:从照片文件检测人脸

文件选择与位图解码

照片场景通过 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");

选中文件后,用BitmapDecoder解码位图,并制作两份副本——一份交给检测器,一份用于显示:

using (IRandomAccessStream fileStream = await photoFile.OpenAsync(Windows.Storage.FileAccessMode.Read)) { BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream); BitmapTransform transform = this.ComputeScalingTransformForSourceImage(decoder); using (SoftwareBitmap originalBitmap = await decoder.GetSoftwareBitmapAsync( decoder.BitmapPixelFormat, BitmapAlphaMode.Ignore, transform, ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage)) { const BitmapPixelFormat InputPixelFormat = BitmapPixelFormat.Gray8; if (FaceDetector.IsBitmapPixelFormatSupported(InputPixelFormat)) { using (SoftwareBitmap detectorInput = SoftwareBitmap.Convert(originalBitmap, InputPixelFormat)) { // 显示用副本 WriteableBitmap displaySource = new WriteableBitmap(originalBitmap.PixelWidth, originalBitmap.PixelHeight); originalBitmap.CopyToBuffer(displaySource.PixelBuffer); FaceDetector detector = await FaceDetector.CreateAsync(); IList<DetectedFace> faces = await detector.DetectFacesAsync(detectorInput); this.SetupVisualization(displaySource, faces); } } } }

关键点有三个:

  1. 格式转换:用SoftwareBitmap.Convert把原始位图转为Gray8,这是检测器输入;
  2. 显示副本:WriteableBitmap不支持灰度图,因此显示时仍使用原始像素格式的位图,通过CopyToBuffer拷贝到WriteableBitmap.PixelBuffer;
  3. 检测调用:DetectFacesAsync(detectorInput)返回IList<DetectedFace>,其中DetectedFace.FaceBox(BitmapBounds结构)给出了每个人脸的包围矩形坐标。

大图降采样:1280 像素高度限制

FaceDetector在超大图片上的性能会显著下降,示例因此设置了一个输入高度上限。C# 与 C++/WinRT 两侧的常量完全一致:

private readonly uint sourceImageHeightLimit = 1280;
static constexpr uint32_t sourceImageHeightLimit = 1280;

ComputeScalingTransformForSourceImage在解码时按比例降采样:当PixelHeight超过 1280 时,按1280 / 原始高度的比例同时缩放宽高,返回一个BitmapTransform交给GetSoftwareBitmapAsync。源码注释明确说明这是"为兼顾检测精度与性能而选择的任意值",实际应用应根据自身性能/精度需求调整。

场景二:从摄像头快照检测人脸

摄像头初始化与预览

网络摄像头场景的状态机(Idle → Streaming → Snapshot)由 Scenario2_DetectInWebcam.xaml.cs 管理。启动流式传输时依次完成:创建FaceDetector→ 初始化MediaCapture→ 缓存视频属性 → 开始预览:

this.faceDetector = await FaceDetector.CreateAsync(); MediaCaptureInitializationSettings settings = new MediaCaptureInitializationSettings(); settings.StreamingCaptureMode = StreamingCaptureMode.Video; // 仅视频,不申请麦克风 await this.mediaCapture.InitializeAsync(settings); this.mediaCapture.CameraStreamStateChanged += this.MediaCapture_CameraStreamStateChanged; var deviceController = this.mediaCapture.VideoDeviceController; this.videoProperties = deviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties; this.CamPreview.Source = this.mediaCapture; // CaptureElement 的 Source 必须在启动前设置 await this.mediaCapture.StartPreviewAsync();

这里有两个值得注意的工程细节:

  • 能力声明匹配:初始化设置只声明了StreamingCaptureMode.Video,与 Package.appxmanifest 中仅声明的webcam设备能力对应。源码注释特别警告:如果改用包含麦克风的默认构造函数,则必须同时在清单中补充microphone能力,否则初始化会失败;
  • 异常处理:如果用户在隐私设置中禁用了摄像头,InitializeAsync会抛出UnauthorizedAccessException,示例会给出明确提示:"Webcam is disabled or access to the webcam is disabled for this app. Ensure Privacy Settings allow webcam usage."。UWP 应用中相机访问需要用户在系统设置中授予隐私权限,这是实际部署时最常见的失败点之一。

抓帧并执行检测

点击 "Take Snapshot" 后,用VideoFrame指定 NV12 目标格式,再调用GetPreviewFrameAsync让系统把摄像头原生帧转换为该格式:

const BitmapPixelFormat InputPixelFormat = BitmapPixelFormat.Nv12; using (VideoFrame previewFrame = new VideoFrame(InputPixelFormat, (int)this.videoProperties.Width, (int)this.videoProperties.Height)) { await this.mediaCapture.GetPreviewFrameAsync(previewFrame); if (FaceDetector.IsBitmapPixelFormatSupported(previewFrame.SoftwareBitmap.BitmapPixelFormat)) { IList<DetectedFace> faces = await this.faceDetector.DetectFacesAsync(previewFrame.SoftwareBitmap); // WriteableBitmap 不支持 NV12,转换为 32 位 BGRA 用于显示 using (SoftwareBitmap convertedSource = SoftwareBitmap.Convert(previewFrame.SoftwareBitmap, BitmapPixelFormat.Bgra8)) { displaySource = new WriteableBitmap(convertedSource.PixelWidth, convertedSource.PixelHeight); convertedSource.CopyToBuffer(displaySource.PixelBuffer); } this.SetupVisualization(displaySource, faces); } }

注意这里的格式选择与照片场景相反:检测输入保持 NV12(摄像头预览原生格式,避免额外转换开销),而显示副本转为 BGRA8,因为WriteableBitmap不支持 NV12。

抓帧完成后立即关闭摄像头并释放MediaCapture,进入Snapshot状态展示结果;点击 "Clear Display" 则回到Idle。应用挂起(Suspending事件)时,示例通过延迟(SuspendingOperation.GetDeferral)确保先回到Idle状态、释放摄像头资源,再完成挂起流程。

检测结果的可视化与缩放同步

用黄色边框框出人脸

两个场景共享同一套可视化逻辑。共享 XAML 中定义了一个DataTemplate(见 Scenario1_DetectInPhoto.xaml):

<Page.Resources> <DataTemplate x:Name="HighlightedFaceBox"> <Border BorderBrush="Yellow" BorderThickness="2"/> </DataTemplate> </Page.Resources>

C# 侧的MainPage.HighlightFaces(SampleConfiguration.cs)把检测结果逐个渲染到Canvas上:

public static void HighlightFaces(WriteableBitmap displaySource, IList<DetectedFace> foundFaces, Canvas canvas, DataTemplate template) { double widthScale = canvas.ActualWidth / displaySource.PixelWidth; double heightScale = canvas.ActualHeight / displaySource.PixelHeight; foreach (DetectedFace face in foundFaces) { var box = (FrameworkElement)template.LoadContent(); box.Tag = face.FaceBox; // 保存原始包围盒,供后续缩放 ApplyScale(box, widthScale, heightScale); canvas.Children.Add(box); } // 输出 "Found N human faces in the image" 等状态消息 }

C++/WinRT 的SampleHelpers::HighlightFaces(SampleConfiguration.cpp)逻辑完全对应,仅把 C# 的box.Tag换成了box.Tag(box_value(face.FaceBox()))。

缩放自适应:Tag 保存原始坐标

由于图片尺寸与Canvas的实际显示尺寸往往不同,示例把face.FaceBox的原始值存入元素的Tag,再按缩放比例换算宽高与边距:

static void ApplyScale(FrameworkElement box, double widthScale, double heightScale) { BitmapBounds faceBox = (BitmapBounds)box.Tag; box.Width = faceBox.Width * widthScale; box.Height = faceBox.Height * heightScale; box.Margin = new Thickness(faceBox.X * widthScale, faceBox.Y * heightScale, 0, 0); }

当窗口尺寸变化触发Canvas.SizeChanged时,RepositionFaces重新计算缩放因子并对所有子元素重新应用ApplyScale,从而保证人脸框始终与图像内容对齐。这是 UWP 自适应布局中处理"图像坐标系 ↔ 控件坐标系"映射的通用手法。

构建与运行

系统要求

按 README.md 的说明,示例需要:

  • 硬件:摄像头(仅用于视频捕获场景,照片场景不需要);
  • 客户端:Windows 10;
  • 服务端:Windows Server 2016 Technical Preview;
  • 手机:Windows 10。

Package.appxmanifest中声明TargetDeviceFamily的MinVersion="10.0.10240.0",即首个 Windows 10 正式版本即可运行。

构建步骤

  1. 若下载的是整个示例集合的 ZIP,务必解压整个归档文件,而不仅是目标示例所在文件夹——示例依赖共享的SharedContent目录;
  2. 启动 Visual Studio,选择File > Open > Project/Solution;
  3. 定位到示例的cs或cppwinrt子目录,双击其中的解决方案文件(BasicFaceDetection.sln);
  4. 按Ctrl+Shift+B或选择Build > Build Solution。

运行步骤

  • 仅部署:选择Build > Deploy Solution;
  • 调试运行:按F5或选择Debug > Start Debugging;
  • 免调试运行:按Ctrl+F5或选择Debug > Start Without Debugging。

重要边界:FaceDetector 与 FaceTracker 的选型

README 明确给出了一条关键的设计约束:FaceDetector面向静态图像或视频单帧,并非为视频播放或实时摄像头流优化。如果需要实时跟踪人脸(无论是直播流还是视频片段),应改用FaceTrackerAPI。

仓库中的相关示例可直接对照学习:

  • BasicFaceTracking:实时人脸跟踪示例(C++/C#/C++/WinRT);
  • HolographicFaceTracking:基于 C++ 的混合现实人脸跟踪;
  • FilePicker:照片场景依赖的文件选择器用法;
  • CameraStarterKit:摄像头初始化与预览的完整示例;
  • SimpleImaging:位图解码与SoftwareBitmap基础操作。

小结

BasicFaceDetection 示例完整演示了 UWP 人脸检测的四个核心环节:FaceDetector的创建与复用、输入位图的获取与像素格式转换、DetectFacesAsync的调用,以及基于Canvas的检测结果可视化。其源码还包含两个值得迁移到实际项目的工程细节:用 1280 像素高度上限对大图降采样以平衡性能与精度,以及用Tag保存原始包围盒坐标实现窗口缩放时的人脸框自适应。理解这些实现后,你可以快速为自己的 UWP 应用接入静态图片/单帧视频的人脸检测能力,并在需要连续跟踪时平滑切换到FaceTracker方案。

  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载

相关推荐

上一篇:5个步骤掌握Audiveris:免费开源乐谱识别软件的终极指南
下一篇:Semantica store migrate 命令详解:FAISS、SQLite 与 pgvector 之间迁移向量库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询