Xamarin で 人工知能パーツ Microsoft Cognitive Services を使った表情分析アプリを作ろう! (Emotion API × Xamarin.Forms 編)

  • 6
    Like
  • 0
    Comment

Microsoft Cognitive Services を使った表情分析 Web アプリ ( Xamarin.Forms 版)

Microsoft Cognitive Services とは?

Microsoft Cognitive Services とはテキストや画像解析などの、自前で実装すると相当大変な機能を API を叩くだけで利用できるサービスです。
https://azure.microsoft.com/ja-jp/services/cognitive-services/

今回は、 その中の Emotion API を利用していきます。

Emotion API とは?

画像を送り込むだけで、顔を分析し、数値化した Json データを取得することが出来るサービスです。

Xamarin とは?

C# を使い、 iOS, Android, Windows, macOS のアプリを作ることが出来る開発ツールです。
Xamarin には、Xamarin.Tradional(または Xamarin ネイティブ), Xamarin.Forms の2種類の開発手法があります。今回は、 Xamarin.Forms の方を使っていきます。

詳しくは、日本Microsoft テクニカルエバンジェリスト 千代田まどか 氏の資料をご覧になってください。

Xamarin Overview

今回作るもの

今回は、Xamarin.Forms を使い、スマホから写真を撮影し、顔の表示位置と表情分析スコアを表示するスマホ用アプリを作成していきます。
Xamarin.Forms のプロジェクトテンプレートをベースに作成していきます。

完成図

Android の方は、Google Play からダウンロードしてみてください。

Xamarin.Forms & Cognitive Services の利用に必要な環境セットアップ

Visual Studio

Xamarin の開発を行うためには、 Visual Studio 2017(or Visual Studio for Mac) が必要です。
また、 Visual Studio 2017 のセットアップ時に .net によるモバイル開発 を選択している必要があります。

インストール手順は Xamarin やりたい人向け Visual Studio 2017 インストール手引書 - Xamarin 日本語情報 を参考にしてください。

Microsoft Account, Microsoft Azure Account

Microsoft Cognitive Services を利用するために必要です。

詳しくは、日本マイクロソフト テクニカルエバンジェリスト 大森彩子 氏の Qiita をご覧ください

また、 Emotion API の keyをリンク先に書いてある通りに取得してください!

Xamarin.Forms による実装

Visual Studio 2017 による Xamarin.Forms のプロジェクトの作成(Windows)

Visual Studio のテンプレートから Xamarin.Forms のプロジェクトを作成
※ Visual Studio を起動し、上部のメニューバーから ファイル>新規作成>プロジェクト を選択します。

テンプレートから Visual C#>Cross-Platform>Cross Platform App (Xamarin) を選択し、任意のプロジェクト名を入力します。ここでは「XamEmotion」としました。

作成する Xamarin.Forms プロジェクトのタイプを選択

空のアプリ、Xamarin.Forms、ポータブルクラスライブラリ (PCL) を選択して OK をクリックします。

作成する UWP のバージョンはそのままでOKボタンをクリックしてください

Visual Studio for Mac による Xamarin.Forms プロジェクトの作成(macOS)

Visual Studio for Mac のテンプレートから Xamarin.Forms のプロジェクトを作成
※ Visual Studio for Mac を起動し、メニューバーから ファイル>新しいソリューション を選択します。

Multiplatform>アプリ>Blank Forms App を選択して 次へ をクリックします。

スクリーンショット 2017-09-13 11.48.10.png

任意のアプリ名を入力して(ここでは「XamEmotion」としました。) 共有コード>ポータブルクラスライブラリの使用 を選択して 次へ をクリックします。

スクリーンショット 2017-09-13 11.50.48.png

そのまま 作成 をクリックします。

スクリーンショット 2017-09-13 11.51.37.png

Xamarin.Forms のプロジェクトが作成されました

スクリーンショット 2017-09-13 12.01.42.png

※ Visual Studio for Mac では UWP プロジェクトは作成されません。

Nuget Package

Xamarin の開発では、通常の .NET 開発と同じようにパッケージ管理システムに NuGet を利用します。

Visual Studio 2017 の場合はソリューションを右クリックして ソリューションのNuGetパッケージの管理 から NuGet パッケージマネージャーを起動してソリューション全体に対して一括で NuGet パッケージを追加/削除ができます。

Visual Studio for Mac の場合は、各プロジェクトを右クリックして 追加>NuGetパッケージの追加 で NuGet パッケージマネージャーを起動します。

スクリーンショット 2017-09-13 12.03.18.png

スクリーンショット 2017-09-13 12.04.46.png

Microsoft.BCL.Build パッケージのインストール

Emotion API のクライアントライブラリの依存関係に存在する Microsoft.BCL.Build はこの後の Emotion API 用クライアントライブラリのインストールで同時にインストールされるのですが、Xamarin.Forms のプロジェクトではそれより高いバージョンが必要なため、先に個別でインストールしておく必要があります。
※ 執筆時は 1.0.21

ソリューションのパッケージの管理ウィンドウの参照タブで「bcl.build」と検索して Microsoft.Bcl.Build をインストールします。

Visual Studio for Mac では、Microsoft BCL Build Components と表示されています。ID が Microsoft.Bcl.Build となっていることを確認してください。

スクリーンショット 2017-09-13 12.12.57.png

Emotion API 用クライアントライブラリのインストール

ソリューションのパッケージの管理ウィンドウの参照タブで「Emotion」や「Microsoft.ProjectOxford.Emotion」と検索して Microsoft.ProjectOxford.Emotion をインストールします。

スクリーンショット 2017-09-13 12.17.52.png

PCL Storage のインストール

PCL Storage は Xamarin.Forms でファイルの読み書きをする処理を共通コードで簡潔に記述するため行うために使用するライブラリです。
(Xamarin.Forms の PCL プロジェクトの名前空間に System.IO.File がないため、その代わりにとなるもの)
PCL Storage は、 Xamarin Plugin と呼ばれる公式ライブラリのうちの1つで、これらの Plugin ライブラリを使用すれば、簡単にカメラとか位置情報とかを使うことが出来ます。

ソリューションのパッケージの管理ウィンドウの参照タブで「PCLStorage」と検索してインストールします。

スクリーンショット 2017-09-13 12.19.21.png

Media Plugin for Xamarin and Windows をインストール

カメラの使用やギャラリーから画像をピックアップするために使います。

ソリューションのパッケージの管理ウィンドウの参照タブで「Xam.Plugin.Media」と検索してインストールします。

スクリーンショット 2017-09-13 12.20.57.png

Media Plugin for Xamarin and Windows をインストールすると、Readme.txt が表示されます。iOS/Android/UWP でそれぞれ必要な処理が書いてありますので、実行します。

Android

プロジェクトのターゲットフレームワークを API 25(Android 7.1)にします。Android プロジェクトを右クリックして、プロジェクトのプロパティ>アプリケーション>ターゲットフレームワーク が 7.1 以上になっていることを確認します。

Surface_2017-09-13 15_00_19.png

この時に Visual Studio 2017 の場合は Androidマニフェスト タブを開き、パッケージ名、アイコン、バージョン番号、バージョン名を入力しておきます。

image.png

Visual Studio for Mac の場合は、Android プロジェクトを右クリックして オプション を選択し、ダイアログの ビルド>Androidアプリケーション>対象のAndroidバージョン が API 25 になっていることを確認します。

スクリーンショット 2017-09-13 15.05.38.png

次に Android プロジェクトの MainActivity.cs を開き OnCreate メソッドの下に次のオーバーライドメソッドを追加します。

MainActivity.cs
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
    PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

次に Properties フォルダの AndroidManifest.xml をダブルクリックで開きます。

image.png

スクリーンショット 2017-09-13 15.07.57.png

Visual Studio for Mac の場合は、開いた後で画面下の ソース(見づらいですがw)をクリックして XML を表示します。

スクリーンショット 2017-09-13 15.09.29.png

<application></application> タグの中に次を追加します。

Before
AndroidManifest.xml
<!-- 略 -->
<application android:label="XamEmotion.Android" android:icon="@drawable/icon"></application>
<!-- 略 -->
After
AndroidManifest.xml
<!-- 略 -->
<application android:label="XamEmotion.Android" android:icon="@drawable/icon">
  <provider android:name="android.support.v4.content.FileProvider"
            android:authorities="YOUR_APP_PACKAGE_NAME.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"></meta-data>
  </provider>
</application>
<!-- 略 -->

YOUR_APP_PACKAGE_NAME はアプリのパッケージ名で置き換えてください。

次に Resources フォルダ内に xml フォルダを作成し、その中に file_path.xml ファイルを作成します。

Visual Studio 2017 の場合はフォルダを右クリックして、追加>新しい項目 で表示されるダイアログで データ>XML ファイル を選択します。

image.png

Visual Studio for Mac の場合はフォルダを右クリックして 追加>新しいファイル で表示されるダイアログで XML>空のXMLファイル を選択します。

スクリーンショット 2017-09-13 15.27.22.png

作成された XML の中身を次で置き換えます。

file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
    <external-files-path name="my_movies" path="Movies" />
</paths>

iOS

プライバシーポリシーに対応

  1. Info.plist を開きます。

Visual Studio の場合は、右クリックメニューから ファイルを開くアプリケーションの選択 をクリックします。

image.png

XML (テキスト) エディタを選択し開きます。

image.png

Visual Studio for Mac の場合は Info.plist を右クリックして プログラムから開く>任意のテキストエディタ を選択します。

スクリーンショット 2017-09-13 15.31.53.png

  1. 内の要素の最後に以下の要素を入力します。
<key>NSPhotoLibraryUsageDescription</key>
  <string>This app accesses the photo library to analyze facial expressions.</string>
<key>NSCameraUsageDescription</key>
  <string>This app accesses the camera to analyze facial expressions.</string>

UWP

アプリマニフェスト>機能>Webカメラ にチェックを入れ、UWP アプリから Web カメラを使うと宣言します。(ローカルでデバッグ実行する分には問題がないはずです)

image.png

コーディング

ロジックの作成

カメラを撮影するクラスの作成

プロジェクトを右クリックして、 新しいクラスを作成します。

PhotoClient という名前のクラスを作成

Visual Studio for Mac では、プロジェクトを右クリックして、追加>新しいファイル からダイアログで General>空のクラス を選択し、名前を「PhotoClient」にして 新規 ボタンをクリックします。

スクリーンショット 2017-09-13 13.08.31.png

Before

PhotoClient.cs
// 略

class PhotoClient
{
}

After

PhotoClient.cs
using System;
using System.Threading.Tasks;
using Plugin.Media;
using Plugin.Media.Abstractions;

// 略

public static class PhotoClient
{
    public static async Task<string> TakePhotoAsync()
    {
        // カメラを初期化
        await CrossMedia.Current.Initialize();

        // カメラを使えるかどうか判定
        if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
        {
            throw new NotSupportedException("You should Set up camera");
        }

        // 撮影し、保存したファイルを取得
        var photo = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions());

        // 保存したファイルのパスを取得
        return photo.Path;
    }
}

カメラロール/ギャラリーから画像を選択するクラスを作成

上と同じように GalleryClient という名前でクラスを新規作成してください。

Before:GalleryClient.cs

// 略
class GalleryClient
{
}

After:GalleryClient.cs

using Plugin.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 略

public static class GalleryClient
{
    public static async Task<string> PickPhotoAsync()
    {
<<<<<<< HEAD
        public static async Task<string> PickPhotoA()
        {
            // galleryから写真を選択させる
            var photo = await CrossMedia.Current.PickPhotoAsync();
=======
        // galleryから写真を選択させる
        var photo = await CrossMedia.Current.PickPhotoAsync();
>>>>>>> EDIT_REQUEST

        // 保存したファイルのパスを取得
        return photo.Path;
    }
}

Emotion API を叩くクラスを作成

上と同じように EmotionApiClient という名前で、新規クラスを作成してください。

Before

EmotionApiClient.cs
// 略

class EmotionApiClient
{
}

After

EmotionApiClient.cs
using System.Threading.Tasks;
using Microsoft.ProjectOxford.Common.Contract;
using Microsoft.ProjectOxford.Emotion;
using PCLStorage;

// 略

public static class EmotionApiClient
{
    // ここにEmotion APIのsubscribeキーを入力してください
    private static readonly string SubscribeKey = "YOUR_Subscribe_KEY";

    public static async Task<EmotionScores> AnalyzeAsync(string photoURL)
    {
        var client = new EmotionServiceClient(SubscribeKey);
        var file = await FileSystem.Current.GetFileFromPathAsync(photoURL);
        var imageStream = await file.OpenAsync(FileAccess.Read);
        var result = await client.RecognizeAsync(imageStream);
        return result[0].Scores;
    }
}

View の作成

最後に作成したロジックを使用する View を作成します。

※ Visual Studio for Mac の場合は、<プロジェクト名>Page.xaml となり、XamEmotion で作成した場合は XamEmotionPage.xaml ですので、ご注意ください。
スクリーンショット 2017-09-13 12.24.55.png

before

MainPage.xaml
<Label Text="Welcome to Xamarin Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />

After

MainPage.xaml
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Image x:Name="ImagePreview" Grid.Row="0" />
    <Button Grid.Row="1"
            Clicked="Button_OnClicked"
            Text="解析" />
    <StackLayout Grid.Row="2" HorizontalOptions="Center">
        <Label x:Name="Anger">怒り</Label>
        <Label x:Name="Contempt">軽蔑</Label>
        <Label x:Name="Disgust">むかつき</Label>
        <Label x:Name="Fear">恐れ</Label>
        <Label x:Name="Happiness">喜び</Label>
        <Label x:Name="Neutral">無表情</Label>
        <Label x:Name="Sadness">悲しみ</Label>
        <Label x:Name="Surprise">驚き</Label>
    </StackLayout>
</Grid>

コードビハインドも修正します。

Before

MainPage.xaml.cs
// 略

public MainPage()
{
    InitializeComponent();
}

After

MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.ProjectOxford.Common.Contract;
using Xamarin.Forms;

// 略

public MainPage()
{
    InitializeComponent();
}

private async void Button_OnClicked(object sender, EventArgs e)
{
    var photoUrl = "";

    var imageChoiceResult = await DisplayAlert("画像の元を選択してください", "", "カメラ", "ギャラリー");

    try
    {
        if (imageChoiceResult)
        {
            // 写真を撮影し、保存したURLを取得
            photoUrl = await PhotoClient.TakePhotoAsync();
        }
        else
        {
            // ギャラリーから選んだ写真のURLを取得
            photoUrl = await GalleryClient.PickPhotoAsync();

        }
    }
    catch (Exception exception)
    {
        await DisplayAlert("Error", exception.Message, "OK");
    }

    // 画像を表示
    ImagePreview.Source = photoUrl;

    EmotionScores emotionResult;
    try
    {
        // Cognitive Services - Emotion API を叩く
        emotionResult = await EmotionApiClient.AnalyzeAsync(photoUrl);
    }
    catch (Exception exception)
    {
        await DisplayAlert("Error", exception.Message, "OK");
        return;
    }

    // 表示するためにビューにセットしていく
    Anger.Text = $"怒り: {(emotionResult.Anger * 100).ToString("0.000")}%";
    Contempt.Text = $"軽蔑: {(emotionResult.Contempt * 100).ToString("0.000")}%";
    Disgust.Text = $"むかつき: {(emotionResult.Disgust * 100).ToString("0.000")}%";
    Fear.Text = $"恐れ: {(emotionResult.Fear * 100).ToString("0.000")}%";
    Happiness.Text = $"喜び: {(emotionResult.Happiness * 100).ToString("0.000")}%";
    Neutral.Text = $"無表情: {(emotionResult.Neutral * 100).ToString("0.000")}%";
    Sadness.Text = $"悲しみ: {(emotionResult.Sadness * 100).ToString("0.000")}%";
    Surprise.Text = $"驚き: {(emotionResult.Surprise * 100).ToString("0.000")}%";
}

アプリケーションの動作確認

F5 または デバック>デバックの開始 をクリックして、プロジェクトのビルドおよび起動を行います。

Windows 環境で開発をしている人は UWP 版を実行してください。環境のせいで動かない。といったことが少なくなります。

無事に完成させることは出来ましたか?

ソース公開

今回作成したものの完成品を Github にて公開しています。もしうごかなければ、参考にしてみてください!

Github

追加課題

  • 複数人の表情を見れるようにしよう
  • MVVM の形に書きなおしてみよう
  • 撮影・選択した画像の顔の部分に枠を描画してみよう
  • Xamarin.Forms から Xamarin.Tradional へ書き直してみよう