2016 年 10 月

第 31 卷,第 10 期

此文章由机器翻译。

认知服务 - Xamarinn.Forms 中 Microsoft 认知服务的面部和表情识别

作者:Alessandro Del Del

在 Build 2016 会议上,Microsoft 宣布推出首个预览版认知服务 (microsoft.com/cognitive-services),这是一组丰富的跨平台 RESTful API,你可以利用它们基于任意设备上任意平台的自然用户交互创建下一代应用程序。认知服务(也称为“Project Oxford”(牛津计划))基于机器学习并完全融入“对话即平台”哲学,Microsoft 愿意将其带入应用程序生态系统。在更高层面,认知服务 API 可通过 RESTful 服务获得,目前提供以下类别的 API:

  • 影像: 影像服务提供可以分析图像和视频的 API,以识别面部和表情,并检测可操作的信息。该类别包括计算机影像、面部、表情和视频 API。
  • 语音: 语音服务提供的 API 更容易实现文本到语音、自然语音识别,甚至识别谁在与说话人识别服务对话。它们包括 Bing 语音、自定义识别智能服务和说话人识别 API。
  • 语言: 语言服务以自然语言理解为导向,这意味着检测和修复拼写错误、理解语音命令,以及分析复杂的文本(包括情绪和关键词组)。它们包括 Bing 拼写检查、语言理解智能服务、语言分析、文本分析和网络语言模型 API。
  • 知识: 知识服务通过寻找个性化的产品推荐、事件、地点和学术论文或期刊帮助应用程序扩大客户知识范围。其中包括学术知识、实体链接智能服务、知识探索服务和推荐 API。
  • Search: 基于 Bing 的 Search 服务允许你在其应用程序中采用强大的搜索工具。所包含的服务名称都非常容易理解: Bing AutoSuggest、Bing 图像搜索、Bing 新闻搜索、Bing 视频搜索和 Bing 网页搜索 API。

在本文中,我将介绍如何结合面部和表情 API,从你可以使用照相机拍摄的照片或借由运行于 Android、iOS 或 Windows 10 的 C# 和 Visual Studio 2015 创建的 Xamarin.Forms 应用程序磁盘上的相册中的照片检索面部细节和表情。图 1 显示了本文教程的结果。值得一提的是,在本文中使用 Xamarin.Forms 时,同样可以通过传统的 Xamarin 应用程序,以及支持 REST 的任意其他平台来完成操作。前提是你对创建 Xamarin.Forms 应用程序和代码共享的概念有基本的了解;如果不了解,请务必阅读我之前的文章: “使用 Xamarin.Forms 构建跨平台用户体验” (msdn.com/magazine/mt595754) 和“使用 Xamarin.Forms 跨移动平台共享 UI 代码” (msdn.com/magazine/dn904669)。

通过 Xamarin.Forms 创建的跨平台应用程序上的面部和表情识别
图 1 通过 Xamarin.Forms 创建的跨平台应用程序上的面部和表情识别(左侧为 Android 设备,右侧为 Windows 10 桌面)

订阅识别服务 API

为了构建充分利用认知服务的应用程序,必须订阅你感兴趣的服务。目前,Microsoft 支持免费试用,你可以在订阅页面激活 (bit.ly/2b2rKDO),但目前的计划可能在未来会有所更改。在页面上,注册一个 Microsoft 帐户,然后单击“请求新的试用”。 你会看到一个可用服务列表;确保选择面部和表情 API 的免费预览版。此时,订阅页面将显示活动服务列表;你应该会看到面部和表情 API 订阅。图 2 显示了基于我的订阅的一个示例。请注意,每个活动服务有两个密钥。你将需要其中一个密钥来调用 API。现在,保持密钥处于隐藏状态。创建 Xamarin.Forms 应用程序时,你将取消密钥隐藏。

激活面部和表情 API 订阅
图 2 激活面部和表情 API 订阅

一般而言,认知服务提供 RESTful API,这意味着你可以通过任何平台上的 HTTP 请求使用支持 REST 的任何语言与这些服务交互。例如,以下 HTTP POST 请求演示了如何向表情识别服务发送图像进行表情检测:

POST https://api.projectoxford.ai/emotion/v1.0/recognize HTTP/1.1
Content-Type: application/json
Host: api.projectoxford.ai
Content-Length: 107
Ocp-Apim-Subscription-Key: YOUR-KEY-GOES-HERE
{ "url": "http://www.samplewebsite.com/sampleimage.jpg" }

当然,你必须使用你的其中一个密钥来替换 Ocp-Apim 订阅密钥,使用实际的图像地址替换虚构的图像 URL。在交换中,表情识别服务将发送回检测结果作为 JSON 响应,如图 3 所示。

图 3 表情识别服务检测响应

[
  {
    "faceRectangle": {
      "height": 70,
      "left": 26,
      "top": 35,
      "width": 70
    },
    "scores": {
      "anger": 2.012591E-11,
      "contempt": 1.95578984E-10,
      "disgust": 1.02281912E-10,
      "fear": 1.16242682E-13,
      "happiness": 1.0,
      "neutral": 9.79047E-09,
      "sadness": 2.91102975E-10,
      "surprise": 1.71011272E-09
    }
  }
]

图 3 中的示例响应显示了表情服务返回的一个框,其中有检测到的人脸和称为分数的数组,该数组中包含表情列表和一个介于 0 和 1 之间的值(该值表示表情代表实际表情的可能性)。在一般情况下,向 RESTful 服务发送 HTTP 请求和期待 JSON 响应是所有认知服务的常用方法。但是,对于使用 C # 的.NET 开发人员,Microsoft 还提供客户端可移植库,你可以从 NuGet 中下载这些库,并且这些库使得能够通过完全以对象为导向的方式更轻松地与托管代码中的服务交互。这是有关面部和表情 API 的情况,稍后你将看到。不要忘了查看官方文档,其中包含基于 REST 方法和可用客户端库的示例 (bit.ly/2b2KJrB)。注册这两个服务并拥有密钥后,接下来就可以通过 Xamarin.Forms 和 Microsoft Visual Studio 2015 来创建跨平台应用程序了。

创建 Xamarin.Forms 应用程序

正如你所了解的那样,你可以通过选择可移植或共享的项目模板使用 Xamarin.Forms 来创建跨平台应用程序。因为我将介绍如何针对认知服务 API 利用客户端库,所以该示例应用程序基于可移植类库 (PCL) 模型。在 Visual Studio 2015 中,选择“文件 | 新建项目”。如果你已经安装有最新的 Xamarin (xamarin.com/download),你会在“新建项目”对话框的跨平台节点 Visual C# 下发现一个新的叫做 Blank Xaml 应用程序(Xamarin.Forms 可移植)的项目模板。这是一个有意思的模板,它提供了一个空白的 XAML 页面,而不需要手动创建。图 4 显示了新模板。

调用解决方案 FaceEmotionRecognition 并单击“确定”。在该解决方案的生成过程中,系统会要求你为通用 Windows 平台 (UWP) 项目指定最低目标版本。这完全由你自己决定,但我建议目标设定为可用的最高版本。

创建新的 Xamarin.Forms 应用程序
图 4 创建新的 Xamarin.Forms 应用程序

为 Xamarin 引入插件

示例应用程序将使用认知服务 API 来识别照片中的面部细节和表情(用设备的现有照片或通过照相机拍摄新照片)。这意味着该应用程序将需要访问 Internet 来连接到服务,并需要提供拍摄和选择照片的功能。虽然应用程序可以轻松连接到网络,但作为开发人员,你有责任检查网络可用性。其实,像检查网络可用性和拍照等功能需要在 Android、iOS 和 Windows 项目中编写特定的代码。非常幸运的是,Xamarin 支持可以在 Xamarin.Forms 中使用以及可以安装到 PCL 项目的插件,让它们代你完成作业。插件是一个从 NuGet 安装的库,它将本机 API 包装成公用代码实现,并在 PCL 项目中调用。其中有大量的插件,一些由 Xamarin 支持和开发,其他由开发人员社区创建并发布。插件都是开放源代码,并在 bit.ly/29XZ3VM 的 GitHub 上列出。在本文中,我将展示如何使用连接插件和媒体插件。

安装 NuGet 包

解决方案准备就绪后,你需要做的第一件事是安装以下NuGet 包:

  • Microsoft.ProjectOxford.Face: 为面部 API 安装客户端库,并且只能安装到 PCL 项目。
  • Microsoft.ProjectOxford.Emotion: 为表情 API 和其他如面部 API 安装客户端库,只能安装到 PCL 项目。
  • Xam.Plugin.Connectivity: 包括 Xamarin.Forms 的连接插件,且必须安装到解决方案中的所有项目。
  • Xam.Plugin.Media: 包括 Xamarin.Forms 的媒体插件(例如连接 API 等),必须安装到解决方案中的所有项目。

安装好所需的 NuGet 包后,务必在编写代码前生成解决方案,以便刷新所有引用。

设计 UI

示例应用程序的 UI 由单个页面组成。为简单起见,我将使用自动生成的 Mainpage.xaml 文件。本页中定义了两个按钮,一个用于通过照相机拍照,另一个用于上传现有图像;ActivityIndicator 控件在等待服务响应时将显示忙碌状态;图像控件将显示所选图像;Stacklayout 面板中与自定义类进行数据绑定的多个标签将包含选定照片的检测结果。图 5 显示了页面的完整 XAML 代码。

图 5 主页 UI

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:FaceEmotionRecognition"
  xmlns:conv="clr-namespace:FaceEmotionRecognition. 
    Converters;assembly=FaceEmotionRecognition"
    x:Class="FaceEmotionRecognition.MainPage">
  <StackLayout Orientation="Vertical">
    <Button x:Name="TakePictureButton" Clicked="TakePictureButton_Clicked"
      Text="Take from camera"/>
    <Button x:Name="UploadPictureButton" Clicked="UploadPictureButton_Clicked"
      Text="Pick a photo"/>
    <ActivityIndicator x:Name="Indicator1" IsVisible="False" IsRunning="False" />
    <Image x:Name="Image1" HeightRequest="240" />
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Gender: "/>
      <Label x:Name="GenderLabel" Text="{Binding Path=Gender}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Age: "/>
      <Label x:Name="AgeLabel" Text="{Binding Path=Age}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Emotion: "/>
      <Label x:Name="EmotionLabel" Text="{Binding Path=Emotion}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Smile: "/>
      <Label x:Name="SmileLabel"
        Text="{Binding Path=Smile}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Glasses: "/>
      <Label x:Name="GlassesLabel" Text="{Binding Path=Glasses}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Beard: "/>
      <Label x:Name="BeardLabel"
        Text="{Binding Path=Beard}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Moustache: "/>
      <Label x:Name="MoustacheLabel"
        Text="{Binding Path=Moustache}"/>
    </StackLayout>
  </StackLayout>
</ContentPage>

下一步是准备一个存储面部和表情检测结果的地方。

通过类存储检测结果

将面部和表情检测结果存储到 UI 中的最佳做法是创建一个自定义类,而不是手动填充标签。这不仅是一个更加以对象为导向的方法,同时还允许将类实例的数据绑定到 UI。也就是说,我们来创建一个名为 FaceEmotionDetection 的新类:

public class FaceEmotionDetection
{
  public string Emotion { get; set; }
  public double Smile { get; set; }
  public string Glasses { get; set; }
  public string Gender { get; set; }
  public double Age { get; set; }
  public double Beard { get; set; }
  public double Moustache { get; set; }
}

每个属性都有一个非常容易理解的名称,并且将存储­来自面部和表情 API 组合的信息。

声明服务客户端

在编写任何其他代码之前,最好添加以下使用指令:

using Microsoft.ProjectOxford.Emotion;
using Microsoft.ProjectOxford.Emotion.Contract;
using Microsoft.ProjectOxford.Face;
using Microsoft.ProjectOxford.Face.Contract;
using Plugin.Connectivity;
using Plugin.Media;

这些指令将简化对认知服务 API 和插件对象名称的调用。面部 API 和表情 API 提供 Microsoft.ProjectOxford Face.Face­ServiceClient 和 Microsoft.ProjectOxford.Emotion.Emotion­ServiceClient 类,这些类连接到识别服务,并分别返回有关面部和表情细节的详细信息。首先要做的是声明两者的一个实例,将密钥传递给构造函数,如下所示:

private readonly IFaceServiceClient faceServiceClient;
private readonly EmotionServiceClient emotionServiceClient;
public MainPage()
{
  InitializeComponent();
  // Provides access to the Face APIs
  this.faceServiceClient = new FaceServiceClient("YOUR-KEY-GOES-HERE");
  // Provides access to the Emotion APIs
  this.emotionServiceClient = new EmotionServiceClient("YOUR-KEY-GOES-HERE");
}

请注意,必须提供你自己的密钥。面部和表情 API 密钥可在 Microsoft 识别服务门户的订阅页面找到 (bit.ly/2b2rKDO),如图 2 所示。

捕获并加载图像

在 Xamarin.Forms 中,访问照相机和文件系统都需要编写特定于平台的代码。一个简单的方法是将媒体插件用于 Xamarin.Forms,它可以让你在 PCL 项目中从磁盘选择照片和视频,以及使用照相机拍摄照片和视频,并且只用几个代码行。该插件公开了一个名为 CrossMedia 的类,其中公开了以下成员:

  • Current: 返回 CrossMedia 类的一个实例。
  • IsPickPhotoSupported  和  IsPickVideoSupported: 如果当前设备支持从磁盘中选择照片和视频,则 bool 属性返回 true。
  • PickPhotoAsync 和 PickVideoAsync: 调用特定于平台的 UI 来相应地选择本地照片或视频并返回媒体文件类型的对象的方法。
  • IsCameraAvailable: 如果设备内置照相机,则 bool 属性返回 true。
  • IsTakePhotoSupported  和  IsTakeVideoSupported: 如果当前设备支持通过照相机拍摄照片和视频,则 bool 属性返回 true。
  • TakePhotoAsync 和 TakeVideoAsync: 启动内置照相机来相应地拍摄照片或视频,并返回媒体文件类型的对象的方法。

不要忘记在应用程序清单中设置访问照相机的适当权限。例如,在 UWP 项目中,你需要网络摄像头和照片库权限,而在 Android 上你需要 CAMERA、READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 权限。忘记设置所需的权限会导致运行时异常。现在我们为 UploadPictureButton 编写 Clicked 事件处理程序,如图 6 所示。

图 6 从磁盘选择一张照片

private async void UploadPictureButton_Clicked(object sender, EventArgs e)
{
  if (!CrossMedia.Current.IsPickPhotoSupported)
  {
    await DisplayAlert("No upload", "Picking a photo is not supported.", "OK");
    return;
  }
  var file = await CrossMedia.Current.PickPhotoAsync();
  if (file == null)
    return;
  this.Indicator1.IsVisible = true;
  this.Indicator1.IsRunning = true;
  Image1.Source = ImageSource.FromStream(() => file.GetStream());
  this.Indicator1.IsRunning = false;
  this.Indicator1.IsVisible = false;
}

代码首先检查选取的照片是否受支持,如果IsPickPhotoSupported 返回 false,则显示一条错误消息。PickPhoto­Async(以及 PickVideoAsync)返回媒体­文件类型的对象,这是一个在 Plugin.Media 命名空间中定义的类,表示所选文件。你必须调用其 GetStream 方法来返回一个流,该流可通过其 FromStream 方法用作图像控制的源。使用照相机拍摄照片也很容易,如图 7 所示。

图 7 使用照相机拍摄照片

private async void TakePictureButton_Clicked(object sender, EventArgs e)
{
  await CrossMedia.Current.Initialize();
  if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.
    IsTakePhotoSupported)
  {
    await DisplayAlert("No Camera", "No camera available.", "OK");
    return;
  }
  var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
  {
    SaveToAlbum = true,
    Name = "test.jpg"
  });
  if (file == null)
    return;
  this.Indicator1.IsVisible = true;
  this.Indicator1.IsRunning = true;
  Image1.Source = ImageSource.FromStream(() => file.GetStream());
  this.Indicator1.IsRunning = false;
  this.Indicator1.IsVisible = false;
}

这里比较有趣的一点是,TakePhotoAsync 采用 StoreCameraMediaOptions 类型的参数,通过该对象你可以指定在什么位置以及如何保存照片。如果你想要将照片保存到本机照片中,可以将 SaveToAlbum 属性设置为 true,或者如果你想要保存到其他文件夹,则可以设置 Directory 属性。正如你所见,只需花上一点点功夫和几个代码行,你的应用程序便可以很轻松地利用所有支持平台的­重要功能。

检测表情和实现面部识别

现在可以实现面部和表情识别了。因为这是一篇介绍性的文章,所以我主要侧重于简单明了。我将展示如何对照片中的单个面部进行检测,并介绍 API 中最重要的对象和成员。同时会给出关于如何在适当的地方实现更详细检测的建议。基于上述假设,让我们来编写一个执行检测的异步方法。第一个是关于表情检测,如下所示:

private async Task<FaceEmotionDetection> DetectFaceAndEmotionsAsync(MediaFile inputFile)
{
  try
  {
    // Get emotions from the specified stream
    Emotion[] emotionResult = await
      emotionServiceClient.RecognizeAsync(inputFile.GetStream());
    // Assuming the picture has one face, retrieve emotions for the
    // first item in the returned array
    var faceEmotion = emotionResult[0]?.Scores.ToRankedList();

该方法接收通过选取或拍摄照片生成的媒体文件。检测照片上的面部表情非常简单,因为你只需从 EmotionServiceClient 类的实例调用 RecognizeAsync 方法。此方法可以接收流或 URL 作为参数。在本例中,它从媒体文件对象中获取流。RecognizeAsync 返回一组表情对象。数组中的每个表情将存储在照片中的单个面部上检测到的表情。假设选定的照片只有一个面部,则代码将检索数组中的第一个项。表情类型公开了一个名为 Scores 的属性,它包含其中有八个表情名称的列表以及它们的近似值。更确切地说,你将获取一个 IEnumerable<string, float>。通过调用其 TorankedList 方法,你可以得到一个经检测的表情分类列表。API 无法准确地检测出单个表情。相反,它们只是检测到一些可能的表情。返回的最高值大约就是面部实际表情,但仍然有可以检查到的其他值。  该列表中的最高值代表估计可能性最高的表情,可能是面部的实际表情。为了更好地理解,请考虑以下在调试程序数据提示的帮助下检索到的表情排名列表(基于如图 1 所示的示例照片):

[0] = {[Happiness, 1]}
[1] = {[Neutral, 1.089301E-09]}
[2] = {[Surprise, 7.085784E-10]}
[3] = {[Sadness, 9.352855E-11]}
[4] = {[Disgust, 4.52789E-11]}
[5] = {[Contempt, 1.431213E-11]}
[6] = {[Anger, 1.25112E-11]}
[7] = {[Fear, 5.629648E-14]}

正如你所看到的,幸福的值为 1,这是列表中的最高值,是实际表情的估计可能性。下一步是检测面部属性。FaceServiceClient 类曝光非常强大的 DtectAsync 方法。它不仅可以检索面部属性,如性别、年龄和微笑,还可以识别人,返回面部框(照片上检测到的面部区域),而且 27 个面部界标点可以让应用程序识别照片中的鼻子、嘴巴、耳朵和眼睛等信息。DetectAsync 具有以下特征:

Task<Contract.Face[]> DetectAsync(Stream imageStream,
  bool returnFaceId = true, bool returnFaceLandmarks = false,
  IEnumerable<FaceAttributeType> returnFaceAttributes = null);

在 DetectAsync 最基本的调用中,它需要一个指向照片或 URL 的流,并返回面部框,而 returnFaceId 和returnFaceLandmarks 可选参数分别让你识别一个人并返回面部界标。通过面部 API 可创建一组人,并给每个人分配一个 id,因此你可以很容易地进行识别。取代的面部界标对识别面部特征非常有用,并将通过面部对象的 FaceLandmarks 属性提供。识别和界标都超出了本文的范围,但可以分别在 bit.ly/2adPvoPbit.ly/2ai9WjV 找到更多关于这些主题的信息。同样,关于如何使用面部界标的内容不会进行介绍,但它们存储在面部对象的 FaceLandmarks 属性中。在当前的示例方案中,目标是检索面部属性。首先需要的是一组 FaceAttributeType 枚举,它定义了你要检索的属性列表:

// Create a list of face attributes that the
// app will need to retrieve
var requiredFaceAttributes = new FaceAttributeType[] {
  FaceAttributeType.Age,
  FaceAttributeType.Gender,
  FaceAttributeType.Smile,
  FaceAttributeType.FacialHair,
  FaceAttributeType.HeadPose,
  FaceAttributeType.Glasses
  };

接下来,调用 DetectAsync,传递图像流和面部属性列表。returnFaceId 和 returnFaceLandmarks 参数为 false,因为此时相关信息是不必要的。方法调用类似如下:

// Get a list of faces in a picture
var faces = await faceServiceClient.DetectAsync(inputFile.GetStream(),
  false, false, requiredFaceAttributes);
// Assuming there is only one face, store its attributes
var faceAttributes = faces[0]?.FaceAttributes;

DetectAsync 返回一组面部对象,每个对象代表照片中的一张面部。代码获取数组中的第一个项(它表示一个面部),并检索其面部属性。注意最后一行如何使用随 C# 6 引入的空条件运算符 (?),如果数组中的第一个元素也是 null,该运算符会返回 null,而不是引发 NullReferenceException。可在 bit.ly/2bc8VZ3 中找到有关此运算符的更多信息。你已了解面部和表情的相关信息,现在可以创建 FaceEmotionDetection 类的一个实例并填充其属性,如以下代码所演示:

FaceEmotionDetection faceEmotionDetection = new FaceEmotionDetection();
faceEmotionDetection.Age = faceAttributes.Age;
faceEmotionDetection.Emotion = faceEmotion.FirstOrDefault().Key;
faceEmotionDetection.Glasses = faceAttributes.Glasses.ToString();
faceEmotionDetection.Smile = faceAttributes.Smile;
faceEmotionDetection.Gender = faceAttributes.Gender;
faceEmotionDetection.Moustache = faceAttributes.FacialHair.Moustache;
faceEmotionDetection.Beard = faceAttributes.FacialHair.Beard;

此时需要注意以下几点:

  • 表情列表中的最高值是通过调用返回 IEnumerable<string, float> 的 Scores.ToRankedList 方法后调用 FirstOrDefault 获得。
  • 这里 FirstOrDefault 返回的值是 KeyValuePair <string, float> 类型的对象,并且字符串类型的密钥存储在将在 UI 中显示的人工可读文本中。
  • 眼镜是指定经检测的面部是否佩戴眼镜以及眼镜类型的枚举。为了简单起见,该代码调用 ToString,但你完全可以采用转换器来实现不同的字符串格式。

该方法主体中的最后信息块返回 FaceEmotionDetection 类的实例,并执行异常处理:

return faceEmotionDetection;
  }
  catch (Exception ex)
  {
    await DisplayAlert("Error", ex.Message, "OK");
    return null;
  }
}

要做的最后一件事是调用自定义的 DetectFaceAndEmotionAsync 方法。你可以在 Activityindicator 控件的 IsRunning 和 IsVisible 属性设置为 false 前,在两个 Clicked 事件处理程序内部执行此操作:

FaceEmotionDetection theData = await DetectFaceAndEmotionsAsync(file);
this.BindingContext = theData;
this.Indicator1.IsRunning = false;
this.Indicator1.IsVisible = false;

网页的 BindingContext 属性接收 FaceeMotiondetection 类的一个实例作为数据源,并且数据绑定子控件将自动显示相关信息。通过 Model-View-ViewModel 等模式,可以使用 ViewModel 类封装结果。做大量的准备工作后,现在便可以准备对应用程序进行测试了。

测试应用程序

选择你所选的平台并按 F5。如果使用 Microsoft 仿真器,可以利用仿真工具来选择物理网络摄像头进行拍照,并且可以模拟 SD 卡来上载文件。图 1 显示了 Android 设备和在桌面模式下运行的 Windows 10 上对我的照片的检测结果。

面部和表情 API 表现极为出色,因为返回的值非常接近实际情况(尽管仍然为近似值)。值得一提的是,FaceEmotionDetection 类有一些 double 类型的属性,如微笑、胡子和髭。他们返回的数值可能对实际应用程序中的最终用户没有太大意义。因此,如果你想把这些数值转换为人工可读字符串,可以考虑采用值转换器和 IValueConverter 接口 (bit.ly/2bZn01J)。

执行网络连接检查

一个设计良好的需要访问 Internet 资源的应用程序,首先务必要检查连接可用性。对于访问照相机和文件系统,在 Xamarin.Forms 中检查连接可用性会要求有特定于平台的代码。幸运的是,连接插件可提供帮助,它会提供一个共享的方式来直接从 PCL 项目执行此检查。该插件提供一个名为 CrossConnectivity 的类,其 Current 属性代表该类的一个实例。它公开一个名为 IsConnected 的 bool 属性,如果连接可用,就会返回 true。若要检查示例应用程序的网络可用性,只需在声明 DetectFaceAndEmotionAsync 方法后放置以下代码:

private async Task<FaceEmotionDetection>
  DetectFaceAndEmotionsAsync(MediaFile inputFile)
{
  if(!CrossConnectivity.Current.IsConnected)
  {
    await DisplayAlert("Network error",
      "Please check your network connection and retry.", "OK");
    return null;
  }

该类还公布以下有趣的成员:

  • ConnectivityChanged: 连接状态更改时引发的事件。你可以订阅此事件,并通过 ConnectivityChangedEventArgs 类型的对象获取有关连接状态的信息。
  • BandWidths: 为当前平台返回可用带宽列表的属性。

可在 bit.ly/2bbU7wu 找到有关连接插件的其他信息。

总结

Microsoft 认知服务提供了基于机器学习的 RESTful 服务和丰富 API,让你可创建下一代应用程序。通过将这些服务的强大力量与 Xamarin 相结合,你将能够为面向 Android、iOS 和 Windows 的跨平台应用程序带来自然用户交互,从而为客户打造卓越的体验。


Alessandro Del Sole自 2008 年起被评为 Microsoft MVP。他已经 5 次获得年度 MVP 这一殊荣,发表过很多关于 Visual Studio .NET 开发的书籍、电子书、指导视频和文章。Del Sole 是 Brain-Sys (brain-sys.it) 的解决方案开发专家,专注于 .NET 开发、培训和咨询。你可以关注他的 Twitter @progalex

衷心感谢以下技术专家对本文的审阅: James McCaffrey 和 James Montemagno