Xamarin 日本語情報

Xamarin(ザマリン) の代理店だったエクセルソフト田淵のブログです。主に Xamarin に関するエントリーをアップしていきます。(なるべく正しい有益な情報を掲載していきたいと考えていますが、このブログのエントリーは所属組織の公式見解ではありませんのでご注意ください)

Xamarin で Amazon Rekognition を使うには

こんにちは。エクセルソフトの田淵です。

本エントリーは Xamarin Advent Calendar 2018 - Qiita の 2日目のエントリーです。明日は @gnk263 さんです。よろしくお願いいたします。

さて、先日「初心者向けXamarinハンズオン! #5」で最新の Face API を使って画像の人の幸福度を測定するところまでをやりました。(実は大本の Xamarin Dev Days は 2016 年に開催したイベントなのですが、ちゃんとプロジェクトやドキュメントが定期的にメンテされています。凄いですよね!)

jxug.connpass.com

ドキュメントは以下に公開しているので、興味のある方はぜひやってみてください。

github.com

で!アドベントカレンダーのネタとして Azure の Cognitive Service でもざっと触りなおそうかなーと思っていたのですが、@tonkun_no さんが「久しぶりに何か書きます(Xamarin + Cognitive Servicesかなぁ)」と仰っていたのでそこはお譲りし、違うやつ、AWS の顔認識の PaaS を触ってみることにしました。

完成するとこんな感じです。

f:id:ytabuchi:20181128173052p:plain:w300 f:id:ytabuchi:20181128173124p:plain:w300

Amazon Rekognition とは

ということで前段が長くなってしまいましたが、【Amazon Rekognition】人工知能・機械学習に基づく画像認識・画像解析サービス | AWS です。

Amazon Rekognition では、画像分析と動画分析をアプリケーションに簡単に追加できます。Rekognition API に画像または動画を与えるだけで、このサービスが対象物、人、テキスト、シーン、アクティビティ、それに不適切なコンテンツまで検出します。

とあるように、

  • 対象物体、シーン、アクティビティ検出(Computer Vision - 画像内のシーンおよびアクティビティ認識)
  • 顔認識(Face - 画像内の顔検出)
  • 顔分析(Face - 画像内の感情認識)
  • 動線の検出(Video Indexer - ビデオ内のオブジェクト、シーン、アクティビティの検出(かな?))
  • 安全でないコンテンツの検出(Content Moderator - 画像とビデオの性表現が露骨なコンテンツまたは不快感を与えるコンテンツのモデレート)
  • 有名人の認識(Computer Vision - 画像内の有名人とランドマークの認識)
  • 画像中のテキスト(Computer Vision - 画像内の光学式文字認識 (OCR)/手書き認識)

などが利用できます。(私は Microsoft Azure を良く使っているので、老婆心でカッコ内に Cognitive Services のディレクトリ | Microsoft Azure にある Azure Cognitive Services での同様のサービスを記載しましたw)

料金は こちら をご参照ください。

1年間の無料アカウントの方は「最初の 12 か月間は、1 か月あたり 5,000 枚の画像分析と、毎月最大 1,000 個の顔メタデータの保存が可能」で、一般アカウントでも、「1 か月あたり画像処理100 万枚までは処理画像 1,000 枚あたり 1.3 USD で顔メタデータのストレージは 1,000枚あたり 0.013 USD」です。

Xamarin からは .NET Standard 2.0 のライブラリで Rekognition にアクセスすれば良いでしょう。

早速やっていきます。

事前準備

いくつか必要なものがあります。

1)AWS アカウント

当然ですが、アカウントが必要です。新規に申し込むと1年間の無料利用権が付いていますので、今回のサンプルも無料で動かせます。まだアカウントを持っていない方は以下のドキュメントを参考にアカウントを作成してください。

AWS アカウント作成の流れ | AWS

2)IAM ユーザー

Root 権限を持つ上記のアカウントではなく、特定の権限のみをアタッチした IAM ユーザーでアクセスする必要があるようです。

ステップ 1: AWS アカウントを設定して IAM ユーザーを作成する - Amazon Rekognition

こちらに従って、任意の IAM ユーザーを作成してください。

私の場合は、コンソールにログインする Admin ユーザーと、今回使用する「プログラムによるアクセス」にだけチェックを付けた制限ユーザーを作成しました。

f:id:ytabuchi:20181128132421p:plain:w600

ユーザーを作成する時に一緒にグループも作成でき、グループに対してポリシーをアタッチできます。

f:id:ytabuchi:20181128133416p:plain:w600

「AmazonRekognitionFullAccess」をアタッチしたユーザーは以下のようになるはずです。

f:id:ytabuchi:20181128133451p:plain:w600

適切にユーザーを作成したら、「認証情報」タブで「アクセスキーの生成」をクリックします。

f:id:ytabuchi:20181128133532p:plain:w600

「アクセスキー ID」と「シークレットアクセスキー」を控えておきます。csv をダウンロードしても良いでしょう。

f:id:ytabuchi:20181128133728p:plain:w450

コンソールにログインできるようになれば、Rekognition Console からデモを試すことができますので、やってみてください。

3)AWSSDK for .NET のインストール

SDK があるようなので、インストールします。(必須ではありませんが、VS 拡張があるので入れておいた方が便利っぽいです。)

aws.amazon.com

.NET の「インストール」で最新の AWSSDK をダウンロードできます。

SDK には AWS Toolkit for Visual Studio が含まれているため、インストールすると、以下のように Visual Studio 2017 でプロジェクトテンプレートが使えたり、

f:id:ytabuchi:20181128141120p:plain:w450

アクセスキー ID とシークレットキーをプロファイル毎に登録できたりします。

f:id:ytabuchi:20181128141459p:plain:w600

準備は以上です。

(ユーザーの API Key と Secret Key だけあれば Azure みたいにインスタンスを作らなくても良い。というのは新鮮でした。)

NET Standard ライブラリ作成

準備ができたので .NET Standard のライブラリを作成します。私の場合は、「RekognitionSample.Core」という名前でプロジェクトを作成しました。

WindowsVisual Studio 2017 では「Visual C#>.NET Standard>クラスライブラリ」

f:id:ytabuchi:20181128134029p:plain:w450

macOSVisual Studio for Mac では「マルチプラットフォーム>ライブラリ>.NET Standard ライブラリ」で作成できます。

f:id:ytabuchi:20181128134347p:plain:w450

空のソリューションを最初に作成して、プロジェクトを追加するのが良いかもしれません。

AWS SDK Rekognition の NuGet パッケージを追加

Windows の場合は、Core プロジェクトを右クリックして、「NuGet パッケージの管理」をクリックし、表示される NuGet パッケージマネージャーで「awssdk.rekognition」と検索して、パッケージをインストールします。

f:id:ytabuchi:20181129092220p:plain:w450

macOS の場合は Core のプロジェクトを右クリックして、「追加>NeGetパッケージの追加」を選択し、表示されるパッケージマネージャーで「awssdk.rekognition」と検索して、パッケージをインストールします。

f:id:ytabuchi:20181128212622p:plain:w450

モデル追加

まずは解析した結果を受け取る DetectedFaceDetail モデルを用意します。

public class DetectedFaceDetail
{
    public string Gender { get; set; }
    public float GenderConfidence { get; set; }
    public float HappinessConfidence { get; set; }
    public int AgeRangeHigh { get; set; }
    public int AgeRangeLow { get; set; }
}

サービスクラス作成

次に、ファイルパスを引数にもらい、Rekognition の結果を DetectedFaceDetailList として返す RekognitionService クラスを作成し、メソッドを二つ用意します。

Rekognition は画像を渡す際に S3Bucket のタグから、または Base64エンコードした文字列を使用できます。SDK を利用すると、Base64エンコードする必要はなく、Amazon.Rekognition.Model.Image を渡すことができます。何と便利な!(ImageBytes プロパティは MemoryStream 型なので、それを受けて、内部で Base64 エンコードしてくれているのでしょう。)

public async Task<List<DetectedFaceDetail>> GetFaceDetailsFromLocalFileAsync(string filePath)
{
    //画像のMemoryStreamを作成
    var imageStream = await GenerateImageStreamFromLocalFileAsync(filePath);
    if (imageStream == null)
        return null;

    try
    {
        //AWS Rekognition Client を作成
        using (var rekognitionClient = new AmazonRekognitionClient(Secrets.AccessKey, 
                                                                    Secrets.SecretKey, 
                                                                    RegionEndpoint.APNortheast1))
        {
            var request = new DetectFacesRequest
            {
                Image = new Image
                {
                    Bytes = imageStream
                },
                Attributes = new List<string> { "ALL" }
            };

            //responseを受け取り、必要な情報を抽出
            var response = await rekognitionClient.DetectFacesAsync(request);

            var faceList = new List<DetectedFaceDetail>();
            foreach (var face in response.FaceDetails)
            {
                faceList.Add(new DetectedFaceDetail
                {
                    Gender = face.Gender.Value,
                    GenderConfidence = face.Gender.Confidence,
                    HappinessConfidence = face.Emotions.Find(x => x.Type.Value == EmotionName.HAPPY).Confidence,
                    AgeRangeHigh = face.AgeRange.High,
                    AgeRangeLow = face.AgeRange.Low
                });
            }

            return faceList;
        }
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.Message);
    }

    return null;
}

private async Task<MemoryStream> GenerateImageStreamFromLocalFileAsync(string filePath)
{
    try
    {
        using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            using (var memoryStream = new MemoryStream())
            {
                await fileStream.CopyToAsync(memoryStream);

                return memoryStream;
            }
        }
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.Message);
    }

    return null;
}

少し長いですが、

  • AmazonRekognitionClient(string, string, RegionEndpoint) を最初に控えておいた「アクセスキーID」「シークレットキー」とエンドポイントを指定してインスタンス化(エンドポイントは RegionEndpoint Class | AWS SDK for .NET V3 を参照してください。APNortheast1 が東京リージョンです。)
  • DetectFacesRequest クラスのインスタンスAmazon.Rekognition.Model.Image として画像の MemoryStream と取得する属性を指定
  • DetectFacesAsync メソッドでレスポンスを受け取り

をやっているだけです。

DetectFacesResponse オブジェクトが戻ってきますので、FaceDetails プロパティ内の List<Amazon.Rekognition.Model.FaceDetail> から必要な情報を取り出しています。

Amazon.Rekognition.Model.FaceDetail クラスの詳細は こちら をご覧ください。

FaceDetail に含まれる Emotions プロパティの型は List<Amazon.Rekognition.Model.Emotion> です。Emotion クラスには ConfidenceType があり、Type の型は EmotionName クラスField で定義されています。

各クラス、メソッドの詳細は API ドキュメントを参照してください。

AWS SDK for .NET V3 API Documentation

余談ですが、AWS SDK for .NET によるプログラミング - AWS SDK for .NET などのドキュメントは基本 .NET Framework クライアントアプリや ASP.NET MVC など向けに書かれていて、App.configWeb.config に設定を書き込み、コードから読み込ませる方法が紹介されています。

.NET Core を使用した AWS SDK for .NET の設定 - AWS SDK for .NET

上記のように .NET Core を使ったサンプルも紹介されているのですが、config ファイルの代わりに設定ファイルを読み込もう。という感じでした。

API ドキュメントに以下のような AmazonRekognitionClient() のコンストラクタで直接アクセスキー、シークレットキーを指定するやり方があったのでそれを使用していますが、ちゃんとやる場合は設定ファイルで指定するのが良いのかもしれません。

名前 説明
AmazonRekognitionClient(string, string, RegionEndpoint) Constructs AmazonRekognitionClient with AWS Access Key ID and AWS Secret Key

とりあえず、これで動くようになっているはずです。

Console アプリから確認

var rekognition = new RekognitionService();
var task = rekognition.GetFaceDetailsFromLocalFileAsync(@"D:\family.jpg");
var res = task.Result;

こんな感じで List<DetectedFaceDetail> が取得できるのがわかるかと思います。

Xamarin.Forms からの呼び出し

ここまでできれば、後は Xamarin.Forms から呼び出すだけです。

カメラで撮った写真を RekognitionService に渡してあげましょう。

カメラの写真の取得は、James Montemagno さんが作成した Xam.Plugin.Media を使います。

Xamarin.Forms のプロジェクト全部にプラグインをインストールします。

f:id:ytabuchi:20181128170324p:plain:w450

特に Android では Permission 関連でやることが多いので、ドキュメント をしっかり読んで対応しましょう。

Android でやらないといけない処理は

  • OnRequestPermissionsResultMainActivity に追加
  • CrossCurrentActivity のコードを MainActivityOnCreate 内に追加
  • AndroidManifest.xml<application></application> 内に provider の記述を追加
  • Resources フォルダ内に file_paths.xml ファイルを作成し、XML コードを追加

です。

Android で初回ビルド後の実行時に Java.Lang.Exception とかが出る場合は、Android プロジェクトの bin, obj フォルダを一度削除してリビルドするとうまくいくケースが多いです。

iOS でやらないといけない処理は

  • info.plist に Permission の処理を追加

です。

後は、MainPage.xaml を次のように書き換えて、

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"
             xmlns:local="clr-namespace:RekognitionSample"
             x:Class="RekognitionSample.MainPage">
    <ScrollView>
        <StackLayout>
            <Button Text="Take a photo" 
                Clicked="Button_Clicked"/>
            <Image x:Name="image" Aspect="AspectFit"/>
        </StackLayout>
    </ScrollView>
</ContentPage>

小ネタとして iPhone X 対応の ios:Page.UseSafeArea="true" が入っています。

コードビハインドの Button_Clickedイベントハンドラに以下のコードを追加します。このコードは Xam.Plugin.Media のページの Permission Recommendations のコードをそのまま拝借したものです。

private async void Button_Clicked(object sender, EventArgs e)
{
    var cameraStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
    var storageStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Storage);

    if (cameraStatus != PermissionStatus.Granted || storageStatus != PermissionStatus.Granted)
    {
        var results = await CrossPermissions.Current.RequestPermissionsAsync(new[] { Permission.Camera, Permission.Storage });
        cameraStatus = results[Permission.Camera];
        storageStatus = results[Permission.Storage];
    }

    if (cameraStatus == PermissionStatus.Granted && storageStatus == PermissionStatus.Granted)
    {
        var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
        {
            Directory = "Sample",
            Name = "test.jpg"
        });

        if (file == null)
            return;

        image.Source = ImageSource.FromFile(file.Path);

        var rekognition = new RekognitionService();
        var faceList = await rekognition.GetFaceDetailsFromLocalFileAsync(file.Path);

        var faceCount = faceList.Count();
        var face = faceList.FirstOrDefault();

        await DisplayAlert("Rekognition", $"Detected: {faceCount} Person\n\nAgeRange: {face.AgeRangeLow} - {face.AgeRangeLow}\nGender: {face.Gender}\nGender Confidence: {face.GenderConfidence}%\nHappiness: {face.HappinessConfidence}%", "OK");
    }
    else
    {
        await DisplayAlert("Permissions Denied", "Unable to take photos.", "OK");
        //On iOS you may want to send your user to the settings screen.
        //CrossPermissions.Current.OpenAppSettings();
    }
}
  • Permission の許可が得られていれば CrossMedia.Current.TakePhotoAsync メソッドで写真を撮り、file に保存
  • MainPage の Image にファイルを表示する

後は Console アプリでもやったように、

  • RekognitionServiceインスタンス化して、GetFaceDetailsFromLocalFileAsync メソッドで faceList を取得
  • (今回はサンプルとして)取得された最初の情報を DisplayAlert で表示

です。

ビルドして動作させると以下のようになります。

f:id:ytabuchi:20181128173052p:plain:w300 f:id:ytabuchi:20181128173124p:plain:w300

iOS だと 81% の確立で女性という結果だったり、Android だと男性になったけど 57歳って…w という不思議な結果が出ていますが、会社の同僚にやってみたらそれっぽいのが出たので、現在の私の風貌が分かりづらいんだろうなw と思っておくことにします。

サンプルのコードを以下に置いておきます。ご興味があれば触ってみてください。

github.com

まとめ

Microsoft Azure と違い、自分のアカウントで Rekognition のリソースを作らなくて良いのは便利だなと思いました。

これについて id:beachside さんと話していたら、Azure の Face などのリソースもインスタンスを立てているわけではなく、Key を作っているだけとのこと。逆に AWS ではリソース(サービス)毎に Key やアクセス権を管理できない(=ユーザー毎の管理になるため)ので面倒だ。と。なるほど、アプローチが違うだけで、同じでしたw

SDK はやっぱり便利です。Rest API で使うこともできますが、認証やレスポンスの JSON の整形を気にすることなく、オブジェクトとしてデータを扱えます。.NET Standard に対応しているので、今回の Core プロジェクトは Xamarin ネイティブからも当然使えます。

AWS SDK for .NET ドキュメント の「開発者ガイド」は非常に少ない記事とサンプルしかないので基本的には API リファレンス を見ていくことになります。

以上です。

Xamarin 気になった方は

Visual Studio 2017 をインストールして触ってみてください。手順書は こちらのエントリー をご覧ください。 学習用リソースJXUG リンクページ に参考資料を纏めてますので併せてどうぞ。

Xamarin の導入支援サービスを始めました。ベースは基本的なアプリを一緒に作ることで Xamarin を使えるようになって頂く内容ですが、ご要望に応じて講習内容のカスタマイズも可能です。詳しくは田淵までお問い合わせください(^^)

Xamarin 有償トレーニング : XLsoft エクセルソフト

Xamarin の情報が欲しい方はこのブログも購読いただいたり、私のTwitterアカウントをフォローいただいたりすると嬉しいです。

私が所属している エクセルソフト の宣伝を少しさせてください。弊社は開発者向けの様々なソフトウェアを扱っています。Office/PDF ファイルを .NET/Java で操作するライブラリ Aspose(アスポーズ)Windows アプリ、Web ページ、iOS/Android アプリの UI テストができる TestComplete などお勧めです(^^) また、Visual Studio Professional/Enterprise with MSDN も販売してますし、日本で売っていない海外のソフトウェア、開発ツールなどを弊社経由で日本円で購入頂くことも可能です。ご興味あれば 弊社ページ を覗いてみてください。

以上です。

エクセルソフト | ダウンロード | 学習用リソース | JXUG リンクページ | ブログ購読