65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 UWP 面部检测 API 在 WPF 中进行面部检测

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2020年10月7日

CPOL

3分钟阅读

viewsIcon

8183

使用 UWP 面部检测 API 在 WPF 中检测图像中的面部

引言

通用 Windows 平台的Windows.Media.FaceAnalysis命名空间包含可用于检测图像文件和视频帧中人脸的 API。人脸检测 API 是一个可用于桌面应用程序的 UWP API,这得益于Windows 10 WinRT API 包。本文将探讨如何在 WPF 应用程序中使用 UWP 人脸检测 API,特别是用于检测图像文件中的面部。

必备组件

要学习本文内容,需要对 MVVM 模式有一定的了解。要运行示例项目,您应该安装以下内容:

  • .NET Core 3.1
  • Visual Studio 2019

背景

示例应用程序是一个 .NET Core WPF 项目,它引用了Microsoft.Windows.SDK.Contracts包(Windows 10 WinRT API 包),这使桌面应用程序能够访问某些 UWP API。用户可以选择一个图像文件,然后单击“**检测人脸**”按钮来检测所选图像中的人脸。

人脸检测

要使用 UWP 人脸检测 API,您必须导入Windows.Media.FaceAnalysis命名空间。在示例项目中,这在包含DetectFaces()方法的FaceDetectionService类中完成,该方法执行人脸检测过程。

    public async Task<IList<DetectedFace>> DetectFaces(Stream fileStream)
    {
        var stream = fileStream.AsRandomAccessStream();
        var bitmapDecoder = await BitmapDecoder.CreateAsync(stream);

        using SoftwareBitmap bitmap = await bitmapDecoder.GetSoftwareBitmapAsync();

        var bmp = FaceDetector.IsBitmapPixelFormatSupported(bitmap.BitmapPixelFormat) 
            ? bitmap : SoftwareBitmap.Convert(bitmap, BitmapPixelFormat.Gray8);

        var faceDetector = await FaceDetector.CreateAsync();
        var detectedFaces = await faceDetector.DetectFacesAsync(bmp);

        return detectedFaces;
    }    

FaceDetector仅适用于SoftwareBitmap,因此目标图像使用BitmapDecoder转换为SoftwareBitmap。然后检查位图像素格式,如果它不是当前设备上FaceDetector支持的格式之一,则进行转换。DetectFacesAsync()检测SoftwareBitmap中的人脸,并返回DetectedFace对象的集合。

标记人脸

为了使用边界框标记检测到的人脸,我使用了System.Drawing命名空间中的Graphics类。

    public Bitmap DetectedFacesBitmap(Stream fileStream, IList<DetectedFace> detectedFaces, 
        Color boxColor, int strokeThickness = 2)
    {
        var bitmap = new Bitmap(fileStream);
            
        using (var graphics = Graphics.FromImage(bitmap))
        {
            using var stroke = new Pen(boxColor, strokeThickness);
            foreach (var face in detectedFaces)
            {
                BitmapBounds faceBox = face.FaceBox;
                graphics.DrawRectangle(stroke, (int)faceBox.X, (int)faceBox.Y,
                        (int)faceBox.Width, (int)faceBox.Height);
            }
        }
        return bitmap;
    }    

DetectedFace包含一个名为FaceBox的属性,该属性提供检测到的人脸的边界。绘制包含检测到人脸的图像上的矩形时,将使用这些边界。

ViewModel

示例项目遵循 MVVM 模式,并且只有一个视图模型 – MainWindowViewModel。此视图模型包含两个属性;一个类型为string,指定所选图像的路径;另一个类型为Bitmap,用于处理后的图像。视图模型还包含用于执行图像选择和人脸检测的命令。

    using System.Drawing;
    using System.IO;
    using System.Threading.Tasks;
    using FaceDetection.Commands;
    using FaceDetection.Services.Interfaces;

    namespace FaceDetection.ViewModels
    {
        public class MainWindowViewModel : ViewModelBase
        {
            private readonly IDialogService dialogService;
            private readonly IFaceDetectionService faceDetectionService;

            public MainWindowViewModel(IDialogService dialogSvc, 
                                       IFaceDetectionService faceDetectionSvc)
            {
                dialogService = dialogSvc;
                faceDetectionService = faceDetectionSvc;
            }

            private string _selectedImage;
            public string SelectedImage
            {
                get => _selectedImage;
                set
                {
                    _selectedImage = value;
                    OnPropertyChanged();
                }
            }

            #region Select Image Command
            private RelayCommand _selectImageCommand;
            public RelayCommand SelectImageCommand =>
                _selectImageCommand ??= new RelayCommand(_ => SelectImage());

            private void SelectImage()
            {
                var image = dialogService.PickFile("Select Image",
                    "Image (*.jpg; *.jpeg; *.png)|*.jpg; *.jpeg; *.png");

                if (string.IsNullOrWhiteSpace(image)) return;

                SelectedImage = image;
            }
            #endregion

            private Bitmap _facesBitmap;
            public Bitmap FacesBitmap
            {
                get => _facesBitmap;
                set
                {
                    _facesBitmap = value;
                    OnPropertyChanged();
                }
            }

            #region Detect faces Command
            private RelayCommandAsync _detectFacesCommand;
            public RelayCommandAsync DetectFacesCommand =>
                _detectFacesCommand ??= new RelayCommandAsync
                                        (DetectFaces, _ => CanDetectFaces());

            private async Task DetectFaces()
            {
                await using FileStream fileStream = File.OpenRead(_selectedImage);
                var faces = await faceDetectionService.DetectFaces(fileStream);
                FacesBitmap = faceDetectionService.DetectedFacesBitmap
                              (fileStream, faces, Color.GreenYellow);
                SelectedImage = null;
            }

            private bool CanDetectFaces() => !string.IsNullOrWhiteSpace(SelectedImage);
            #endregion
        }
    }    

视图

使用MainWindow.xamlImage控件的数据触发器在所选图像和处理后的图像之间进行切换。

    <Image Margin="10">
        <Image.Style>
            <Style TargetType="Image">
                <Setter Property="Source" Value="{Binding SelectedImage, Mode=OneWay}"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding SelectedImage}" Value="{x:Null}">
                        <Setter Property="Source" 
                                Value="{Binding FacesBitmap, 
                                Converter={StaticResource BitmapConverter}}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Image.Style>
    </Image>    

由于FacesBitmap的类型为Bitmap,因此必须将其转换为BitmapSource。这是使用转换器完成的。

    public class BitmapToBitmapSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
               object parameter, CultureInfo culture)
        {
            if (value is null) return null;

            using var bitmap = (Bitmap)value;
            using var stream = new MemoryStream();
            bitmap.Save(stream, ImageFormat.Bmp);
            stream.Position = 0;
            var bmpImg = new BitmapImage();
            bmpImg.BeginInit();
            bmpImg.CacheOption = BitmapCacheOption.OnLoad;
            bmpImg.StreamSource = stream;
            bmpImg.EndInit();
            bmpImg.Freeze();
            return bmpImg;
        }

        public object ConvertBack(object value, Type targetType, 
                                  object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }    

结论

如您所见,使用 UWP 人脸检测 API 非常简单。需要注意的是,虽然该 API 非常擅长检测人脸,但它可能无法检测部分可见的人脸,因为像素可能不足以供人脸检测器使用。

就是这样!如果您需要查看示例项目的其余代码,请使用本文顶部的下载链接克隆或下载代码库。

历史

  • 2020 年 10 月 7 日:初始发布
© . All rights reserved.