LineBot+ImageSearchで手軽にAlibabaクラウドの画像検索サービスを利用する

皆さんはじめまして、HRです。

今回はLineBotからAlibabaクラウドのImageSearchを使って、画像検索機能を利用してみました。

早速、実現方法について簡単に記載させていただきます。

①まずLineBotを作成するために、下記のURLからLineDevelopersアカウントを作成します。

developers.line.biz

アカウント作成後、LineBotを一つ作成し、友達を追加します。

LineDevelopersの下記情報をメモして、後ほどLineBotAPIを呼び出しする際利用します。

  • (1)Channel secret
  • (2)Channel access token (long-lived)
②Alibabaクラウド上で、ImageSearchインスタンスをデプロイ方法は下記のドキュメントを参照します。

www.alibabacloud.com

③ImageSearchのインスタンス作成後、下記のドキュメントに従って、事前に画像をインプットします。

www.alibabacloud.com

④今回利用するコードは下記になります。簡単に説明させていただきます。

利用するライブラリは以下になります。

import os
import re
import oss2
import base64
from io import BytesIO
from PIL import Image

import aliyunsdkimagesearch.request.v20190325.AddImageRequest as AddImageRequest
import aliyunsdkimagesearch.request.v20190325.DeleteImageRequest as DeleteImageRequest
import aliyunsdkimagesearch.request.v20190325.SearchImageRequest as SearchImageRequest
from aliyunsdkcore.client import AcsClient

from flask import Flask
from flask import request
from flask import abort

from linebot import LineBotApi
from linebot import WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent
from linebot.models import TextMessage
from linebot.models import TextSendMessage
from linebot.models import ImageMessage
from linebot.models import ImageSendMessage

後ほどコードをECSにデプロイするため、ECSの環境変数は以下のものを設定します。
まずはLineBotAPIの呼び出しに必要なトークン情報です。

# LineBot Access Token
CHANNEL_ACCESS_TOKEN = os.environ["CHANNEL_ACCESS_TOKEN"]
CHANNEL_SECRET = os.environ["CHANNEL_SECRET"]

次にImageSearchを利用するための必要なトークン情報も設定します。

  • (1)Alibabaクラウドにアクセスに必要なアクセスキーとシークレットトークン
  • (2)OSSにアクセスするためのURL、バケット名、バケットディレクトリ
  • (3)ImageSearchインスタンスのURL、Bot名、リージョン名
# AliCloud Access Token
ALICLOUD_ACCESS_KEY = os.environ["ALICLOUD_ACCESS_KEY"]
ALICLOUD_ACCESS_SECRET = os.environ["ALICLOUD_ACCESS_SECRET"]
ALICLOUD_REGION = os.environ["ALICLOUD_REGION"]
ALICLOUD_BUCKET_NAME = os.environ["ALICLOUD_BUCKET_NAME"]
ALICLOUD_BUCKET_URL = os.environ["ALICLOUD_BUCKET_URL"]
ALICLOUD_BUCKET_DIR = os.environ["ALICLOUD_BUCKET_DIR"]
ALICLOUD_IMGSEARCH_URL = os.environ["ALICLOUD_IMGSEARCH_URL"]
ALICLOUD_IMGSEARCH_BOT = os.environ["ALICLOUD_IMGSEARCH_BOT"]

さて、本体のLineBotからImageSearchのAPIを呼びだしする部分は以下になります。

line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(CHANNEL_SECRET)

auth = oss2.Auth(ALICLOUD_ACCESS_KEY, ALICLOUD_ACCESS_SECRET)
bucket = oss2.Bucket(auth, ALICLOUD_BUCKET_URL, ALICLOUD_BUCKET_NAME)

# create AcsClient instance
client = AcsClient(ALICLOUD_ACCESS_KEY, ALICLOUD_ACCESS_SECRET, ALICLOUD_REGION)

# callback()
@app.route("/callback", methods=['POST'])
def callback():
  # Get X-Line-Signature header value
  signature = request.headers['X-Line-Signature']

  # Get request body as text
  body = request.get_data(as_text=True)
  app.logger.info("Request body: " + body)

  # Handle webhook body
  try:
    handler.handle(body, signature)
  except InvalidSignatureError:
    abort(400)

  return 'OK'

# ImageMessage Handler
@handler.add(MessageEvent, message=ImageMessage)
def handle_message(event):
  # get binary image
  message_id = event.message.id
  message_content = line_bot_api.get_message_content(message_id)
  image_bin = BytesIO(message_content.content)
  
  # search image
  request = SearchImageRequest.SearchImageRequest()
  request.set_endpoint(ALICLOUD_IMGSEARCH_URL)
  request.set_InstanceName(ALICLOUD_IMGSEARCH_BOT)

  encoded_pic_content = base64.b64encode(image)
  request.set_PicContent(encoded_pic_content)
  
  response = client.do_action_with_exception(request)

  # get string result
  data = str(response)

  # get image name
  imgName = (re.search("[a-z]*_[0-9]*_[0-9]*", data)).group(0)

  # get image on oss
  url = bucket.sign_url('GET', ALICLOUD_BUCKET_DIR + imgName + '.' + imgFormat, 60)

  # reply image message
  line_bot_api.reply_message(
    event.reply_token,
    ImageSendMessage(
      original_content_url = url,
      preview_image_url = url
    )
  )

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
  line_bot_api.reply_message(
    event.reply_token,
    TextSendMessage(text=event.message.text)
  )

iPhoneで取得した画像はサイズだと直接ImageSearchに送信できないため、画像をリサイズします。
※今回は画像サイズを一律(250,800)にしています。

  # resize image
  pil_img = Image.open(image_bin)
  re_img = pil_img.resize((250,800))
  
  # get binary image
  output = BytesIO()
  re_img.save(output, format=imgFormat)
  image = output.getvalue()

Flaskサーバを外部公開するため、下記のように記載します。

if __name__ == "__main__":
  callback()
  app.run(debug=False, host='0.0.0.0', port=443)
⑤作成したプログラムをECSにデプロイし、実行します。
⑥最後はLineDevelopersのWebHookURLに今回公開するサイトのURLを登録して構築完了です!

f:id:sbc_hr:20200228151239p:plain


それでは実際動作する際のイメージを確認してみましょう。


(1)作成したImageSearchのBotを選択しチャット画面を開きます。

f:id:sbc_hr:20200228151511j:plain


(2)携帯のカメラを起動して、検索したい画像を写真撮影して送信すれば、一番類似の画像が返ってくることが確認できました。

f:id:sbc_hr:20200228151452j:plain

まとめ

以上でiPhoneから画像を撮影し、LineBotを経由でImageSearchで画像検索ができました〜
ImageSearchとLineBotを結合することで、ImageSearchの利用もよりユーザーに近ずくことができると考えて今回のデモを作成しました。
ぜひ皆さんも実装してみてはいかがでしょうか。

今回は画像の検索のみ実装しましたが、また第2弾では画像の登録や削除も実装してみます!