2017.09.08

Heroku×Einstein Visionで作る画像認識LINE Bot

はじめに

今回は、HerokuとEinstein Visionを使って画像認識するLINE Botを作成してみたいと思います。

Einstein Visionはセールスフォース・ドットコム社が提供する画像認識APIです。
HerokuのAddonを利用することで簡単にアプリに組み込むことができます。

システム概要

Heroku上に構築したBotアプリで画像情報を受け取り、Einstein Visionで画像認識した結果をLINEに返信します。
今回、Botアプリの作成にはSpring Bootを使用します。

システム概要

必要なもの

- Heroku アカウント
- LINE Bot アカウント
- Java開発環境
- Git
- Heroku CLI

Heroku設定

HerokuにAppを作成し、Einstein VisionをAddonに追加します。

すると、Config Varsに以下の項目が追加されます。
- EINSTEIN_VISION_ACCOUNT_ID
- EINSTEIN_VISION_PRIVATE_KEY
- EINSTEIN_VISION_URL

さらに、以下の環境変数をConfig Varsに追加します。
ここで定義しておけば環境毎に異なる変数をアプリで直接編集しなくて良いので便利です。
Key Value
EINSTEIN_VISION_MODEL_ID GeneralImageClassifier
LINE_CHANNEL_SECRET LINE developers / Basic informationの「Channel Secret」
LINE_CHANNEL_TOKEN LINE developers / Basic informationの「Channel Access Token」

Botアプリ作成

次にSpring BootでBotアプリを作成します。

LINEのイメージメッセージイベントは画像が直接送られてくる訳ではなく、イベントに含まれるmessageIdを元にLINEのContent APIをコールして取得する必要があります。
また、Einstein VisionはJWTを使ったOAuth2認証が必要になります。

これらを踏まえ以下のような処理を実装していきます。

① LINEのイメージイベントを受信する
② LINEから画像を取得する
③ Einstein VisionのJWTアサーションを作成する
④ JWTアサーションを使ってアクセストークンを取得する
⑤ Einstein Visionで画像を認識する
⑥ 認識結果をLINEに返信する

1. Spring Bootプロジェクト作成

まずはプロジェクトを作成します。
プロジェクト作成には以下のSPRING INITIALIZRというサイトが便利です。

https://start.spring.io/

Maven Project with Javaを選択し、プロジェクトを作成します。
dependenciesは直接編集するのでここでは選択しなくて構いません。

Zipファイルが出来上がりますので、適当な場所に解凍します。

2. pom.xmlの編集

pom.xmlを以下のように編集します。
LINE Bot SDKやJWTアサーションの作成に必要なライブラリなどを依存関係に追加します。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <groupId>com.example</groupId>
        <artifactId>line-img-prediction</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>

        <name>line-img-prediction</name>
        <description>Demo project for Spring Boot</description>

        <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>1.5.6.RELEASE</version>
                <relativePath/> <!-- lookup parent from repository -->
        </parent>

        <properties>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
                <java.version>1.8</java.version>
        </properties>

        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                </dependency>

                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>

                <dependency>
                        <groupId>com.linecorp.bot</groupId>
                        <artifactId>line-bot-spring-boot</artifactId>
                        <version>1.8.0</version>
                </dependency>

                <dependency>
                        <groupId>commons-codec</groupId>
                        <artifactId>commons-codec</artifactId>
                        <version>1.9</version>
                </dependency>

                <dependency>
                        <groupId>com.fasterxml.jackson.core</groupId>
                        <artifactId>jackson-databind</artifactId>
                        <version>2.8.5</version>
                </dependency>

                <dependency>
                        <groupId>org.bitbucket.b_c</groupId>
                        <artifactId>jose4j</artifactId>
                        <version>0.6.0</version>
                </dependency>

                <dependency>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.16.18</version>
                        <scope>provided</scope>
                </dependency>

                <dependency>
                        <groupId>commons-io</groupId>
                        <artifactId>commons-io</artifactId>
                        <version>2.5</version>
                </dependency>

        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>


</project>
pom.xml
編集したら、STSやIntelliJなどのIDEでMavenプロジェクトとしてImportします。

3. application.yml作成

src/main/resources/config配下にapplication.ymlを作成し、アプリケーションの設定情報を定義します。
${Key}と記述することで、Heroku Config Varsで定義している外部設定値を読み込んでくれます。
einsteinVision:
  accountId: ${EINSTEIN_VISION_ACCOUNT_ID}
  privateKey: ${EINSTEIN_VISION_PRIVATE_KEY}
  url: ${EINSTEIN_VISION_URL}
  expiryInSeconds: 720
  tokenUrl: ${EINSTEIN_VISION_URL}v2/oauth2/token
  predictUrl: ${EINSTEIN_VISION_URL}v2/vision/predict
  modelId: ${EINSTEIN_VISION_MODEL_ID}
line.bot:
  channel-token: ${LINE_CHANNEL_TOKEN}
  channel-secret: ${LINE_CHANNEL_SECRET}
  handler.path: /callback
application.yml

4. クラス作成

以下のクラスを作成します。
Class Description
MyLineMessageHandler LINEメッセージイベントのハンドラークラス
EinsteinVisionTokenService Einstein Visionのアクセストークンを取得するクラスのインタフェース
EinsteinVisionTokenServiceImpl Einstein Visionのアクセストークンを取得する実装クラス
EinsteinVisionPredictionService Einstein Visionで画像認識を行うクラスのインタフェース
EinsteinVisionPredictionServiceImpl Einstein Visionで画像認識を行う実装クラス
EinsteinVisionProperties Einstein Visionの設定情報を保持するクラス
EinsteinVisionTokenResponseEntity Einstein Visionのアクセストークン取得結果を保持するクラス
EinsteinVisionPredictionResponseEntity Einstein Visionの画像認識結果を保持するクラス
EinsteinVisionProbabilityResponseEntity Einstein Visionの画像認識結果の詳細を保持するクラス
SDKがないためEinstein Visionに関連したクラスが多く見えますが、実際に処理が必要なクラスはEinsteinVisionTokenServiceImplとEinsteinVisionPredictionServiceImplだけです。


<MyLineMessageHandler>
LINEのメッセージイベントをハンドリングするクラスです。
通常であればRestControllerでエンドポイントを作成するところですが、今回はSpring Boot用のLINE Bot SDKの機能を利用します。
クラスに@LineMessageHandler、メソッドに@EventMappingをつけることで、エンドポイントとして機能するようになります。
メソッドには受け取りたいイベントに応じた型で引数を指定します。
署名検証やイベントの判別処理を書かなくて良いためかなり便利です。
package com.example.lineimgprediction.handler;

import com.example.lineimgprediction.entity.EinsteinVisionPredictionResponseEntity;
import com.example.lineimgprediction.entity.EinsteinVisionProbabilityResponseEntity;
import com.example.lineimgprediction.service.EinsteinVisionPredictionService;
import com.example.lineimgprediction.service.EinsteinVisionTokenService;
import com.linecorp.bot.client.LineMessagingClient;
import com.linecorp.bot.client.MessageContentResponse;
import com.linecorp.bot.model.ReplyMessage;
import com.linecorp.bot.model.event.MessageEvent;
import com.linecorp.bot.model.event.message.ImageMessageContent;
import com.linecorp.bot.model.message.Message;
import com.linecorp.bot.model.message.TextMessage;
import com.linecorp.bot.spring.boot.annotation.EventMapping;
import com.linecorp.bot.spring.boot.annotation.LineMessageHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

/**
 * LINEメッセージイベントのハンドラークラス.
 */
@Slf4j
@LineMessageHandler
public class MyLineMessageHandler {

    @Autowired
    private EinsteinVisionTokenService einsteinVisionTokenService;

    @Autowired
    private EinsteinVisionPredictionService einsteinVisionPredictionService;

    @Autowired
    private LineMessagingClient lineMessagingClient;

    /**
     * イメージメッセージイベントをハンドリングする.
     * @param event イメージメッセージイベント
     */
    @EventMapping
    public void handleImageMessageEvent(MessageEvent<ImageMessageContent> event) {
        log.info("***** Image Message Event *****");
        handleContent(
                event.getMessage().getId(),
                messageContentResponse -> replyMessage(messageContentResponse, event.getReplyToken()));
    }

    /**
     * メッセージコンテンツを取得する.
     * @param messageId メッセージID
     * @param messageConsumer メッセージコンシューマ
     */
    private void handleContent(String messageId, Consumer<MessageContentResponse> messageConsumer) {
        final MessageContentResponse messageContentResponse;

        try {
            // LINEからメッセージコンテンツを取得する
            log.info("***** Get Message Content *****");
            messageContentResponse = lineMessagingClient.getMessageContent(messageId).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }

        messageConsumer.accept(messageContentResponse);
    }

    /**
     * 画像を認識し、メッセージを返信する.
     * @param messageContentResponse メッセージコンテンツ取得結果
     * @param replyToken リプライトークン
     */
    private void replyMessage(MessageContentResponse messageContentResponse, String replyToken) {
        InputStream responseInputStream = messageContentResponse.getStream();

        // Einstein Visionのアクセストークンを取得する
        log.info("***** Get Access Token *****");
        final String accessToken = einsteinVisionTokenService.getAccessToken();

        // Einstein Visionで画像認識を行う
        log.info("***** Prediction with Image *****");
        EinsteinVisionPredictionResponseEntity einsteinVisionPredictionResponseEntity = null;
        try {
             einsteinVisionPredictionResponseEntity =
                einsteinVisionPredictionService.predictionWithImageBase64String(
                        Base64.encodeBase64String(IOUtils.toByteArray(responseInputStream)),
                        accessToken);
        } catch (IOException e) {
            new RuntimeException(e);
        }

        // 結果をパースする
        StringBuilder stringBuilder = new StringBuilder();
        for (EinsteinVisionProbabilityResponseEntity probabilityResponseEntity
                : einsteinVisionPredictionResponseEntity.getProbabilities()) {
            if (stringBuilder.length() > 0) {
                stringBuilder.append("\n");
            }
            stringBuilder.append(probabilityResponseEntity.getLabel() + "(" + probabilityResponseEntity.getProbability() * 100 + "%)");
        }

        List<Message> messageList = new ArrayList<>();
        messageList.add(new TextMessage(stringBuilder.toString()));

        // LINEに返信する
        log.info("***** Reply Message *****");
        lineMessagingClient.replyMessage(new ReplyMessage(replyToken, messageList));
    }
}
MyLineMessageHandler.java
<EinsteinVisionProperties>
Einstein Visionの設定情報を保持するクラスです。

@DataはLombokが提供する機能です。
getter/setterなどのボイラーテンプレートを自動生成してくれます。
なお、Lombokを使用するにあたってIDE側で設定が必要になります。
IntelliJの場合は、File > Settings > Plugins > Browse repositoriesからLombok Pluginをインストールします。

@ConfigurationPropertiesはSpring Bootが提供する機能です。
DI対象のクラスに@ConfigurationPropertiesを付与することで、application.ymlの設定値を読み込んだ状態でインジェクションされます。
application.ymlはHeroku Config Varsの設定値を参照するようにしているので、このクラスからHeroku Config Varsの設定値を取得することができるようになります。
package com.example.lineimgprediction.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Data
@ConfigurationProperties(prefix = "einsteinVision")
public class EinsteinVisionProperties {
    private String accountId;
    private String privateKey;
    private String url;
    private float expiryInSeconds;
    private String tokenUrl;
    private String predictUrl;
    private String modelId;
}
EinsteinVisionProperties.java
<EinsteinVisionTokenService / EinsteinVisionTokenServiceImpl>
Einstein Visionのアクセストークンを取得するクラスです。

Private KeyからJWTアサーションを作成し、アクセストークンを要求します。
こちらの処理については以下のHeroku Dev Centerのサンプルを参考にしています。

https://devcenter.heroku.com/articles/einstein-vision#generate-a-token-in-java
package com.example.lineimgprediction.service;

/**
 * Einstein Visionのアクセストークンを取得するクラスのインタフェース.
 */
public interface EinsteinVisionTokenService {
    public String getAccessToken();
}
EinsteinVisionTokenService.java
package com.example.lineimgprediction.service;

import com.example.lineimgprediction.entity.EinsteinVisionTokenResponseEntity;
import com.example.lineimgprediction.properties.EinsteinVisionProperties;
import org.apache.commons.codec.binary.Base64;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.lang.JoseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.UUID;

/**
 * EinsteinVisionのアクセストークンを取得する実装クラス.
 */
@Service
public class EinsteinVisionTokenServiceImpl implements EinsteinVisionTokenService {

    @Autowired
    private EinsteinVisionProperties einsteinVisionProperties;

    /**
     * アクセストークンを取得する.
     * @return アクセストークン
     */
    public String getAccessToken() {
        final HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        final MultiValueMap<String, String> bodyMap = new LinkedMultiValueMap<>();
        bodyMap.add("assertion", createJwtAssertion());
        bodyMap.add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");

        final HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(bodyMap, httpHeaders);

        RestTemplate restTemplate = new RestTemplate();

        final ResponseEntity<EinsteinVisionTokenResponseEntity> responseEntity =
                restTemplate.postForEntity(
                        einsteinVisionProperties.getTokenUrl(),
                        httpEntity,
                        EinsteinVisionTokenResponseEntity.class);

        return responseEntity.getBody().getAccessToken();
    }

    /**
     * JWTアサーションを作成する.
     * @return JWTアサーション
     */
    private String createJwtAssertion() {
        final JwtClaims jwtClaims = new JwtClaims();
        jwtClaims.setSubject(einsteinVisionProperties.getAccountId());
        jwtClaims.setAudience(einsteinVisionProperties.getTokenUrl());
        jwtClaims.setExpirationTimeMinutesInTheFuture(einsteinVisionProperties.getExpiryInSeconds() / 60);
        jwtClaims.setIssuedAtToNow();

        // generate the payload
        final JsonWebSignature jwt = new JsonWebSignature();
        jwt.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
        jwt.setPayload(jwtClaims.toJson());
        jwt.setKeyIdHeaderValue(UUID.randomUUID().toString());

        // sign using the private key
        jwt.setKey(createPrivateKey());

        try {
            return jwt.getCompactSerialization();
        } catch (JoseException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * プライベートキーを作成する.
     * @return プライベートキー
     */
    private PrivateKey createPrivateKey() {
        final String privateKeyBase64 = einsteinVisionProperties.getPrivateKey();
        String privateKeyPEM =
                privateKeyBase64.replace("-----BEGIN RSA PRIVATE KEY-----\n", "")
                                .replace("\n-----END RSA PRIVATE KEY-----", "");

        // Base64 decode the data
        byte[] encoded = Base64.decodeBase64(privateKeyPEM);

        try {
            DerInputStream derReader = new DerInputStream(encoded);
            DerValue[] seq = derReader.getSequence(0);

            // skip version seq[0];
            BigInteger modulus = seq[1].getBigInteger();
            BigInteger publicExp = seq[2].getBigInteger();
            BigInteger privateExp = seq[3].getBigInteger();
            BigInteger primeP = seq[4].getBigInteger();
            BigInteger primeQ = seq[5].getBigInteger();
            BigInteger expP = seq[6].getBigInteger();
            BigInteger expQ = seq[7].getBigInteger();
            BigInteger crtCoeff = seq[8].getBigInteger();

            RSAPrivateCrtKeySpec keySpec =
                    new RSAPrivateCrtKeySpec(
                            modulus,
                            publicExp,
                            privateExp,
                            primeP,
                            primeQ,
                            expP,
                            expQ,
                            crtCoeff);

            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new IllegalStateException(e);
        }
    }
}
EinsteinVisionTokenServiceImpl.java
<EinsteinVisionTokenResponseEntity>
アクセストークン要求のレスポンスをパースした結果を保持するクラスです。
package com.example.lineimgprediction.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class EinsteinVisionTokenResponseEntity {
    @JsonProperty("access_token")
    private String accessToken;
    @JsonProperty("token_type")
    private String tokenType;
    @JsonProperty("expires_in")
    private float expiresIn;
}
EinsteinVisionTokenResponseEntity.java
<EinsteinVisionPredictionService / EinsteinVisionPredictionServiceImpl>
Einstein Visionに画像認識を要求するクラスです。

アクセストークンをヘッダにセットし、マルチパートで以下のAPIをコールします。

https://metamind.readme.io/docs/prediction-with-image-base64-string

レスポンスはEinsteinVisionPredictionResponseEntityにセットして返却します。
package com.example.lineimgprediction.service;

import com.example.lineimgprediction.entity.EinsteinVisionPredictionResponseEntity;

/**
 * Einstein Visionで画像認識を行うクラスのインタフェース.
 */
public interface EinsteinVisionPredictionService {
    public EinsteinVisionPredictionResponseEntity predictionWithImageBase64String(final String imageBase64String,
                                                                                  final String accessToken);
}
EinsteinVisionPredictionService.java
package com.example.lineimgprediction.service;

import com.example.lineimgprediction.entity.EinsteinVisionPredictionResponseEntity;
import com.example.lineimgprediction.properties.EinsteinVisionProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

/**
 * Einstein Visionで画像認識を行う実装クラス.
 */
@Service
public class EinsteinVisionPredictionServiceImpl implements EinsteinVisionPredictionService {

    @Autowired
    private EinsteinVisionProperties einsteinVisionProperties;

    /**
     * Base64の画像情報をEinstein Visionで認識する.
     * @param imageBase64String Base64の画像情報
     * @param accessToken アクセストークン
     * @return 認識結果
     */
    public EinsteinVisionPredictionResponseEntity predictionWithImageBase64String(final String imageBase64String,
                                                                                  final String accessToken) {
        final HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        httpHeaders.set("Cache-Control", "no-cache");
        httpHeaders.set("Authorization", "Bearer " + accessToken);

        final MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
        parts.add("modelId", einsteinVisionProperties.getModelId());
        parts.add("sampleBase64Content", imageBase64String);
        parts.add("numResults", 5);

        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(parts, httpHeaders);

        RestTemplate restTemplate = new RestTemplate();

        // Prediction with Image Base64 String
        ResponseEntity<EinsteinVisionPredictionResponseEntity> responseEntity =
                restTemplate.postForEntity(
                        einsteinVisionProperties.getPredictUrl(),
                        httpEntity,
                        EinsteinVisionPredictionResponseEntity.class);

        return responseEntity.getBody();
    }
}
EinsteinVisionPredictionServiceImpl.java
<EinsteinVisionPredictionResponseEntity / EinsteinVisionProbabilityResponseEntity>
画像認識要求のレスポンスをパースした結果を保持するクラスです。
package com.example.lineimgprediction.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class EinsteinVisionPredictionResponseEntity {
    @JsonProperty("message")
    private String message;
    @JsonProperty("object")
    private String object;
    @JsonProperty("probabilities")
    private List<EinsteinVisionProbabilityResponseEntity> probabilities;
    @JsonProperty("sampleId")
    private String sampleId;
    @JsonProperty("status")
    private String status;
}
EinsteinVisionPredictionResponseEntity.java
package com.example.lineimgprediction.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class EinsteinVisionProbabilityResponseEntity {
    @JsonProperty("label")
    private String label;
    @JsonProperty("probability")
    private float probability;
}
EinsteinVisionProbabilityResponseEntity.java

5. Procfile作成

Herokuで起動するためにプロジェクト直下にProcfileを作成します。
web: java -Dserver.port=$PORT -jar target/*.jar --server.port=$PORT --spring.profiles.active=heroku
Procfile

Herokuデプロイ

Botアプリを作成したらHerokuへデプロイします。
デプロイ方法は割愛しますが、以下のHeroku Dev Centerのサイトが参考になります。

https://devcenter.heroku.com/articles/git

LINE Webhook設定

BotアプリがLINEのメッセージイベントを受け取れるように、Webhookの設定を行います。
設定はLINE DevelopersのBasic Informationページで行います。
※Webhookが有効になっていない場合は有効化してください。
Webhook URLに以下のURLを設定します。

<Heroku App Domain>callback

LINE Webhook設定

動作確認

設定が終わったら動作確認します。
なお、今回Einstein VisionのモデルはプリビルドされているGENERAL IMAGE MODELを使用しています。

試しに猫の画像を認識させてみます。

動作確認1

種別はあってないですが、とりあえず猫のようなものだとは認識されていますね。

もう一つ試してみます。デジタル時計です。

動作確認2

今度は高い精度で認識してくれました。

どれだけの精度で認識できるかはモデルに依存するところです。
他のモデルを使いたい場合は、Config Varsの「EINSTEIN_VISION_MODEL_ID」を変更してアプリを再起動すればOKです。

終わりに

今回はEinstein Visionを使った画像認識Botを作ってみました。
こういったことが手軽に試せるのもHerokuの良いところですね。
今後の機能拡張にも期待したいと思います。

また、Spring Bootは最近触り始めたのですが、JavaでWebアプリを作る際の面倒な設定が色々と省略できてとても良い印象を持ちました。

<今回作成したソースコード>
https://github.com/yusukenakamoto/line-img-prediction
55 件
     
  • banner
  • banner

関連する記事