人工知能パーツ Microsoft Cognitive Services で食べ物画像判定 BOT を作ろう！ [Bot Framework (C#) 編]

Cognitive Services の Custom Vision Service は、オリジナルの画像判定エンジンを作成して API で推定値を取得できるサービスです。機械学習などで画像判定ロジックを構築しなくても、画像をアップロードしてタグ付けを行うことで、画像判定エンジンを構築できます。
今回は Custom Vision Service で作成した画像判定エンジンを利用して、画像を判定した結果によって異なる返答を返す Bot を作成する方法を紹介します。

ベースは Microsoft Bot Framework (V3.0 以降) テンプレートを利用し、Cognitive Services Custom Vision (Prediction) C# クラスライブラリーを用いて呼び出しを行います。

この BOT アプリは Bot Framework Channel Emulator を使ってローカル環境で稼働確認することが可能です。また、Web 公開 & Bot Framework に登録すると、埋め込み可能な Web Chat が利用できます。

>**人工知能パーツ Microsoft Cognitive Services で食べ物画像判定 BOT を作ろう！**
>(1) [Custom Vision 編](http://qiita.com/annie/items/293525901020685ad5f6)
>(2) Bot Framework 編 : C# 版 ※このページ / [Node.js 版](http://qiita.com/annie/items/41b60b90affe26925761)

Custom Vision 編、Bot Framework 編を通して作成できる食べ物画像判定 BOT↓
![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_10-e1498284424569.png)

#Bot Framework & Cognitive Services 利用に必要な環境、サブスクリプションの準備

##開発環境
###Visual Studio、Bot Framework Template & Bot Framework Channel Emulator
無償の Visual Studio 2017 Community or 2015 Community でOKなので、既存の環境がない場合は、ダウンロードしてインストールします。
[Visual Studio 2017 Community ダウンロードサイト](https://www.visualstudio.com/ja-jp/products/visual-studio-community-vs.aspx)
[Visual Studio 用の Bot Framework C# テンプレート](http://aka.ms/bf-bc-vstemplate)
[Bot Framework Channel Emulator (Windows版)](https://aka.ms/bf-bc-emulator) ※Mac/Linux は [Console版](https://aka.ms/bfemulator) をご利用ください
>Bot Framework 開発環境の作り方は、[Microsoft Bot Framework v3.0 からはじめる BOT 開発: Bot Framework を使うための開発環境](http://qiita.com/annie/items/edc26c0ee9603e84a2e4#bot-framework-%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83) をご覧ください。

#BOT アプリの実装
##Bot アプリケーションの作成
[Visual Studio テンプレートから Bot アプリケーションの作成](http://qiita.com/annie/items/edc26c0ee9603e84a2e4#visual-studio-%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%8B%E3%82%89-bot-%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E4%BD%9C%E6%88%90) と同じ手順で、新規 Bot アプリケーションを作成します。今回は FoodPairingBot という名称で作成しています。
![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_01.png)


##Cognitive Services Custom Vision Service の C# ライブラリーのインストール
ソリューションエクスプローラーでプロジェクト名 (ソリューションの下) を右クリックして、*NuGet パッケージの管理* を選択します。
![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_02.png)

*参照* をクリックし、*custom vision* と入力して検索します。*Microsoft.Cognitive.CustomVision.Prediction* を選択し、*インストール* をクリックしてインストールします。
![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_03.png)

Custom Vision (Prediction) のライブラリーと、依存関係のあるライブラリーが合わせて表示されますので、*OK* をクリックしてインストールします。
![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_04s.png)
![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_05s.png)


インストールが終了したら、NuGet のウインドウを閉じます。

##会話のハンドリングの記述 (基本)
*Dialogs* フォルダー をクリックして開きます。*RootDialog.cs* をクリックして表示し、こちらを編集していきます。RootDialog.cs にはメッセージを受信したときの動作を記述します。

![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_06.png)

冒頭に、先ほど追加した Microsoft.Cognitive.CustomVision を追加します。合わせて System.Net.Http も追加しておきます。

```RootDialog.cs
using Microsoft.Cognitive.CustomVision.Prediction;
using System.Net.Http;
```
###初期メッセージの追加
メッセージを受信したとき最初に *StartAsync* が呼びだされ、*StartAsync* から *MessageReceivedAsync* が呼び出されます。
*MessageReceivedAsync* 呼び出し前に初期メッセージを追加します。 

```RootDialog.cs
public Task StartAsync(IDialogContext context)
{
    // デフォルトのメッセージをセット
    context.PostAsync($"こんにちは！ドリンクおすすめ Botです。");
    context.Wait(MessageReceivedAsync);
    return Task.CompletedTask;
}

```

###Custom Vision を呼び出す準備
MessageReceivedAsync に Custom Vision の画像分析エンジンを呼び出すコードを追加していきます。
下記コードの YOUR_PREDICTION_KEY には Custom Vision の Prediction URL 表示画面で表示される Prediction-key、YOUR_PROJECT_ID には URL に含まれている Project ID をコピーします。
[Custom Vision 編](http://qiita.com/annie/items/293525901020685ad5f6#%E7%94%BB%E5%83%8F%E5%88%A4%E5%AE%9A%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B) の API アクセス情報画面から取得できます。Prediction が [Already Default] になっているのを確認してください。

![](https://msdnshared.blob.core.windows.net/media/2017/11/bluesky_20171130_02-e1512022681472.png)
 
```RootDialog.cs
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    var activity = await result as Activity;

    // 変数定義
    bool food = false;  // "food" タグの有無
    string tag ="";     // 食べ物カテゴリータグ
    string msg = "";    // 返答メッセージ

    // Custom Vision API を使う準備
    var cvEp = new PredictionEndpoint { ApiKey = "YOUR_PREDICTION_KEY" };
    var cvGuid = new Guid("YOUR_PROJECT_ID");

    // 画像が送られてきたら Custom Vision を呼び出してタグを取得
    // メッセージをセット
    // ※次以降の項目で作成します

    await context.PostAsync(msg);
    context.Wait(MessageReceivedAsync);
}

```

###Custom Vision の呼び出し、結果の取得
画像を Stream として取得し、Custom Vision を呼び出します。Probability (≒信頼度) の高いものから参照されるため、Probability > 0.8 となるタグのみを取得します。"food" とそれ以外のタグ (のうち信頼度が一番高いもの) は分けてセットします。

```RootDialog.cs
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    :
    中略
    :
    // 画像が送られてきたら Custom Vision を呼び出してタグを取得
    if (activity.Attachments?.Count != 0)
    {
        // 送られてきた画像を Stream として取得
        var photoUrl = activity.Attachments[0].ContentUrl;
        var client = new HttpClient();
        var photoStream = await client.GetStreamAsync(photoUrl);

        try
        {
            // 画像を判定
            var cvResult = await cvEp.PredictImageAsync(cvGuid, photoStream);

            // food タグ および カテゴリーを取得
            foreach (var item in cvResult.Predictions)
            {
                if (item.Probability > 0.8)
                {
                    if (item.Tag == "food")
                    {
                        food = true;
                    }
                    else
                    {
                        tag = item.Tag;
                        break;
                    }
                }
            }
        }
        catch
        {
            // Error Handling
        }
    }

    // メッセージをセット
    // ※次以降の項目で作成します

    await context.PostAsync(msg);
    context.Wait(MessageReceivedAsync);
}
```

###返答の作成
以下のロジックでメッセージをセットします。
 - "food"＞0.8 & "tag"(他のタグ)＞0.8 → ("food" 以外の)タグの食べ物
 - "food"＞0.8 & "tag"(他のタグ)≦0.8 → "食べ物
 - すべてのタグ≦0.8 → 食べ物でないと判定

```RootDialog.cs
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    :
    中略
    :
    // メッセージをセット
    if (tag != "")
    {
        // タグに応じてメッセージをセット
        msg = "この写真は " + tag + " だね♪";
    }
    else if (food == true)
    {
        //msg = "I'm not sure what it is ...";
        msg = "この食べ物は分からないです．．．日本の夏は麦茶だね！";
    }
    else
    {
        //msg = "Send me food photo you are eating!";
        msg = "食べ物の写真を送ってね♪";
    }

    await context.PostAsync(msg);
    context.Wait(MessageReceivedAsync);
}
```

忘れずに *RootDialog.cs* を保存しておきます。


##アプリケーションの動作確認
ここで一旦 BOT の動作確認を行います。、F5 または デバック＞デバックの開始 をクリックして、プロジェクトのビルドおよび起動を行います。ブラウザが起動して Bot Framework のデフォルト画面が表示されたら、Bot Framework Channel Emulator を起動してアクセスを行います。
Bot Framework Channel Emulator の上部中央にある Bot Url に、起動しているブラウザと同じ URL (デフォルトでは http://localhost:xxxx) に /api/messages を追加したアドレス (http://localhost:xxxx/api/messages) を指定します。

何か文字を入力して送信すると、StartAsync で指定したデフォルトの回答が返信されることを確認してください。
入力エリアの画像アイコンをクリックして、分析した画像を選択、BOT に送信すると、判定されたタグ、または判定できなかったメッセージが返信されるのを確認してください。

![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_08.png)


##会話のハンドリングの記述 (応用)
画像を判定して取得できたタグに応じてメッセージを変更します。

```RootDialog.cs
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    :
    中略
    :
    // メッセージをセット
    if (tag != "")
    {
        // タグに応じてメッセージをセット
        // msg = "この写真は " + tag + " だね♪";
        switch (tag)
        {
            case "curry":
                msg = "カレーおいしそう！甘いチャイでホッとしよう☕";
                break;
            case "gyoza":
                msg = "やっぱ餃子にはビールだね🍺";
                break;
            case "pizza":
                msg = "ピザには刺激的な炭酸飲料★はどうかな？";
                break;
            case "meat":
                msg = "肉、にく、ニク♪ 赤ワインを合わせてどうぞ🍷";
                break;
            case "ramen":
                msg = "やめられないよねー。ラーメンには緑茶でスッキリ☆";
                break;
            case "sushi":
                msg = "今日はちょっとリッチにお寿司？合わせるなら日本酒かな🍶";
                break;
        }

    else if (food == true)
    {
        msg = "この食べ物は分からないです．．．日本の夏は麦茶だね！";
    }
    else
    {
        msg = "食べ物の写真を送ってね♪";
    }

    await context.PostAsync(msg);
    context.Wait(MessageReceivedAsync);
}

```

#アプリケーションの動作(再)確認
F5 または デバック＞デバックの開始 をクリックして、プロジェクトのビルドおよび起動を行います。ブラウザが起動して Bot Framework のデフォルト画面が表示されたら、Bot Framework Channel Emulator を起動してアクセスを行います。

入力エリアの画像アイコンをクリックして、分析した画像を選択、BOT に送信します。
設定したメッセージが返答されたら完成です。

![](https://msdnshared.blob.core.windows.net/media/2017/06/bluesky_20170624_09.png)


#Appendix
完成形のソースコードを GitHub にて公開しました。
https://github.com/a-n-n-i-e/CognitiveCustomVision-DrinkPairingBot/tree/master/CSharp