Skip to Content
Technical Articles
Author's profile photo yasuyuki uno

Part 2 : SAP Cloud Platform FunctionsでLINE BOT開発(画像判定編)

前回のLINE BOT作成記事に引き続き、今回はSAP Leonardo Machine Learning APIと連携し、画像をAIが判定するBOTの作り方について紹介します。

できるようになること

  • SAP Leonardo ML APIの使い方がわかるようになる。
  • SAP Leonardo ML APIをNode.jsから呼び出せるようになる。

所要時間

  • 1時間30分

参考: LINEについて

LINEはアジア圏、特に日本において普及しているSNSツールです。
日本国内におけるユーザ数は約7600万人。デイリーアクティブユーザー(DAU)は85%と、利用者数、利用頻度共に非常に高い数値を誇っています。

参考: LINE Messaging APIとは?

LINEのアカウントを通じてユーザーとの双方向コミュニケーションを実現するAPIです。

  • LINEのトーク画面を使った対話型Botアプリケーションの開発が可能となります。

このAPIは、宅配便の再配達受付や、年賀状の作成など、多くの企業LINEアカウントにて既に活用されています。

参考: SAP Cloud Platform Functionsとは?

SAPが先般発表したサーバレスアーキテクチャのプログラム実行環境です。
Node.jsで作成したプログラムを実行することが出来ます。


手順の概要

  1. SAP API Business HubでLeonardo MLのAPIを試す
  2. チャネルの作成
    LINEアカウントでログインしてBOTアカウント作成や設定をする。
  3. Functionsのサービス有効化
    SAP Cloud Platform CF Trial Europe(Frankfurt)環境にアクセスし、Functionsを利用するための初期設定を行う。
  4. Node.jsのプログラムを作成する
    LINEトーク画面からのメッセージを受け取り、返信するプログラムを作成します。
  5. スマートフォン実機でのテスト

 

1.SAP API Business HubでLeonardo MLのAPIを試す

SAP API Business Hubで、Leonardo Machine LearningのAPIを簡単に試すことが可能です。

Leonardo MLでは、

  1. 事前にトレーニング済みのAIを使う
  2. ユーザが再学習させたAIを使う

ことが出来ます。今回は事前にトレーニング済みの画像分類APIを呼び出してみることにします。

API Business Hubの画像分類APIのページをブラウザで開き、試したいAPIのTry outボタンをクリックします。

画像ファイルを選択し、実行してみます。

何の画像であるかAIが判定した結果が返ってきます。

API Business HubのCode Snippetボタンを押すと、色々なプログラミング言語からAPIを呼び出すコードスニペットを参照することが出来ます。

この後の手順で、Node.jsからLeonardo MLのAPIをコールするときにAPI Keyを利用します。スニペットの画面を見て、API Keyを控えておきましょう。

 

2.チャネルの作成

LINEの公式ページの通りにチャネルの作成を行います。
設定方法、設定箇所は前回の記事をご参照ください。
今回私は、『画像認識くん』という名前のチャネル(BOTアカウント)を作成しました

チャネルの基本設定を行います。設定の変更箇所を朱書きしております。

 

3.Functionsのサービス有効化

2018年10月24日現在、FunctionsはSAP CP CF版TrialのEurope(Frankfurt)リージョンでのみ利用可能です。
また、Betaサービスを有効化する必要があります。

まず、Betaサービスが利用可能な新規サブアカウントを作成します。

次に、trial2サブアカウントのFunctionsサービスを有効化します。

Functionsのインスタンスを作成します。

 

4.Node.jsのプログラムを作成する

Functionsダッシュボードにアクセスし、プログラムを作成します。

プログラム名は『firstmlbot』にしました。

以下のコードをコピペしてご利用ください。
**********箇所をLINE BOTのチャネル用のアクセストークンに書き換えてください。
YOUR_API_KEY箇所をLeonardo ML APIのAPI Keyに書き換えて下さい。

■index.js

var request = require("request");
var https = require("https");

module.exports = {
    handler: function(event, context) {

        if (event.data) {
            if (event.data.events[0].type == "message") {
                if (event.data.events[0].message.type == "image") {
                    var imageid = event.data.events[0].message.id;

                    // get image from below link
                    //https://api.line.me/v2/bot/message/{imageid}/content
                    var options = {
                        method: 'GET',
                        uri: 'https://api.line.me/v2/bot/message/' + imageid + '/content',
                        encoding: null,
                        auth: {
                            bearer: "*********************" // LINE BOT Access Token
                        }
                    };

                    request(options, function(error, response, body) {

                        // get binary image
                        var binaryimage = new Buffer(body);

                        // request to SAP ML Server
                        var boundary = createBoundary();
                        let optionstosap = {
                            host: "sandbox.api.sap.com",
                            port: 443,
                            path: "/ml/imageclassification/classification",
                            method: "POST",
                            headers: {
                                "APIKey": "YOUR_API_KEY",
                                "Content-Type": "multipart/form-data; boundary=" + boundary
                            }
                        };

                        var reqtosap = https.request(optionstosap, function(res) {
                            var data2 = '';
                            res.setEncoding("utf8");

                            res.on("data", (chunk) => {

                                data2 += chunk;

                                var resultarr = JSON.parse(data2).predictions[0].results;

                                var columns = [];

                                for (var k = 0; k < resultarr.length; k++) {

                                    var column = {};
                                    column.title = resultarr[k].label.substring(0, 35);
                                    column.text = Math.round(resultarr[k].score * 100 * 10) / 10 + "%";
                                    var actions = [];
                                    var action = {};
                                    action.type = "uri";
                                    action.label = "Google翻訳";
                                    action.uri = "https://translate.google.co.jp/?hl=ja#en/ja/" + encodeURIComponent(resultarr[k].label);
                                    actions.push(action);
                                    column.actions = actions;
                                    columns.push(column);
                                }

                                var options2 = {
                                    method: 'POST',
                                    uri: 'https://api.line.me/v2/bot/message/reply',
                                    body: {
                                        replyToken: event.data.events[0].replyToken,
                                        messages: [{
                                            type: "template",
                                            altText: "Image classification result.",
                                            template: {
                                                type: "carousel",
                                                columns: columns
                                            }
                                        }]
                                    },
                                    auth: {
                                        bearer: "*********************" // LINE BOT Access Token
                                    },
                                    json: true
                                };
                                request(options2, function(err, res, body) {
                                    console.log(JSON.stringify(res));
                                });


                            });

                            reqtosap.on('end', function() {
                                console.log('data;', data2);
                                res.end();
                            });

                        });

                        reqtosap.on("error", function(e) {
                            console.error(e.message);
                        });


                        var buffer = unicode2buffer(
                            '--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="files"; filename="myimage.png"\r\n' +
                            'Content-Type: image/png\r\n\r\n'
                        );

                        var buffer = appendBuffer(buffer,
                            binaryimage
                        );

                        var buffer = appendBuffer(buffer,
                            unicode2buffer(
                                '\r\n' + '--' + boundary + '--'
                            )
                        );

                        reqtosap.write(Buffer.from(buffer));
                        reqtosap.end();

                    });
                }
            }
        }


        console.log('event data ' + JSON.stringify(event.data));
        return 'hello world from a function!';
    }
}


function createBoundary() {
    var multipartChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    var length = 30 + Math.floor(Math.random() * 10);
    var boundary = "---------------------------";
    for (var i = 0; i < length; i++) {
        boundary += multipartChars.charAt(Math.floor(Math.random() * multipartChars.length));
    }
    return boundary;
}

function unicode2buffer(str) {

    var n = str.length,
        idx = -1,
        byteLength = 512,
        bytes = new Uint8Array(byteLength),
        i, c, _bytes;

    for (i = 0; i < n; ++i) {
        c = str.charCodeAt(i);
        if (c <= 0x7F) {
            bytes[++idx] = c;
        } else if (c <= 0x7FF) {
            bytes[++idx] = 0xC0 | (c >>> 6);
            bytes[++idx] = 0x80 | (c & 0x3F);
        } else if (c <= 0xFFFF) {
            bytes[++idx] = 0xE0 | (c >>> 12);
            bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
            bytes[++idx] = 0x80 | (c & 0x3F);
        } else {
            bytes[++idx] = 0xF0 | (c >>> 18);
            bytes[++idx] = 0x80 | ((c >>> 12) & 0x3F);
            bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
            bytes[++idx] = 0x80 | (c & 0x3F);
        }
        if (byteLength - idx <= 4) {
            _bytes = bytes;
            byteLength *= 2;
            bytes = new Uint8Array(byteLength);
            bytes.set(_bytes);
        }
    }
    idx++;

    var result = new Uint8Array(idx);
    result.set(bytes.subarray(0, idx), 0);

    return result.buffer;
}

function appendBuffer(buf1, buf2) {
    var uint8array = new Uint8Array(buf1.byteLength + buf2.byteLength);
    uint8array.set(new Uint8Array(buf1), 0);
    uint8array.set(new Uint8Array(buf2), buf1.byteLength);
    return uint8array.buffer;
}

■Dependencies

{
  "dependencies": {
    "request": "*"
  }
} 

(再掲)アクセストークンは、LINE BOTのチャネルの基本設定画面で確認できます。

(再掲)API Keyは、SAP API Business HubのCode Snippetで確認できます。

ソースコードを編集したら、ダッシュボード右上のSave and Deployボタンを押して保存します。

次に、トリガ設定(何を契機として作成したプログラムを動作させるかの設定)を行います。今回は、HTTPトリガを利用します。


参考: Functionsで利用できるトリガ

トリガ 説明
HTTP URLを発行し、そのURLに対してHTTPリクエストが行われたときに発火します。
Timer 一定時間おきに処理を実行できます。
Event SAP Enterprise Messagingと組み合わせ、外部から送られてきたイベントを契機としてアプリケーションを動作させることができます。

FunctionsのトリガURLをコピーし、LINE Developersのチャネル設定画面のWebhook URLにペーストします。

これで開発は完了です。テストを行います。

 

5.スマートフォン実機でのテスト

LINE Developersのチャネル設定画面のQRコードをスマートフォンで読み取り、友達追加を行います。

BOTに画像を送ると、Leonardo ML 画像分類APIが何の画像なのかを判定してくれます。

以上で画像の判定をするLINE BOTの作成が出来ました。
下記のQRコードをお手持ちのスマートフォンで読み込むことで、今回作成したLINE BOTを簡単に体験することができます。

今回のLINE BOTは、グループチャットに招待して使うことも可能です。
是非LINEのグループチャットに招待して、知り合いの方と一緒にワイワイ遊んでみて下さい。

Leonardo MLの画像分類機能をビジネスで利用する場合は、トレーニング済AIではなく、再学習させたAIを利用すべきでしょう。

Leonardo MLの再学習については、いずれ記事にしたいと思います。

Assigned Tags

      1 Comment
      You must be Logged on to comment or reply to a post.
      Author's profile photo Maxime Simon
      Maxime Simon

      Blogとコードの共有ありがとうございます!すぐに自分のアカウントで再現でき、インパクトのあるデモが作れます!