教程:创建一个用于显示图像中人脸数据的 WPF 应用Tutorial: Create a WPF app to display face data in an image

本教程介绍如何通过 .NET 客户端 SDK 使用 Azure 人脸 API 检测图像中的人脸,然后在 UI 中显示该数据。In this tutorial, you will learn how to use the Azure Face API, through the .NET client SDK, to detect faces in an image and then present that data in the UI. 请创建一个简单的 Windows Presentation Framework (WPF) 应用程序,用于检测人脸,围绕每张脸绘制一个框架,并在状态栏中显示人脸描述。You'll create a simple Windows Presentation Framework (WPF) application that detects faces, draws a frame around each face, and displays a description of the face in the status bar.

本教程演示如何:This tutorial shows you how to:

  • 创建 WPF 应用程序Create a WPF application
  • 安装人脸 API 客户端库Install the Face API client library
  • 使用客户端库检测图像中的人脸Use the client library to detect faces in an image
  • 围绕每个检测到的人脸绘制一个框架Draw a frame around each detected face
  • 在状态栏上显示突出显示的人脸的描述Display a description of the highlighted face on the status bar

显示使用矩形将检测到的人脸定格的屏幕截图

完整的示例代码在 GitHub 上的认知人脸 CSharp 示例存储库中提供。The complete sample code is available in the Cognitive Face CSharp sample repository on GitHub.

如果没有 Azure 订阅,请在开始前创建一个试用帐户If you don't have an Azure subscription, create a Trial Account before you begin.

先决条件Prerequisites

创建 Visual Studio 项目Create the Visual Studio project

执行以下步骤,以便创建新的 WPF 应用程序项目。Follow these steps to create a new WPF application project.

  1. 在 Visual Studio 中打开“新建项目”对话框。In Visual Studio, open the New Project dialog. 展开“已安装”,接着展开“Visual C#”,然后选择“WPF 应用(.NET Framework)”。Expand Installed, then Visual C#, then select WPF App (.NET Framework).
  2. 将应用程序命名为 FaceTutorial,单击“确定”。Name the application FaceTutorial, then click OK.
  3. 获取所需的 NuGet 包。Get the required NuGet packages. 右键单击解决方案资源管理器中的项目,选择“管理 NuGet 包”,然后找到并安装以下包:Right-click on your project in the Solution Explorer and select Manage NuGet Packages; then, find and install the following package:

添加初始代码Add the initial code

在此部分,需添加应用的基本框架,没有特定于人脸的功能。In this section, you will add the basic framework of the app without its face-specific features.

创建 UICreate the UI

打开 MainWindow.xaml,将其中的内容替换为以下代码—这将创建 UI 窗口。Open MainWindow.xaml and replace the contents with the following code—this creates the UI window. 请注意,FacePhoto_MouseMoveBrowseButton_Click 是将在稍后定义的事件处理程序。Note that FacePhoto_MouseMove and BrowseButton_Click are event handlers that you will define later on.

<Window x:Class="FaceTutorial.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Title="MainWindow" Height="700" Width="960">
    <Grid x:Name="BackPanel">
        <Image x:Name="FacePhoto" Stretch="Uniform" Margin="0,0,0,50" MouseMove="FacePhoto_MouseMove" />
        <DockPanel DockPanel.Dock="Bottom">
            <Button x:Name="BrowseButton" Width="72" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Left"
                     Content="Browse..."
                     Click="BrowseButton_Click" />
            <StatusBar VerticalAlignment="Bottom">
                <StatusBarItem>
                    <TextBlock Name="faceDescriptionStatusBar" />
                </StatusBarItem>
            </StatusBar>
        </DockPanel>
    </Grid>
</Window>

创建 main 类Create the main class

打开 MainWindow.xaml.cs,添加客户端库命名空间和其他必需的命名空间。Open MainWindow.xaml.cs and add the client library namespaces, along with other necessary namespaces.

using Microsoft.Azure.CognitiveServices.Vision.Face;
using Microsoft.Azure.CognitiveServices.Vision.Face.Models;

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

接下来,在 MainWindow 类中插入以下代码。Next, insert the following code in the MainWindow class. 这将使用订阅密钥创建 FaceClient 实例,该密钥必须你自己输入。This creates a FaceClient instance using the subscription key, which you must enter yourself. 此外还必须在 faceEndpoint 中将区域字符串设置为订阅的正确区域(如需包含所有区域终结点的列表,请参阅人脸 API 文档)。You must also set the region string in faceEndpoint to the correct region for your subscription (see the Face API docs for a list of all region endpoints).

// Replace <SubscriptionKey> with your valid subscription key.
// For example, subscriptionKey = "0123456789abcdef0123456789ABCDEF"
private const string subscriptionKey = "<SubscriptionKey>";

private const string faceEndpoint = "https://api.cognitive.azure.cn";

private readonly IFaceClient faceClient = new FaceClient(
    new ApiKeyServiceClientCredentials(subscriptionKey),
    new System.Net.Http.DelegatingHandler[] { });

// The list of detected faces.
private IList<DetectedFace> faceList;
// The list of descriptions for the detected faces.
private string[] faceDescriptions;
// The resize factor for the displayed image.
private double resizeFactor;

private const string defaultStatusBarText =
    "Place the mouse pointer over a face to see the face description.";

接下来,将以下代码粘贴到 MainWindow 方法中。Next paste the following code into the MainWindow method.

InitializeComponent();

if (Uri.IsWellFormedUriString(faceEndpoint, UriKind.Absolute))
{
    faceClient.Endpoint = faceEndpoint;
}
else
{
    MessageBox.Show(faceEndpoint,
        "Invalid URI", MessageBoxButton.OK, MessageBoxImage.Error);
    Environment.Exit(0);
}

最后,向类添加 BrowseButton_ClickFacePhoto_MouseMove 方法。Finally, add the BrowseButton_Click and FacePhoto_MouseMove methods to the class. 这两个方法对应于在 MainWindow.xaml 中声明的事件处理程序。These correspond to the event handlers declared in MainWindow.xaml. BrowseButton_Click 方法创建 OpenFileDialog,供用户选择 .jpg 图像。The BrowseButton_Click method creates an OpenFileDialog, which allows the user to select a .jpg image. 然后,它会在主窗口中显示图像。It then displays the image in the main window. 将会在后面的步骤中插入 BrowseButton_ClickFacePhoto_MouseMove 的剩余代码。You will insert the remaining code for BrowseButton_Click and FacePhoto_MouseMove in later steps. 另请注意 faceList 引用—DetectedFace 对象的列表。Also note the faceList reference—a list of DetectedFace objects. 这是应用存储和调用实际人脸数据的位置。This is where your app will store and call the actual face data.

// Displays the image and calls UploadAndDetectFaces.
private async void BrowseButton_Click(object sender, RoutedEventArgs e)
{
    // Get the image file to scan from the user.
    var openDlg = new Microsoft.Win32.OpenFileDialog();

    openDlg.Filter = "JPEG Image(*.jpg)|*.jpg";
    bool? result = openDlg.ShowDialog(this);

    // Return if canceled.
    if (!(bool)result)
    {
        return;
    }

    // Display the image file.
    string filePath = openDlg.FileName;

    Uri fileUri = new Uri(filePath);
    BitmapImage bitmapSource = new BitmapImage();

    bitmapSource.BeginInit();
    bitmapSource.CacheOption = BitmapCacheOption.None;
    bitmapSource.UriSource = fileUri;
    bitmapSource.EndInit();

    FacePhoto.Source = bitmapSource;
}
// Displays the face description when the mouse is over a face rectangle.
private void FacePhoto_MouseMove(object sender, MouseEventArgs e)
{
}

试用应用Try the app

按菜单上的“启动”,对应用进行测试。Press Start on the menu to test your app. 应用窗口打开后,单击左下角的“浏览”。When the app window opens, click Browse in the lower left corner. 此时会打开“打开的文件”对话框。A File Open dialog should appear. 从文件系统中选择一个图像,验证它是否显示在窗口中。Select an image from your filesystem and verify that it displays in the window. 然后,关闭应用并转到下一步。Then, close the app and advance to the next step.

显示未修改的人脸图像的屏幕截图

上传图像并检测人脸Upload image and detect faces

应用会通过调用 FaceClient.Face.DetectWithStreamAsync 方法来检测人脸,该方法可包装用于上传本地图像的检测 REST API。Your app will detect faces by calling the FaceClient.Face.DetectWithStreamAsync method, which wraps the Detect REST API for uploading a local image.

MainWindow 类中 FacePhoto_MouseMove 方法下面插入以下方法。Insert the following method in the MainWindow class, below the FacePhoto_MouseMove method. 这样会定义一系列人脸特性,用于检索提交的图像文件并将其读取到中。This defines a list of face attributes to retrieve and reads the submitted image file into a Stream. 然后,它将这两个对象传递到 DetectWithStreamAsync 方法调用。Then it passes both of these objects to the DetectWithStreamAsync method call.

// Uploads the image file and calls DetectWithStreamAsync.
private async Task<IList<DetectedFace>> UploadAndDetectFaces(string imageFilePath)
{
    // The list of Face attributes to return.
    IList<FaceAttributeType> faceAttributes =
        new FaceAttributeType[]
        {
            FaceAttributeType.Gender, FaceAttributeType.Age,
            FaceAttributeType.Smile, FaceAttributeType.Emotion,
            FaceAttributeType.Glasses, FaceAttributeType.Hair
        };

    // Call the Face API.
    try
    {
        using (Stream imageFileStream = File.OpenRead(imageFilePath))
        {
            // The second argument specifies to return the faceId, while
            // the third argument specifies not to return face landmarks.
            IList<DetectedFace> faceList =
                await faceClient.Face.DetectWithStreamAsync(
                    imageFileStream, true, false, faceAttributes);
            return faceList;
        }
    }
    // Catch and display Face API errors.
    catch (APIErrorException f)
    {
        MessageBox.Show(f.Message);
        return new List<DetectedFace>();
    }
    // Catch and display all other errors.
    catch (Exception e)
    {
        MessageBox.Show(e.Message, "Error");
        return new List<DetectedFace>();
    }
}

在人脸周围绘制矩形Draw rectangles around faces

接下来,将添加用于在图像中每个检测到的人脸周围绘制矩形的代码。Next, you will add the code to draw a rectangle around each detected face in the image. MainWindow 类的 BrowseButton_Click 方法末尾的 FacePhoto.Source = bitmapSource 行后插入以下代码。In the MainWindow class, insert the following code at the end of the BrowseButton_Click method, after the FacePhoto.Source = bitmapSource line. 这样会通过调用 UploadAndDetectFaces 填充检测到的人脸的列表。This populates a list of detected faces from the call to UploadAndDetectFaces. 然后,它会围绕每个人脸绘制一个矩形,并将修改的图像显示在主窗口中。Then it draws a rectangle around each face and displays the modified image in the main window.

// Detect any faces in the image.
Title = "Detecting...";
faceList = await UploadAndDetectFaces(filePath);
Title = String.Format(
    "Detection Finished. {0} face(s) detected", faceList.Count);

if (faceList.Count > 0)
{
    // Prepare to draw rectangles around the faces.
    DrawingVisual visual = new DrawingVisual();
    DrawingContext drawingContext = visual.RenderOpen();
    drawingContext.DrawImage(bitmapSource,
        new Rect(0, 0, bitmapSource.Width, bitmapSource.Height));
    double dpi = bitmapSource.DpiX;
    // Some images don't contain dpi info.
    resizeFactor = (dpi == 0) ? 1 : 96 / dpi;
    faceDescriptions = new String[faceList.Count];

    for (int i = 0; i < faceList.Count; ++i)
    {
        DetectedFace face = faceList[i];

        // Draw a rectangle on the face.
        drawingContext.DrawRectangle(
            Brushes.Transparent,
            new Pen(Brushes.Red, 2),
            new Rect(
                face.FaceRectangle.Left * resizeFactor,
                face.FaceRectangle.Top * resizeFactor,
                face.FaceRectangle.Width * resizeFactor,
                face.FaceRectangle.Height * resizeFactor
                )
        );

        // Store the face description.
        faceDescriptions[i] = FaceDescription(face);
    }

    drawingContext.Close();

    // Display the image with the rectangle around the face.
    RenderTargetBitmap faceWithRectBitmap = new RenderTargetBitmap(
        (int)(bitmapSource.PixelWidth * resizeFactor),
        (int)(bitmapSource.PixelHeight * resizeFactor),
        96,
        96,
        PixelFormats.Pbgra32);

    faceWithRectBitmap.Render(visual);
    FacePhoto.Source = faceWithRectBitmap;

    // Set the status bar text.
    faceDescriptionStatusBar.Text = defaultStatusBarText;
}

描述人脸Describe the faces

将以下方法添加到 MainWindow 类的 UploadAndDetectFaces 方法下面。Add the following method to the MainWindow class, below the UploadAndDetectFaces method. 这会将检索的人脸特性转换成描述人脸的字符串。This converts the retrieved face attributes into a string describing the face.

// Creates a string out of the attributes describing the face.
private string FaceDescription(DetectedFace face)
{
    StringBuilder sb = new StringBuilder();

    sb.Append("Face: ");

    // Add the gender, age, and smile.
    sb.Append(face.FaceAttributes.Gender);
    sb.Append(", ");
    sb.Append(face.FaceAttributes.Age);
    sb.Append(", ");
    sb.Append(String.Format("smile {0:F1}%, ", face.FaceAttributes.Smile * 100));

    // Add the emotions. Display all emotions over 10%.
    sb.Append("Emotion: ");
    Emotion emotionScores = face.FaceAttributes.Emotion;
    if (emotionScores.Anger >= 0.1f) sb.Append(
        String.Format("anger {0:F1}%, ", emotionScores.Anger * 100));
    if (emotionScores.Contempt >= 0.1f) sb.Append(
        String.Format("contempt {0:F1}%, ", emotionScores.Contempt * 100));
    if (emotionScores.Disgust >= 0.1f) sb.Append(
        String.Format("disgust {0:F1}%, ", emotionScores.Disgust * 100));
    if (emotionScores.Fear >= 0.1f) sb.Append(
        String.Format("fear {0:F1}%, ", emotionScores.Fear * 100));
    if (emotionScores.Happiness >= 0.1f) sb.Append(
        String.Format("happiness {0:F1}%, ", emotionScores.Happiness * 100));
    if (emotionScores.Neutral >= 0.1f) sb.Append(
        String.Format("neutral {0:F1}%, ", emotionScores.Neutral * 100));
    if (emotionScores.Sadness >= 0.1f) sb.Append(
        String.Format("sadness {0:F1}%, ", emotionScores.Sadness * 100));
    if (emotionScores.Surprise >= 0.1f) sb.Append(
        String.Format("surprise {0:F1}%, ", emotionScores.Surprise * 100));

    // Add glasses.
    sb.Append(face.FaceAttributes.Glasses);
    sb.Append(", ");

    // Add hair.
    sb.Append("Hair: ");

    // Display baldness confidence if over 1%.
    if (face.FaceAttributes.Hair.Bald >= 0.01f)
        sb.Append(String.Format("bald {0:F1}% ", face.FaceAttributes.Hair.Bald * 100));

    // Display all hair color attributes over 10%.
    IList<HairColor> hairColors = face.FaceAttributes.Hair.HairColor;
    foreach (HairColor hairColor in hairColors)
    {
        if (hairColor.Confidence >= 0.1f)
        {
            sb.Append(hairColor.Color.ToString());
            sb.Append(String.Format(" {0:F1}% ", hairColor.Confidence * 100));
        }
    }

    // Return the built string.
    return sb.ToString();
}

显示人脸描述Display the face description

将以下代码添加到 FacePhoto_MouseMove 方法。Add the following code to the FacePhoto_MouseMove method. 当光标悬停在检测的人脸矩形上时,此事件处理程序会显示 faceDescriptionStatusBar 中的人脸描述字符串。This event handler displays the face description string in faceDescriptionStatusBar when the cursor hovers over a detected face rectangle.

// If the REST call has not completed, return.
if (faceList == null)
    return;

// Find the mouse position relative to the image.
Point mouseXY = e.GetPosition(FacePhoto);

ImageSource imageSource = FacePhoto.Source;
BitmapSource bitmapSource = (BitmapSource)imageSource;

// Scale adjustment between the actual size and displayed size.
var scale = FacePhoto.ActualWidth / (bitmapSource.PixelWidth / resizeFactor);

// Check if this mouse position is over a face rectangle.
bool mouseOverFace = false;

for (int i = 0; i < faceList.Count; ++i)
{
    FaceRectangle fr = faceList[i].FaceRectangle;
    double left = fr.Left * scale;
    double top = fr.Top * scale;
    double width = fr.Width * scale;
    double height = fr.Height * scale;

    // Display the face description if the mouse is over this face rectangle.
    if (mouseXY.X >= left && mouseXY.X <= left + width &&
        mouseXY.Y >= top && mouseXY.Y <= top + height)
    {
        faceDescriptionStatusBar.Text = faceDescriptions[i];
        mouseOverFace = true;
        break;
    }
}

// String to display when the mouse is not over a face rectangle.
if (!mouseOverFace) faceDescriptionStatusBar.Text = defaultStatusBarText;

运行应用程序Run the app

运行此应用程序并通过浏览方式查找包含人脸的图像。Run the application and browse for an image containing a face. 等待几秒钟,以便人脸服务响应。Wait a few seconds to allow the Face service to respond. 此时会在图像中的每个人脸上看到一个红色矩形。You should see a red rectangle on each of the faces in the image. 如果将鼠标移到人脸矩形上,状态栏中会显示该人脸的描述。If you move the mouse over a face rectangle, the description of that face should appear in the status bar.

显示使用矩形将检测到的人脸定格的屏幕截图

后续步骤Next steps

本教程介绍了人脸服务 .NET SDK 的基本使用过程,并创建了一个应用程序来检测并定格图像中的人脸。In this tutorial, you learned the basic process for using the Face service .NET SDK and created an application to detect and frame faces in an image. 接下来,请深入了解人脸检测的详细信息。Next, learn more about the details of face detection.