LoginSignup
6
3

More than 5 years have passed since last update.

駅すぱあと x Cognitive Services で 画像DE路線当てBOT を作ろう! [BOT実装(Node.js ver) 編]

Last updated at Posted at 2017-09-25

Microsoft Cognitive ServicesCustom Vision Service は、オリジナルの画像判定エンジンを作成して API で推定値を取得できるサービスです。また、駅すぱあとwebサービス は、国内初の乗換案内ソフト 駅すぱあと がもつ様々な情報をWebAPIで提供しているサービスです。

今回は Custom Vision Service で作成した画像判定エンジンを利用して、
鉄道の路線を画像から判別し、 駅すぱあとwebサービス を利用して路線に含まれる駅を検索し、それを返す BOT を作成する方法を紹介します。

Microsoft Cognitive Services の Custom Vision Service は、オリジナルの画像判定エンジンを作成して API で推定値を取得できるサービスです。
機械学習などで画像判定ロジックを構築しなくても、画像をアップロードしてタグ付けを行うことで、画像判定エンジンを構築できます。

駅すぱあとwebサービス は、国内初の乗換案内ソフト 駅すぱあと がもつ様々な情報をWebAPIで提供しているサービスです。
経路探索はもちろん、駅データや時刻表などの情報を取得することができます。
無料で使えるフリープランを用意しており、企業にかぎらず個人でもご利用いただけます。

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

駅すぱあと x Cognitive Services で 画像DE路線当てBOT を作ろう!
(1) Custom Vision 編
(2) BOT実装 編
- Node.js ver 編 ※このページ
- C# ver 編

Custom Vision 編、BOT実装 編を通して作成できる 画像DE路線当てBOT↓

スクリーンショット 2017-09-22 18.26.46.png

この資料では、 Bot Builder を用いてBOTを実装する部分を紹介します。

必要な環境、サブスクリプションの準備

開発環境

Bot Framework Channel Emulator

BOTの挙動をローカルで確認する際に必要となります。
https://github.com/Microsoft/BotFramework-Emulator/releases/ からダウンロードすることができます。

Node.js

Node.jsを利用してBOTを開発します。
https://nodejs.org/en/ からダウンロードすることができます。

ngrok

ローカルホストを外部からでも叩けるようにするツールです。
今回は、BOTを経由して Custom Vision API に 画像を添付してPOSTリクエストを行うのですが、その際に必要となります。
https://ngrok.com/download からダウンロードできます。

駅すぱあと Webサービス のアクセスキー

駅すぱあと Webサービス のフリープランをお申込みいただき、アクセスキーを取得してください。
https://ekiworld.net/service/sier/webservice/free_provision.html
(即時発行ではないので少しばかしお時間かかります・・すいませんorz)

Custom Vision 編 を実施していない場合

Custom Vision 編 を実施していない場合はMicrosoft アカウントを準備してください。([Custom Vision 編] と同じもので問題ありません。)

Microsoft アカウント

Cognitive Services (と必要に応じて Azure) サブスクリプションの申し込みに必要ですので、持っていない場合は取得しておきます。

Microsoft アカウント登録手続き

BOT アプリの実装

BOTを動かす

まず最初に、BOTが自分の環境でちゃんと動くかどうかを確認します。
BOTを実装する作業ディレクトリに移動し、以下のコマンドを実行してください。

$ npm init

ぽちぽちエンターを押して設定していきますが、entry pointの項目だけは、デフォルトではなくapp.jsに設定することをオススメします。
設定が完了したら、BOT開発に必要な以下のモジュールをインストールします。

$ npm install --save botbuilder restify
  • botbuilder...MicrosoftBotFrameworkで動くBOTを作成できるSDKです。
  • restify...REST形式のwebサービス構築に特化したフレームワークです。

作業ディレクトリ直下に、app.jsファイルを作成します。
その中に、以下のコードをコピーして保存してください。
最低限の機能を揃えたサンプルコードです。

var restify = require('restify');
var builder = require('botbuilder');

var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
   console.log('%s listening to %s', server.name, server.url); 
});

var connector = new builder.ChatConnector({
    appId: process.env.MICROSOFT_APP_ID,
    appPassword: process.env.MICROSOFT_APP_PASSWORD
});

server.post('/api/messages', connector.listen());

var bot = new builder.UniversalBot(connector, function (session) {
    session.send("You said: %s", session.message.text);
});

ファイルを保存したら、挙動の確認を行います。
以下のコマンドを打ち込み、サービスを起動させます。

$ node app.js

サービスが起動すると、リクエストを受け付けるポート番号がターミナルに表示されます。

エミュレータを立ち上げます。
ウィンドウ上部のEnter your endpoint URLをクリックし、以下のURLを打ち込みます。
{{ポート番号}}の部分は、サービスを起動した時に表示されるポート番号に置き換えてください。

http://localhost:{{ポート番号}}/api/messages

Microsoft App IDMicrosoft App Passwordを指定する画面が表示されますが、それは無視して、Connectをクリックします。
メッセージ入力欄に適当に何かメッセージを書き込んでみましょう。
例えばHelloと入力したら、You said: Helloと返れば成功です。

20170901102355.png

Custom Vision の呼び出し

さて、自分の環境で無事にBOTが動かせることが確認できたら、Custom Vision APIをBOT内で呼び出してみましょう。

必要なモジュールをインストールします。

$ npm install --save dotenv request
  • dotenv...環境変数を扱う際に便利なモジュールです。
  • request...HTTPリクエストを扱う際に便利なモジュールです。

先ほど作成したapp.jsを開きます。
app.jsの上部に、app.js内でdotenvrequestが使用できるように、モジュールの読み込み記述を行います。

var restify = require('restify');
var builder = require('botbuilder');

// 追記する部分 ここから
require('dotenv').config();
var request = require('request');
// ここまで

:
中略
:

画像をURLとして取得し、Custom Vision を呼び出します。
var bot = new builder.UniversalBot(connector, function (session) {から以下の行を、まるっと下記のコードに書き換えます。

:
中略
:

var bot = new builder.UniversalBot(connector, function (session) {

  // メディア(画像)が添付されているか
  if(session.message.attachments.length > 0) {

    var tag = ""; // custom vision で定義したカテゴリータグ

    var customVisionApiRequestOptions = {
      uri: process.env.CUSTOM_VISION_API_URI,
      headers: {
        "Content-Type": "application/json",
        "Prediction-Key": process.env.CUSTOM_VISION_PREDICTION_KEY
      },
      json: {
        "Url": session.message.attachments[0].contentUrl
      }
    }

    // Custom Vision API へのPOSTリクエスト
    request.post(customVisionApiRequestOptions, function (error, response, body) {
      if(!error && response.statusCode == 200) {

        // Probability (≒信頼度) の高いものからリストされるため、0番目を取得
        tag = response.body.Predictions[0].Tag;

        session.send("%s に、いちばんにてるね!", tag);

      } else {
        console.log("error: " + error);
        session.send("はんていにしっぱいしちゃった。もういちどがぞうを送ってください。");
      }
    })
  } else {
    session.send("でんしゃのしゃしんを送ってね!");
  }
});

process.env.環境変数名で、指定された環境変数名から値を取得することができます。
Custom Vision API のPrediction-KeyやURLは、環境変数で扱いますので、環境変数に値をセットします。

app.jsと同じ階層に、.envという名前でファイルを生成し、その中に環境変数を記述していきます。

CUSTOM_VISION_API_URI={{ご自身の Custom Vision API のURL}}
CUSTOM_VISION_PREDICTION_KEY={{ご自身の Custom Vision API のPrediction Key}}

※windows10の場合、拡張子の前に名前がないとファイルを作らせてくれないようで、ファイルエクスプローラから.envファイルを新規作成できないことが分かりました。こちら( [Win10] Windows上で拡張子だけのファイルを作成する方法 )のページにコマンドプロンプトで作成する方法が挙げられています。

{{ご自身の Custom Vision API のURL}}{{ご自身の Custom Vision API のPrediction Key}}の値は、Custom Vision 編のAPIアクセス情報画面から取得できます。
Prediction URLの画面の、If you have an image URL:と書かれた方の値を利用します。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37393537372f63373531333030352d333661342d643261322d363662392d3065346263383137366433312e706e67.png

挙動を確認します。
エミュレータを開き、メニューボタンから、App Settingsを開きます。

スクリーンショット 2017-09-22 18.34.06.png

ngrokをダウンロードしてzipファイルを解凍すると、ngrokという名の実行ファイルが入っています。
その実行ファイルが置いてあるパスを指定します。
Bypass ngrok for local addressesと書かれたチェックボックスがありますが、チェックを外してください(チェックを入れてしまうと、意図しないエラーが発生してしまいます)。
最後にSAVEをクリックします。

スクリーンショット 2017-09-22 18.54.45.png

サービスを起動します。

$ node app.js

入力エリアの画像アイコンをクリックして、分析した画像を選択し、BOTに送信すると、判定されたタグが返信されるのを確認してください。

スクリーンショット 2017-09-26 11.12.15.png

駅すぱあとwebサービス の呼び出し

画像を判定して取得できたタグに応じてメッセージを変更します。
今回は、 駅すぱあとWebサービス を利用して、路線名から停車駅を取得し、表示するようにします。

駅すぱあとwebサービスの詳しい使い方は、駅すぱあとwebサービス フリープラン ドキュメントに掲載しています。

路線名、停車駅はこちらのAPIを利用して取得できます。
今回は予め路線コードはアプリ内に保持し、停車駅のみをAPIで取得しています。
- 路線名の取得: http://api.ekispert.jp/v1/xml/operationLine?name=路線名&key=アクセスキー
- 停車駅の取得: http://api.ekispert.jp/v1/json/station?&operationLineCode=路線コード&key=アクセスキー

app.jsを編集します。
var bot = new builder.UniversalBot(connector, function (session) {から以下の行を、まるっと下記のコードに書き換えます。

:
中略
:

var bot = new builder.UniversalBot(connector, function (session) {

  // メディア(画像)が添付されているか
  if(session.message.attachments.length > 0) {

    var lineName = ""; // 駅すぱあと運航路線名
    var lineCode = ""; // 駅すぱあと運航路線コード
    var tag = ""; // custom vision で定義したカテゴリータグ
    var stations = []; // 路線停車駅情報(名前、緯度経度、都道府県..etc)リスト
    var stationNames = []; // 路線停車駅名リスト

    var customVisionApiRequestOptions = {
      uri: process.env.CUSTOM_VISION_API_URI,
      headers: {
        "Content-Type": "application/json",
        "Prediction-Key": process.env.CUSTOM_VISION_PREDICTION_KEY
      },
      json: {
        "Url": session.message.attachments[0].contentUrl
      }
    }

    // Custom Vision API へのPOSTリクエスト
    request.post(customVisionApiRequestOptions, function (error, response, body) {
      if(!error && response.statusCode == 200) {

        // Probability (≒信頼度) の高いものからリストされるため、0番目を取得
        tag = response.body.Predictions[0].Tag;

        switch (tag) {
          case "Chuo_Sobu":
            lineName = "JR中央・総武線各駅停車";
            lineCode = "110";
            break;
          case "Chuo_Ex":
            lineName = "JR中央線快速";
            lineCode = "109";
            break;
          case "Keihin-Tohoku":
            lineName = "JR京浜東北線";
            lineCode = "115";
            break;
          case "Tokaido":
            lineName = "JR東海道本線";
            lineCode = "117";
            break;
          case "Yamanote":
            lineName = "JR山手線";
            lineCode = "113";
            break;
          case "Yokosuka_SobuEx":
            lineName = "JR横須賀線";
            lineCode = "116";
            break;
        }

        session.send("%s に、いちばんにてるね!", lineName);

        var ekispertRequestOptions = {
          url: encodeURI("https://api.ekispert.jp/v1/json/station?operationLineCode=" + lineCode + "&key=" + process.env.EKISPERT_ACCESS_KEY),
          json: true
        }

        // 駅すぱあとWebサービスへのGETリクエスト
        request.get(ekispertRequestOptions, function(error, response, body) {
          if (!error && response.statusCode == 200) {

            session.send("%s は、以下のえきをはしるよ。", lineName);

            stations = response.body.ResultSet.Point;

            // 駅名称を列挙した配列を作成
            for(var i=0; i<stations.length; i++) {
              stationNames.push(stations[i].Station.Name);
            }
            session.send("%s", stationNames.join(''));
          } else {
            console.log("error: " + error);
          }
        })
      } else {
        console.log("error: " + error);
        session.send("はんていにしっぱいしちゃった。もういちどがぞうを送ってください。");
      }
    })
  } else {
    session.send("でんしゃのしゃしんを送ってね!");
  }
});

.envファイルに、 駅すぱあとwebサービス のアクセスキーを追加します。

:
中略
:

EKISPERT_ACCESS_KEY={{ご自身の 駅すぱあとwebサービス のアクセスキー}}

{{ご自身の 駅すぱあとwebサービス のアクセスキー}}は、駅すぱあとwebサービスにアクセスする為に必要な値です。
駅すぱあとwebサービス フリープラン 申し込みページから無料でお申し込みいただけます。

挙動を確認します。

$ node app.js

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

スクリーンショット 2017-09-22 18.26.46.png

Appendix

完成形のソースコードを GitHub にて公開しました。
https://github.com/hmaruyama/train-finder-bot

終わりに

様々なサービスを組み合わせることで、簡単にBOTを実装することができました。
作成したBOTをベースに、画像判定の精度をあげるもよし、対応路線のバリエーションを増やすもよし、会話のバリエーションを増やすもよし・・。
パブリックに公開するなら、こちらの手順(Microsoft Bot Framework、Azureを利用したBot開発!Bot作成からWeb上で公開するまで - hi, hikaru)が参考になると思います。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3