Python
画像認識
docomoAPI
linebot
LINEmessagingAPI

[python] LINEで画像を送ると商品検索するbotを作ってみた

:star: 概要

  • LINEから画像が送信されるとMessagingAPIを使ってwebhookで画像を受け取る
  • docomoの画像認識APIで画像に写っている商品を分析し、その結果をカルーセルで表示する
  • docomoの画像認識APIで認識できる商品は、食品パッケージ/書籍/CD/DVD/ゲームソフト/PCソフトのみ

:star: 実際に作ったもの

  • 下のボタン、もしくは@sga5626fでID検索すると友達に追加できます

友だち追加

  • 画像を送るとこんな感じで分析結果を返してくれます

S__81625103.jpg

:star: 事前に準備すること

  • LINEのbotを用意する
  • webhookを受け取るアプリを作る
  • Herokuにデプロイする
  • LINEとアプリを連携する

上記については前に書いた記事 [python] LINEの文字起こし君を作ってみた と同じですので割愛します

:pencil: docomoAPIのAPIキーを取得する

  • # docomo Developer support に登録します
  • APIの新規利用申請をします。必要事項を入力して、画像認識のAPIの利用申請をしましょう

dev.smt.docomo.ne.jp__p=mypage.api.new.select&tx=5af15c5e6f2fa&rp=mypage.api.new.input(Laptop with MDPI screen).png

  • 利用申請が完了すると、APIキーが発行されます

dev.smt.docomo.ne.jp__p=mypage.api.index(Laptop with MDPI screen).png

:pencil: docomoAPIを使って画像から商品を検索する

  • # 画像認識API リファレンス に沿ってリクエストを送ります
  • candidatesがある場合は整形してlistの出力データを作ります
  • categoryによって返ってくるデータが若干違う(例: bookの場合はmakerやbrandがない等)ので注意してください
  • LINEのmessagingAPIで画像URLを送る場合、httpsしか受け付けないのでhttpsの場合のみimageUrlを使うようにしています
docomo.py
import requests

import settings

API_KEY = settings.API_KEY

endpoint = 'https://api.apigw.smt.docomo.ne.jp/imageRecognition/v1/recognize'


def search_product(image=None):
    if image is None:
        return '必要な情報が足りません'

    params = {
        'APIKEY': API_KEY,
        'recog': 'product-all',
        'numOfCandidates': 5,
    }

    headers = {"Content-Type": "application/octet-stream"}

    response = requests.post(
        endpoint,
        headers=headers,
        params=params,
        data=image,
    )

    status = response.status_code
    data = response.json()

    print(data)

    if status != 200 and status != 204:

        if status == 403:
            error = '今月に使用できる回数を超過しました'

        else:
            error = 'エラーが発生しました'

        print(status, data)
        return error

    candidates = data.get('candidates')

    if not candidates:
        return '商品が見つかりませんでした'

    results = []
    for content in candidates:
        content['detail']['maker'] = \
            content['detail'].get('maker') or  \
            content['detail'].get('publisher', '')

        content['detail']['brand'] = \
            content['detail'].get('brand') or  \
            content['detail'].get('author', '')

        imageUrl = 'https://i.vimeocdn.com/video/443809727.jpg?mw=700&mh=394'

        if content['imageUrl'].startswith('https'):
            imageUrl = content['imageUrl']

        result = {
            "thumbnail_image_url": imageUrl,
            "title": content['detail']['itemName'][:40],
            "text": "{maker}: {brand}\n発売日: {releaseDate}"
            .format(**content['detail']),
            "actions": {
                "label": "商品ページを見る",
                "uri": content['sites'][0]['url']
            }
        }
        results.append(result)

    print(results)

    return results

:pencil: LINEでカルーセルテンプレートで返信する

        result = search_product(image)

        if isinstance(result, str):
            messages = [
                TextSendMessage(text=result),
                TextSendMessage(text='食品パッケージ/書籍/CD/DVD/ゲームソフト/PCソフトを検索できるよ!')
            ]

        elif isinstance(result, list):

            columns = [
                CarouselColumn(
                    thumbnail_image_url=column['thumbnail_image_url'],
                    title=column['title'],
                    text=column['text'],
                    actions=[
                        URITemplateAction(
                            label=column['actions']['label'],
                            uri=column['actions']['uri'],
                        )
                    ]
                )
                for column in result
            ]

            messages = TemplateSendMessage(
                alt_text='template',
                template=CarouselTemplate(columns=columns),
            )

  • これをindex.pyに組み込むと、こんな感じになります
index.py
import os
from io import BytesIO

from flask import Flask, abort, request
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (CarouselColumn, CarouselTemplate, ImageMessage,
                            MessageEvent, TemplateSendMessage, TextMessage,
                            TextSendMessage, URITemplateAction)

import settings
from docomo import search_product

app = Flask(__name__)


line_bot_api = LineBotApi(settings.YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(settings.YOUR_CHANNEL_SECRET)


@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)
    # print("body:", body)

    # handle webhook body
    try:
        handler.handle(body, signature)

    except InvalidSignatureError as e:
        print("InvalidSignatureError:", e)
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    # print("handle_message:", event)
    text = event.message.text

    messages = [
        TextSendMessage(text=text),
        TextSendMessage(text='画像を送ってみてね!'),
    ]

    reply_message(event, messages)


@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    # print("handle_image:", event)

    message_id = event.message.id
    message_content = line_bot_api.get_message_content(message_id)

    image = BytesIO(message_content.content)

    try:
        result = search_product(image)

        if isinstance(result, str):
            messages = [
                TextSendMessage(text=result),
                TextSendMessage(text='食品パッケージ/書籍/CD/DVD/ゲームソフト/PCソフトを検索できるよ!')
            ]

        elif isinstance(result, list):

            columns = [
                CarouselColumn(
                    thumbnail_image_url=column['thumbnail_image_url'],
                    title=column['title'],
                    text=column['text'],
                    actions=[
                        URITemplateAction(
                            label=column['actions']['label'],
                            uri=column['actions']['uri'],
                        )
                    ]
                )
                for column in result
            ]

            messages = TemplateSendMessage(
                alt_text='template',
                template=CarouselTemplate(columns=columns),
            )

        reply_message(event, messages)

    except Exception as e:
        print("error:", e)
        reply_message(event, TextSendMessage(text='エラーが発生しました'))


def reply_message(event, messages):
    line_bot_api.reply_message(
        event.reply_token,
        messages=messages,
    )


if __name__ == "__main__":
    port = os.environ.get('PORT', 3333)
    app.run(
        host='0.0.0.0',
        port=port,
    )

:star: 完成!!

  • 以上でLINEで画像を送ると商品検索するbotができました
  • 正直そんなに検索に引っかかりません(涙)

:star: 参考