October 2016

Volume 31 Number 10

Cognitive Services - Microsoft Cognitive Services による Xamarin.Forms での顔と感情の認識

Alessandro Del Del

Build 2016 カンファレンスで、マイクロソフトは Cognitive Services (microsoft.com/cognitive-services、英語) の最初のプレビューを発表しました。これは、クロスプラットフォーム対応の RESTful API の充実したセットです。この API を利用して、任意のデバイスの任意のプラットフォーム向けに、ユーザーとの自然なやり取りに基づく次世代アプリを開発できます。Cognitive Services (別称「Project Oxford」) は機械学習を土台にしています。これはマイクロソフトがアプリのエコシステムに積極的に持ち込もうとしている、プラットフォームとしての会話 (conversation-as-a-platform) の哲学に沿うものです。もう少し大まかなレベルで言えば、Cognitive Services の API は RESTful サービスから利用でき、現状以下のカテゴリの API が提供されています。

  • 視覚: 視覚サービスでは、画像やビデオを分析して顔や感情を見極め、そこから対応可能な情報を読み取ることができる API が提供されます。このカテゴリの API には、Computer Vision API、Face API、Emotion API、Video API などがあります。
  • 音声: 音声サービスでは、音声合成、自然な音声認識、さらには話者認識サービスに話しかける人物の認識などの実装を容易にする API が提供されます。このカテゴリの API には、Bing Speech API、Custom Recognition Intelligent Service API、Speaker Recognition API などがあります。
  • 言語: 言語サービスは、自然言語の理解 (スペル ミスの検出と修正)、音声コマンドの理解、センチメント、キー フレーズなどの複雑なテキストの分析を目指します。このカテゴリの API には、Bing Spell Check API、Language Understanding Intelligent Service API、Linguistic Analysis API、Text Analytics API、Web Language Model API などがあります。
  • 知識: 知識サービスは、製品レコメンデーション、イベント、場所、学術論文、旅行などを個人向けに探し、アプリケーションが利用する顧客についての知識を広げることができるようにします。このカテゴリの API には、Academic Knowledge API、Entity Linking Intelligence Service API、Knowledge Exploration Service API、Recommendations API などがあります。
  • 検索: 検索サービスは Bing を基盤とし、アプリケーションに強力な検索ツールを実装できるようにします。このカテゴリの API は既に著名なものが多く、 Bing Autosuggest API、Bing Image Search API、Bing News Search API、Bing Video Search API、Bing Web Search API などがあります。

今回は、カメラで撮影した写真や、ディスク上のアルバムから読み取った写真から顔の細部と感情を読み取るために Face API と Emotion API を組み合わせる方法を取り上げます。そのため、C# と Visual Studio 2015 を使って、Android、iOS、Windows 10 で実行される Xamarin.Forms アプリを作成します。図 1 が、今回のチュートリアルの結果です。ここで Xamarin.Forms を使用する部分は、従来の Xamarin アプリや REST をサポートするすべてのプラットフォームで同じになります。今回は、Xamarin.Forms アプリの作成とコード共有の考え方についての基本知識があることを前提とします。こうした基本知識については、以前のコラム 『Xamarin.Forms によるクロスプラットフォーム UX のビルド』(msdn.com/magazine/mt595754) や『Xamarin.Forms によるモバイル プラットフォーム間での UI コードの共有』(msdn.com/magazine/dn904669) で取り上げています。

Xamarin.Forms によるクロスプラットフォーム アプリでの顔と感情の認識
図 1 Xamarin.Forms によるクロスプラットフォーム アプリでの顔と感情の認識 (左は Android デバイス、右は Windows 10 デスクトップ)

Cognitive Services API のサブスクライブ

Cognitive Services を利用するアプリをビルドするには、目的のサービスをサブスクライブしなければなりません。現時点で、マイクロソフトはサブスクリプション ページ (bit.ly/2b2rKDO、英語) でアクティブにできる無料試用版を提供していますが、現在のプランは今後変更される可能性があります。このサブスクリプション ページで、Microsoft アカウントを登録すると、[Request new trials] が表示されます。 ここに一覧される利用可能なサービスから、Face API と Emotion API の無料プレビューを選択します。この時点のサブスクリプション ページには、アクティブなサービスのリストが表示され、そこに Face API と Emotion API のサブスクリプションもあります。図 2 は、サブスクリプションに応じた例を示しています。アクティブなサービスごとに 2 つのシークレット キーがあります。API を呼び出すのに必要なのは 1 つです。この時点では、キーを非表示のままにしておきます。このキーは Xamarin.Forms アプリを作成するときに表示します。

Face API と Emotion API のサブスクリプションのアクティブ化
図 2 Face API と Emotion API のサブスクリプションのアクティブ化

大まかに言えば、Cognitive Services は RESTful API を提供します。つまり、REST をサポートするプラットフォームや言語であれば、HTTP 要求を使ってこのサービスと対話できます。たとえば、以下の 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-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 の値です。一般的には、RESTful サービスにHTTP 要求を送信することと、JSON 応答を期待することが、すべての Cognitive Services に共通するアプローチです。ただし、マイクロソフトは C# で作業する .NET 開発者向けにクライアント ポータブル ライブラリも用意しています。このライブラリは、NuGet からダウンロード可能で、マネージ コードや完全なオブジェクト指向の方法でのサービスとの対話を簡単にします。これは Face API と Emotion API にも当てはまりますが、それについては後ほど説明します。REST のアプローチとクライアント ライブラリの両方に基づく例が示されている公式ドキュメンテーション (bit.ly/2b2KJrB、英語) も確認してください。これで両方のサービスに登録し、独自のキーを入手したので、今度は Xamarin.Forms と Microsoft Visual Studio 2015 を使用してクロスプラットフォーム アプリを作成します。

Xamarin.Forms アプリケーションの作成

ポータブル プロジェクト テンプレートまたは共有プロジェクト テンプレートのいずれかを選んで Xamarin.Forms を備えたクロスプラットフォーム アプリを作成します。今回は Cognitive Services API のクライアント ライブラリの利用方法を説明することが目的なので、サンプル アプリケーションはポータブル クラス ライブラリ (PCL) モデルを基盤にします。Visual Studio 2015 で、[ファイル]、[新しいプロジェクト] の順に選択します。Xamarin (xamarin.com/download、英語) から最新の更新プログラムを既にインストールしている場合は、[新しいプロジェクト] ダイアログの [Visual C#]、[Cross-Platform] ノード下に、[Blank Xaml App (Xamarin.Forms Portable)] いう新しいプロジェクト テンプレートが表示されます。これは空白 XAML ページを提供する興味深いテンプレートです。そのため、手動でページを作成する必要はありません。図 4 に、この新しいテンプレートを示します。

ソリューション 名を「FaceEmotionRecognition」にして、[OK] をクリックします。ソリューションの生成中に、ユニバーサル Windows プラットフォーム (UWP) プロジェクトの最小ターゲット バージョンを指定するよう求められます。どの選択肢を選んでも問題ありませんが、できるだけ高いバージョンをターゲットにすることをお勧めします。

新しい Xamarin.Forms アプリケーションの作成
図 4 新しい Xamarin.Forms アプリケーションの作成

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 用のクライアント ライブラリをインストールします。Face API と同様、PCL プロジェクトにのみインストールできます。
  • Xam.Plugin.Connectivity: Xamarin.Forms 用の Connectivity プラグインを含みます。ソリューション内のすべてのプロジェクトにインストールしなければなりません。
  • Xam.Plugin.Media: Xamarin.Forms 用の Media プラグインを含みます。Connectivity API と同様、ソリューション内のすべてのプロジェクトにインストールしなければなりません。

必要な NuGet パッケージをインストールしたら、コードを記述する前に必ずソリューションをビルドして、すべての参照を最新状態に更新します。

UI の設計

サンプル アプリケーションの UI は、1 ページで構成します。簡潔にするため、自動生成される 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 ディレクティブを追加します。

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();
  // 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");
}

前半で入手した独自のシークレット キーに置き換えてください。Face API と Emotion API のシークレット キーは、Microsoft Cognitive Services ポータル (bit.ly/2b2rKDO、英語) のサブスクリプション ページにあります (図 2 参照)。

写真の撮影と読み込み

Xamarin.Forms では、カメラとファイル システムの両方にアクセスするために、プラットフォーム別に固有のコードを記述することになります。比較的シンプルなアプローチは、数行のコードと Xamarin.Forms の Media プラグインを使用する方法です。このプラグインは、PCL プロジェクトで、写真やビデオをディスクから取得したりカメラで撮影できるようにします。このプラグインは CrossMedia クラスを公開しています。このクラスは以下のメンバーを公開します。

  • Current: CrossMedia クラスのシングルトン インスタンスを返します。
  • IsPickPhotoSupported  と  IsPickVideoSupported: 現在のデバイスがディスクの写真とビデオの選択をサポートしている場合に true を返すブール型のプロパティです。
  • PickPhotoAsync と PickVideoAsync: ローカルの写真またはビデオを選択するためにプラットフォーム固有の UI を呼び出して、MediaFile 型のオブジェクトを返すメソッドです。
  • IsCameraAvailable: デバイスが組み込みカメラを備えている場合に true を返すブール型のプロパティです。
  • IsTakePhotoSupported  と  IsTakeVideoSupported: 現在のデバイスがカメラの写真とビデオの選択をサポートしている場合に true を返すブール型のプロパティです。
  • TakePhotoAsync と TakeVideoAsync: 組み込みカメラを起動して写真またはビデオを撮影し、MediaFile 型のオブジェクトを返すメソッドです。

アプリのマニフェストでカメラにアクセスするための適切なアクセス許可を設定します。たとえば、UWP プロジェクトでは Web カメラとピクチャ ライブラリの両方のアクセス許可が必要です。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) は、Media­File 型のオブジェクトを返します。Media­File は、Plugin.Media 名前空間で定義されるクラスで、選択されたファイルを表します。このクラスの GetStream メソッドを呼び出して、FromStream メソッドから Image コントロールのソースとして使用できるストリームを返す必要があります。カメラによる写真撮影も非常に簡単です (図 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 プロパティを true に設定します。ほんの少しの作業と数行のコードを使用すれば、サポート対象のすべてのプラットフォームの重要な機能をアプリで簡単に利用できます。

感情の検出と顔認識の実装

ここからは、いよいよ顔と感情の認識を実装します。本コラムは初心者向けなので、ここではシンプルさを重視します。写真に 1 つだけ写っている顔の検出の実装方法を示し、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();

このメソッドは写真を選択または撮影すると生成される MediaFile を受け取ります。EmotionServiceClient クラスのインスタンスから RecognizeAsync メソッドを呼び出すだけで、写真の顔に浮かぶ感情を簡単に検出できます。このメソッドは、引数としてストリームまたは URL のいずれかを受け取ることができます。今回は、MediaFile オブジェクトからストリームを取得します。RecognizeAsync は、Emotion オブジェクトの配列を返します。配列内の各 Emotion には、写真内の 1 つの顔から検出された感情が格納されます。コードでは、選択した写真には顔が 1 つしか写っていないことを前提に、配列の先頭項目を取得します。Emotion 型は Scores というプロパティを公開します。このプロパティは、8 種類の感情の名前とその正確度のリストを含みます。具体的には、IEnumerable<string, float> を取得します。プロパティの ToRankedList メソッドを呼び出すと、検出済みの感情のリストを並べ替えて取得できます。API では、1 つの感情を正確に検出することはできません。代わりに、可能性のある多くの感情を検出します。返される中で最も高い値の感情が、おそらく顔に浮かぶ実際の感情に近い感情です。しかし、他の値もチェックする価値はあります。  このリストの最高の値は、実際の感情に最も近いもので、おそらく顔に浮かぶ実際の感情を表します。理解を深めるために、デバッガー データ ヒントを使って取得した感情の順位付けしたリストを考えます。このリストは、図 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 です。これはリスト内で最も高い値で、おそらく実際の感情を最も表していると考えられます。次に、顔の特徴を検出します。FaceServiceClient クラスは、非常に強力な DetectAsync メソッドを公開します。このメソッドは、性別、年齢、笑顔といった顔の特徴を取得できるだけでなく、人を認識し、顔を囲む四角形 (写真で顔を検出した領域) や、写真上での鼻、口、耳、目の位置などの情報をアプリが特定できるようにする、27 か所の顔の手掛かりも返します。DetectAsync のシグネチャは次のとおりです。

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

最も基本的な呼び出しでの DetectAsync は、写真を指すストリームまたは URL を要求し、顔を囲む四角形を返します。オプション パラメーターの returnFaceId と returnFaceLandmarks はそれぞれ人を見極め、顔の大きな手掛かりを返します。Face API では、人のグループを作成して各個人に ID を割り当てることができるため、認識を簡単に行えるようになります。また、顔にある手掛かりは顔の特徴を見極めるのに役立ち、Face オブジェクトの FaceLandmarks プロパティで使用できます。ID も顔にある手掛かりも本コラムの範囲外ですが、bit.ly/2adPvoP (英語) と bit.ly/2ai9WjV (英語) では、それぞれのトピックについて詳細を確認できます。同様に、顔にある大きな手掛かりの使い方についても説明しませんが、これは Face オブジェクトの 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 は、それぞれが写真に写った顔を表す Face オブジェクトの配列を返します。コードでは、配列の先頭の項目 (1 つの顔を表す) を取り出し、その顔の特徴を取得します。最後の行では、C# 6 から導入された Null 条件演算子 (?) を使用しています。この演算子により。配列の先頭要素が Null の場合は NullReferenceException をスローする代わりに Null を返しています。この演算子の詳細については、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;

この時点で以下を考慮します。

  • 感情のリストの最高値は、Scores.ToRankedList の呼び出し結果に対して FirstOrDefault を呼び出すことで取得します。Scores.ToRankedList メソッドは、IEnumerable<string, float> を返します。
  • FirstOrDefault から返される値は KeyValuePair<string, float> 型のオブジェクトで、string 型の Key は、UI に表示される判読可能なテキストとして感情の名前を格納します。
  • Glasses は、検出された顔が眼鏡をかけているかどうかと、眼鏡の種類を示す列挙値です。コードをシンプルにするために 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 クラスのインスタンスをデータ ソースとして受け取り、データ バインドされた子コントロールが関連情報を自動的に表示します。MVVM などのパターンを使用すれば、結果をビューモデル クラスでラップすることになります。多くの作業を終えた後は、いよいよアプリケーションのテストです。

アプリケーションのテスト

任意のプラットフォームを選択して F5 キーを押します。マイクロソフトのエミュレーターを使用する場合、そのエミュレーター ツールを利用して、写真撮影時に実際の Web カメラを選択したり、SD カードのシミュレーションを行ってファイルをアップロードすることができます。図 1 は、Android デバイスと、Windows 10 のデスクトップ モードでの筆者の写真の検出結果を示しています。

Face API と Emotion API はすばらしい仕事を果たし、推定であったとしても現実に非常に近い値を返します。FaceEmotionDetection クラスには、Smile、Beard、Moustache など、double 型のプロパティがあります。これらのプロパティが返す数値は、実際のアプリのエンド ユーザーには意味がない場合もあります。そのため、この数値を判読可能な文字列に変換する場合は、値コンバーターと IValueConverter インターフェイス (bit.ly/2bZn01J) の実装を考えます。

ネットワーク接続チェックの実装

インターネット上のリソースにアクセスする必要のあるアプリを正しく設計するには、必ず最初に接続の可用性をチェックしなければなりません。Xamarin.Forms では、カメラやファイル システムにアクセスする場合と同様、接続の可用性チェックにはプラットフォーム固有のコードが必要です。さいわい、PCL プロジェクトからこのチェックを直接実行するための共通方法を提供する Connectivity プラグインがあります。このプラグインは、CrossConnectivity というクラスと、そのクラスのシングルトン インスタンスを表す Current プロパティを提供します。このクラスは、接続を利用できる場合は単純に true を返す、IsConnected というブール型のプロパティを公開します。サンプル アプリケーションでネットワークの可用性をチェックするには、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 は 2008 年から Microsoft MVP の一員です。彼は年間 MVP を 5 度受賞し、Visual Studio による .NET 開発に関する、書籍、電子ブック、説明ビデオ、記事を手がけてきました。彼は、Brain-Sys (brain-sys.it、英語) でソリューション デベロッパー エキスパートとして活躍し、.NET 開発、トレーニング、コンサルティングに特化しています。Twitter は、@progalex (英語) からフォローできます。

この記事のレビューに協力してくれた技術スタッフの James McCaffrey と James Montemagno に心より感謝いたします。