Октябрь 2016

Том 31, номер 10

Cognitive Services - Распознавание лиц и эмоций в Xamarin.Forms с помощью Microsoft Cognitive Services

Алессандро Дель Соуле | Октябрь 2016 | Исходный код можно скачать по ссылке

Продукты и технологии:

Xamarin.Forms, Microsoft Cognitive Services

В статье рассматриваются:

  • обзор Microsoft Cognitive Services и способ подписки на этот API;
  • использование Face API и Emotion API для получения атрибутов и эмоций лиц на изображениях;
  • манипуляции и отображение результата распознавания в Xamarin.Forms;
  • использование плагинов для Xamarin с целью захвата и загрузки изображений.

Microsoft объявила на конференции Build 2016 о первой предварительной версии Cognitive Services (microsoft.com/cognitive-services), богатом наборе кросс-платформенных RESTful API, с помощью которых можно создавать приложения следующего поколения на основе естественного взаимодействия пользователя с любой платформой на любом устройстве. Cognitive Services, также известные как «Project Oxford», основаны на машинном обучении и отлично укладываются в философию «беседа как платформа» (conversation-as-a-platform), которую Microsoft хочет ввести в экосистему приложений. На более высоком уровне Cognitive Services API доступны через RESTful-сервисы и в настоящее время предлагают следующие категории API.

  • Vision Сервисы Vision предлагают API, которые позволяют анализировать изображения и видео для идентификации лиц и эмоций, а также для обнаружения информации для принятия решений (actionable information). Эта категория включает Computer Vision API, Face API, Emotion API и Video API.
  • Speech Сервисы Speech предлагают API, упрощающие реализацию преобразования текста в речь, распознавания естественной речи и даже для идентификации того, кто говорит с сервисом опознавания говорящего (speaker recognition service). Эта категория включает Bing Speech API, Custom Recognition Intelligent Service API и Speaker Recognition API.
  • Language Сервисы Language ориентированы на восприятие естественного языка, т. е. на обнаружение и исправление орфографических ошибок, распознавание голосовых команд и анализ сложного текста, в том числе эмоциональной окраски и ключевых фраз. Эта категория включает Bing Spell Check API, Language Understanding Intelligent Service API, Linguistic Analysis API, Text Analytics API и Web Language Model API.
  • Knowledge Сервисы Knowledge помогают приложениям расширять знания клиентов за счет нахождения персонализированных рекомендаций по товарам, мероприятиям, местам, а также академическим статьям или журналам. Эта категория включает Academic Knowledge API, Entity Linking Intelligence Service API, Knowledge Exploration Service API и Recommendations API.
  • Search Сервисы Search основаны на Bing и позволяют реализовать мощные средства поиска в приложениях. Названия входящих в эту категорию сервисов понятны без пояснений: Bing Autosuggest API, Bing Image Search API, Bing News Search API, Bing Video Search API и Bing Web Search API.

В этой статье я объясню, как комбинировать Face API и Emotion API, чтобы получать детальные особенности лиц и распознавать эмоции на снимках, сделанных вами камерой или загруженных из альбома на диске в приложении Xamarin.Forms, которое написано на C# в Visual Studio 2015 для выполнения в Android, iOS или Windows 10. На рис. 1 показаны результаты, к которым мы придем к концу статьи. Важно упомянуть, что, несмотря на использование Xamarin.Forms в этой статье, то же самое можно сделать с помощью традиционных приложений Xamarin, а также на любой другой платформе, поддерживающей REST. Я исхожу из того, что у вас есть базовые навыки в создании приложения Xamarin.Forms и базовое понимание концепций общего кода; если это не так, прочитайте мои предыдущие статьи: «Build a Cross-Platform UX with Xamarin.Forms» (msdn.com/magazine/mt595754) и «Share UI Code Across Mobile Platforms with Xamarin.Forms» (msdn.com/magazine/dn904669).

Распознавание лиц и эмоций в кросс-платформенном приложении Xamarin.Forms (Android-устройство слева, настольная версия Windows 10 справа)
Рис. 1. Распознавание лиц и эмоций в кросс-платформенном приложении Xamarin.Forms (Android-устройство слева, настольная версия Windows 10 справа)

Подписка на Cognitive Services API

Чтобы создавать приложения, использующие преимущества Cognitive Services, вы должны подписаться на сервис, который вас интересует. В настоящее время Microsoft предлагает бесплатные пробные подписки, которые можно активировать на странице подписок (bit.ly/2b2rKDO), но текущая тарифная политика может измениться в будущем. Войдите на эту страницу под своей учетной записью Microsoft, затем щелкните Request new trials. После этого вы увидите список доступных сервисов; выберите бесплатные пробные подписки на Face API и Emotion API. К этому моменту ваша страница подписок будет показывать список активных сервисов; вы должны увидеть подписки на Face API и Emotion API. На рис. 2 приведен пример на основе моих подписок. Заметьте, что для каждого активного сервиса предоставляются два секретных ключа. Один из них понадобится для вызова API. А пока спрячьте их куда-либо. Вы покажете один из ключей при создании приложения Xamarin.Forms.

Активация подписок на Face API и Emotion API
Рис. 2. Активация подписок на Face API и Emotion API

Cognitive Services предоставляют 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.png" }

Конечно, вы должны заменить Ocp-Apim-Subscription-Key одним из собственных ключей, а фиктивный 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 показывает, что сервис Emotion вернул прямоугольник, в котором было распознано лицо, и массив scores, содержащий список эмоций; значения между 0 и 1 указывают, насколько вероятна истинность каждой эмоции. В целом, отправка HTTP-запросов RESTful-сервисам и ожидание JSON-ответов — распространенный подход в случае любых Cognitive Services. Однако для .NET-разработчиков, пишущих на C#, Microsoft также предлагает клиентские портируемые библиотеки, которые можно скачать из NuGet и которые упрощают взаимодействие с сервисами в управляемом коде, обеспечивая полностью объектно-ориентированный подход. Как вы вскоре увидите, это относится также к Face API и Emotion API. Не забудьте просмотреть официальную документацию, где содержатся примеры, основанные как на REST, так и на клиентских библиотеках (bit.ly/2b2KJrB). Теперь, когда вы зарегистрировались на оба сервиса и имеете свои ключи, пора переходить к созданию кросс-платформенного приложения с помощью Xamarin.Forms и Microsoft Visual Studio 2015.

Создание приложения Xamarin.Forms

Как вам известно, кросс-платформенные приложения с Xamarin.Forms можно создавать, выбрав шаблон проекта либо Portable, либо Shared. Поскольку я буду объяснять, как использовать клиентские библиотеки для Cognitive Services API, приложение-пример будет основано на модели Portable Class Library (PCL). В Visual Studio 2015 выберите File | New Project. Если вы уже установили последние обновления от Xamarin (xamarin.com/download), то найдете новый шаблон проекта — Blank Xaml App (Xamarin.Forms Portable) в узле Visual C#, Cross-Platform в диалоге New Project. Это интересный шаблон, предоставляющий пустую XAML-страницу, и он избавляет от необходимости самостоятельно создавать такую страницу. Новый шаблон показан на рис. 4.

Создание нового приложения Xamarin.Forms
Рис. 4. Создание нового приложения Xamarin.Forms

Назовите решение FaceEmotionRecognition и щелкните OK. В процессе генерации решения вас попросят указать минимальную целевую версию для проекта Universal Windows Platform (UWP). Это оставляется на ваш выбор, но я рекомендую ориентироваться на высшую доступную версию.

Плагины для Xamarin

Приложение-пример будет использовать Cognitive Services API для распознавания детальных особенностей лиц и эмоций со снимков, существующих на устройстве или только что сделанных камерой. Это подразумевает, что приложению понадобится доступ к Интернету для подключения к сервисам и возможность выбора и создания снимков. Хотя приложение может легко соединиться с сетью, проверка доступности сети является вашей задачей как разработчика. На самом деле такие функции, как проверка доступности сети и возможности создания снимков, потребовали бы написания специфического кода в проектах для Android, iOS и Windows. К счастью, Xamarin поддерживает плагины, которые можно использовать в Xamarin.Forms и которые можно установить в проект PCL, чтобы они выполняли работу за вас. Плагин — это библиотека, устанавливаемая из NuGet, которая обертывает родные API в общий код и вызывается в проекте PCL. Плагинов весьма много — некоторые из них разработаны и поддерживаются Xamarin, а другие созданы и опубликованы сообществом разработчиков. Все плагины имеют открытый исходный код и перечислены на GitHub по ссылке bit.ly/29XZ3VM. В этой статье я покажу, как пользоваться плагинами Connectivity и Media.

Установка NuGet-пакетов

Когда решение готово, первым делом нужно установить следующие NuGet-пакеты.

  • Microsoft.ProjectOxford.Face Устанавливает клиентскую библиотеку для Face API и должен быть установлен только в проект PCL.
  • Microsoft.ProjectOxford.Emotion Устанавливает клиентскую библиотеку для Emotion API и должен быть установлен только в проект PCL, как и в случае Face API.
  • Xam.Plugin.Connectivity Содержит плагин Connectivity для Xamarin.Forms и должен быть установлен во все проекты в решении.
  • Xam.Plugin.Media Содержит плагин Media для Xamarin.Forms и должен быть установлен во все проекты в решении, как и в случае Connectivity API.

После установки необходимых NuGet-пакетов обязательно соберите решение до написания кода, чтобы все ссылки были обновлены.

Дизайн UI

UI приложения-примера состоит из одной страницы. Простоты ради я буду использовать автоматически генерируемый файл MainPage.xaml. Эта страница определяет две кнопки (одна для создания снимка камерой, другая для загрузки существующего изображения), элемент управления ActivityIndicator (показывает состояние занятости при ожидании ответа от сервиса), элемент управления Image (отображает выбранное изображение), набор надписей (меток) в секциях 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; }
}

Каждое свойство имеет понятное без пояснений имя и будет хранить информацию, поступающую от комбинации Face API и Emotion 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;

Это упростит вызов объектов по именам как для Cognitive Services API, так и для плагинов. Face API и Emotion API предоставляют классы Microsoft.ProjectOxford.Face.FaceServiceClient и Microsoft.ProjectOxford.Emotion.EmotionServiceClient, которые подключаются к Cognitive Services и соответственно возвращают информацию о лице и эмоциях. Первым делом вы должны объявить экземпляры обоих этих классов, передав свой секретный ключ в конструктор, как показано ниже:

private readonly IFaceServiceClient faceServiceClient;
private readonly EmotionServiceClient emotionServiceClient;

public MainPage()
{
  InitializeComponent();

  // Обеспечивает доступ к Face API
  this.faceServiceClient =
    new FaceServiceClient("YOUR-KEY-GOES-HERE");
  // Обеспечивает доступ к Emotion API
  this.emotionServiceClient =
    new EmotionServiceClient("YOUR-KEY-GOES-HERE");
}

Заметьте, что вы должны предоставлять свои секретные ключи. Эти ключи для Face API и Emotion API можно найти на странице подписок портала Microsoft Cognitive Services (bit.ly/2b2rKDO), как было показано на рис. 2.

Захват и загрузка изображений

В Xamarin.Forms доступ к камере и файловой системе потребовал бы написания специфичного для платформы кода. Более простой подход — использование плагина Media для Xamarin.Forms, который позволяет загружать снимки и видеозаписи с диска, а также создавать снимки и видеозаписи с помощью камеры из проекта PCL; для этого достаточно написать несколько строк кода. Этот плагин включает класс CrossMedia, который предоставляет следующие члены.

  • Current Возвращает singleton-экземпляр класса CrossMedia.
  • IsPickPhotoSupported и IsPickVideoSupported Булевы свойства, возвращающие true, если текущее устройство поддерживает извлечение снимков и видеозаписей с диска.
  • PickPhotoAsync и PickVideoAsync Методы, вызывающие специфичный для платформы UI, чтобы соответственно извлечь локальный снимок или видеозапись, и возвращающие объект типа MediaFile.
  • IsCameraAvailable Булево свойство, возвращающее true, если устройство имеет встроенную камеру.
  • IsTakePhotoSupported и IsTakeVideoSupported Булевы свойства, возвращающие true, если текущее устройство поддерживает создание снимков или видеозаписей с помощью камеры.
  • TakePhotoAsync и TakeVideoAsync Методы, которые запускают встроенную камеру соответственно для создания снимка или видеозаписи и возвращают объект типа MediaFile.

Не забудьте задать должные разрешения в манифесте приложения для доступа к камере. Например, в UWP-проекте нужны разрешения Webcam и Pictures Library, тогда как для Android требуются разрешения CAMERA, READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE. Если необходимые разрешения не заданы, вы получите исключения в период выполнения. Теперь давайте напишем обработчик событий Clicked для UploadPictureButton (рис. 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. PickPhotoAsync (а также PickVideoAsync) возвращает объект типа MediaFile, который является классом, определенным в пространстве имен Plugin.Media, и который представляет выбранный файл. Вы должны вызвать его метод GetStream, чтобы получить поток данных (stream), применяемый в качестве источника для элемента управления Image через его метод 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.png"
  });

  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, если хотите сохранить снимок в локальной папке для снимков, сделанных камерой (camera roll), или задать свойство Directory, если предпочитаете сохранить снимок в другую папку. Как видите, с минимумом усилий ваше приложение легко использует важную возможность на всех поддерживаемых платформах.

Реализация распознавания лиц и эмоций

Теперь займемся реализацией распознавания лиц и эмоций. Поскольку это вводная статья, я сосредоточусь на том, чтобы реализация была простой. Я покажу, как реализовать распознавание одного лица на снимке, и опишу наиболее важные объекты и члены API. Кроме того, я дам вам предложения по тому, как реализовать более детализированное распознавание, где это имеет смысл. Учитывая эти допущения, давайте приступим к написанию асинхронного метода, выполняющего распознавание. Первая часть относится к распознаванию эмоций и выглядит так:

private async Task<FaceEmotionDetection>
  DetectFaceAndEmotionsAsync(MediaFile inputFile)
{
  try
  {
    // Получаем эмоции из указанного потока данных
    Emotion[] emotionResult = await emotionServiceClient.
      RecognizeAsync(inputFile.GetStream());

    // Предполагая, что на снимке только одно лицо, получаем
    // эмоции для первого элемента в возвращаемом массиве
    var faceEmotion = emotionResult[0]?.Scores.ToRankedList();

Этот метод принимает MediaFile, который создается при выборе или создании снимка. Распознавание эмоций лиц на снимке достаточно прямолинейно, поскольку вы просто вызываете метод RecognizeAsync экземпляра класса EmotionServiceClient. Метод RecognizeAsync может принимать в качестве аргумента либо поток данных, либо URL. Здесь он получает поток данных из объекта MediaFile. RecognizeAsync возвращает массив объектов Emotion. Каждый Emotion в массиве хранит эмоции, обнаруженные для одного лица на снимке. Предполагая, что на выбранном снимке присутствует только одно лицо, код получает первый элемент массива. Тип Emotion предоставляет свойство Scores, содержащее список из названий восьми эмоций и их оценочных значений. Конкретнее, вы получаете IEnumerable<string, float>. Вызовом его метода ToRankedList вы можете получить отсортированный список обнаруженных эмоций. Эти API не в состоянии распознать единственную эмоцию. Вместо этого они распознают ряд возможных эмоций. Наивысшее из возвращенных значений приблизительно соответствует реальной эмоции лица, тем не менее имеются и другие значения, которые можно было бы проверить. Наивысшее значение в этом списке представляет эмоцию с самым высоким уровнем вероятности, что, возможно, является реальной эмоцией лица. Для лучшего понимания посмотрите на следующий ранжированный список эмоций, полученный с помощью подсказок по данным (data tips) отладчика на основе простого снимка, приведенного на рис. 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]}

Как видите, Happiness имеет наивысшее в списке значение 1 — это оценка правдоподобия (estimated likelihood) реальной эмоции. Следующий шаг — обнаружение атрибутов лица. Класс FaceServiceClient предоставляет чрезвычайно мощный метод DetectAsync. Он не только получает атрибуты лица вроде пола, возраста и улыбки, но и распознает людей, возвращая прямоугольник лица (область на снимке, где было распознано лицо), а также 27 опорных точек лица (face landmark points), которые позволяют приложению идентифицировать такую информацию, как положение носа, рта, ушей и глаз на снимке. DetectAsync имеет следующую сигнатуру:

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

При самом базовом вызове DetectAsync требует передачи потока данных снимка или его URL и возвращает прямоугольник лица; returnFaceId и returnFaceLandmarks являются не обязательными параметрами, которые позволяют идентифицировать человека и получить опорные точки лица. Face API дает возможность создавать группы людей и назначать идентификатор каждой персоне, чтобы вы могли легко выполнять распознавание. Опорные точки полезны в идентификации черт лица и будут доступны через свойство FaceLandmarks объекта Face. Как идентификация, так и концепция опорных точек выходит за рамки этой статьи, но вы найдете больше информации на эти темы по ссылкам bit.ly/2adPvoP и bit.ly/2ai9WjV соответственно. Аналогично я не стану показывать, как использовать опорные точки лица. В данном примере целью является получение атрибутов лица. Для этого прежде всего нужен массив перечислимых FaceAttributeType, определяющий список интересующих вас атрибутов:

// Создаем список атрибутов лица, которые
// понадобится получать приложению
var requiredFaceAttributes = new FaceAttributeType[] {
  FaceAttributeType.Age,
  FaceAttributeType.Gender,
  FaceAttributeType.Smile,
  FaceAttributeType.FacialHair,
  FaceAttributeType.HeadPose,
  FaceAttributeType.Glasses
};

Далее вызываем DetectAsync, передавая поток изображения и список атрибутов лица. Аргументы returnFaceId и returnFaceLandmarks равны false, так как соответствующая информация на этот момент не нужна. Вызов метода выглядит так:

// Получаем список лиц на снимке
var faces = await faceServiceClient.DetectAsync(
  inputFile.GetStream(), false, false, requiredFaceAttributes);

// Предполагая, что на снимке только одно лицо,
// сохраняем его атрибуты
var faceAttributes = faces[0]?.FaceAttributes;

DetectAsync возвращает массив объектов Face, каждый из которых представляет лицо на снимке. Код принимает первый элемент массива, представляющий одно лицо, и извлекает из него атрибуты лица. Заметьте, что в последней строке используется оператор проверки на null (null conditional operator) (?), введенный в 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;

Несколько соображений по этому поводу.

  • Наивысшее значение в списке эмоций возвращается вызовом FirstOrDefault применительно к результату вызова метода Scores.ToRankedList, который возвращает IEnumerable<string, float>.
  • Здесь значение, возвращенное FirstOrDefault, — это объект типа KeyValuePair<string, float>, и Key типа string хранит название эмоции в понятном человеку тексте, который будет отображаться в UI.
  • Glasses — это перечисление, которое указывает, есть ли на распознанном лице очки и какого они вида. Этот код вызывает ToString ради простоты, но вы определенно могли бы реализовать конвертер для другого форматирования строки.

Заключительный блок в теле метода возвращает экземпляр класса FaceEmotionDetection и реализует обработку исключений:

return faceEmotionDetection;
  }
  catch (Exception ex)
  {

    await DisplayAlert("Error", ex.Message, "OK");
    return null;
  }
}

Наконец, вы должны вызвать пользовательский метод DetectFaceAndEmotionAsync. Это можно сделать в обоих обработчиках событий Clicked перед самой установкой в false свойств IsRunning и IsVisible элемента управления ActivityIndicator:

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, работающей в режиме для настольных ПК.

Face API и Emotion API проделывают изумительную работу, возвращая значения, очень близкие к истинным, хоть и не совсем точные. Стоит упомянуть, что класс FaceEmotionDetection содержит некоторые свойства типа double, такие как Smile, Beard и Moustache. Они возвращают числовые значения, которые в реальном приложении могут не иметь особого смысла для конечного пользователя. Поэтому, если вы хотите преобразовать эти числовые значения в понятные человеку строки, можно подумать о реализации конвертеров значений и интерфейса IValueConverter (bit.ly/2bZn01J).

Реализация проверки сетевых подключений

Хорошо спроектированное приложение, которому нужен доступ к ресурсам в Интернете, всегда должно первым делом проверять доступность подключения. Что касается камеры и файловой системы, то в Xamarin.Forms проверка доступности соединения потребовала бы специфичного для платформы кода. К счастью, здесь приходит на помощь плагин Connectivity, который обеспечивает общий способ выполнения этой проверки непосредственно из проекта PCL. Плагин предлагает класс CrossConnectivity со свойством Current, которое представляет singleton-экземпляр класса. Оно содержит булево свойство IsConnected, которое просто возвращает 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 Свойство, возвращающее список доступных полос пропускания для текущей платформы.

Дополнительную информацию о плагине Connectivity см. по ссылке bit.ly/2bbU7wu.

Заключение

Microsoft Cognitive Services предоставляют RESTful-сервисы и богатые API, основанные на машинном обучении, которые позволяют создавать приложения следующего поколения. Объединив мощь этих сервисов с Xamarin, вы сможете ввести естественное взаимодействие с пользователем в ваши кросс-платформенные приложения для Android, iOS и Windows, обеспечив клиентам потрясающие возможности.


Алессандро Дель Соуле (Alessandro Del Sole) — был Microsoft MVP с 2008 года. Награждался званием MVP of the Year пять раз. Автор многих печатных и электронных книг, обучающих видеороликов и статей по .NET-разработке в Visual Studio. Является экспертом и разработчиком решений в Brain-Sys (brain-sys.it), основное внимание уделяя .NET-разработке, обучению и консалтингу. Следите за его заметками в Twitter (@progalex).

Выражаю благодарность за рецензирование статьи экспертам Джеймсу Маккафри (James McCaffrey) и Джеймсу Монтемагно (James Montemagno).