今さらline botでdocomoAPIの画像認識を試してみたら意外に高性能だった


#概要
lineにてdocomo APIによる画像認識を行い、送った写真から製品を判断し想定される製品のAmazonサイトを紹介するbotです。

#結果
想定していたよりも画像認識技術が高性能でした。

このように商品のパッケージの写真を撮ってbotに送信する。

<img width="375" alt="IMG_3604.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/186776/008e814e-6d56-e548-8144-977ac71b59e6.png">

画像から推定される商品を３つ表示するようにしました。
最大で１０つの推定される商品の情報を取得可能です。

<img width="375" alt="IMG_3603.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/186776/21d67fe1-c9b1-d39e-a2ac-5bb745404b64.png">

docomoAPIによりその商品のamazon urlを取得できるため「Amazonで見る」をクリックするとそのページに飛べる機能を作成できます。

<img width="375" alt="IMG_3601.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/186776/d7339b38-7ae3-6b01-c347-cbe09259dec6.png">

以下の画像のように、写真の一部分に商品が写っている写真（スクショ等）でも商品の判定を行ってくれました。ただし、一番目の候補には別の商品が判定されました、、、

<img width="375" alt="IMG_3602.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/186776/8ddffe3b-10b5-c437-255a-3a404d49c0f9.png">

<img width="375" alt="IMG_3599.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/186776/91f1a181-a8b6-c825-1e0a-d14f8ea9ebb7.png">


商品が判定されてなかった場合は、「見つかりませんでした。」のメッセージが表示されるようにしました。
また、メッセージを送っても何も返事がないのは寂しいので、recruitAPIのTalkAPI機能を実装し、メッセージに答えてくれるようになっています。

#ソースコード
[Github](https://github.com/penpen0317/line_bot_docomoAPI-Imagerecognition-/)

#参考
linebotを作成する際に、以下のサイトがとても参考になりました。
[Django+HerokuでLINE Messaging APIのおそ松botを作るまで](https://qiita.com/yakan10/items/b7ad35c2cbba5db81462/)
[LINE Messaging APIとPythonを使ってChatbotを作ってみた](https://qiita.com/Kosuke-Szk/items/eea6457616b6180c82d3/)


#環境
アプリケーションフレームワークにはDjangoを使用し、herokuにてサーバーを立てました。
Os:macOS Sierra
Python:Python 3.6.1
Django:1.11.5

#作成方法
すでにline bot、docomoAPIの使い方や、herokuサーバーの構築方法はたくさんの記事があるため、
作成したview.pyのみ説明します。
全コードはgithubを参照ください。

```view.py

    # Reply text
def reply_text(reply_token, rep_meg):
    payload = {
        "replyToken": reply_token,
        "messages": [
            {
                "type": "text",
                "text": rep_meg
            }
        ]
    }

reply
    requests.post(REPLY_ENDPOINT, headers=HEADER, data=json.dumps(payload))  # to send data to LINE
    return rep_meg


    # Save image
def save_image(messegeid):
    message_content = lINE_BOT_API.get_message_content(messegeid)

    i = Image.open(BytesIO(message_content.content))
    filename = '/tmp/' + messegeid + '.jpg'
    i.save(filename)

    return filename

    # Get json
def get_json(filename):
    with open(filename, mode='rb') as f:
        result = requests.post(
            url=__build_url('recognize'),
            params={'APIKEY': docomoapikey, 'recog': 'product-all', 'numOfCandidates': 5},
            data=f,
            headers={'Content-Type': 'application/octet-stream'})
        result.raise_for_status()
        result = result.json()
    return result
```

reply_test・・・ユーザが送信したメッセージを元に、recruitAPIのtalk機能より
得られたメッセージを返します。request post を行う。

save_image・・・ユーザが送信した画像をサーバ側で取得します。

get_jshon・・・docomoAPIの画像認識機能より商品の情報(jshon)を取得します。

```view.py

    # Reply carousel
def post_carousel(reply_token,imageUrl,title,brand,releaseDate,maker,url,itmeName):
    payload = {
          "replyToken":reply_token,
          "messages":[
              {
                "type": "template",
                "altText": "商品結果",
                "template": {
                    "type": "carousel",
                    "columns": [

                        {
                          "thumbnailImageUrl": imageUrl[0],
                          "title": itmeName[0],
                          "text": "ブランド名：" + brand[0] + " メーカー名："+ maker[0] +" 発売日：" +releaseDate[0],
                          "actions": [

                              {
                                  "type": "uri",
                                  "label": "Amazonで見る",
                                  "uri": url[0]
                              }
                          ]
                        },
                        {
                          "thumbnailImageUrl": imageUrl[1],
                          "title": itmeName[1],
                          "text": "ブランド名：" + brand[1] + " メーカー名："+ maker[1] +" 発売日：" +releaseDate[1],
                          "actions": [

                              {
                                  "type": "uri",
                                  "label": "Amazonで見る",
                                  "uri": url[1]
                              }
                          ]
                        },
                        {
                          "thumbnailImageUrl": imageUrl[2],
                          "title": itmeName[2],
                          "text": "ブランド名：" + brand[2] + " メーカー名："+ maker[2] +" 発売日：" +releaseDate[2],
                          "actions": [

                              {
                                  "type": "uri",
                                  "label": "Amazonで見る",
                                  "uri": url[2]
                              }
                          ]
                        }
                      ]
                }
              }
            ]
    }
    req = requests.post(REPLY_ENDPOINT, headers=HEADER, data=json.dumps(payload))
    return title[0]
```

画像判定された商品の結果をTemplate Message機能を使って表示します。
表示する情報は、商品イメージ、商品名、ブランド名、メーカー名、発売日、amazonurlへのアクセスとしました。そのほかにも、重さ、サイズ、等を取得し表示できます。

```view.py

def callback(request):
    reply = ""
    request_json = json.loads(request.body.decode('utf-8'))  # to get json

    for e in request_json['events']:
        reply_token = e['replyToken']  # to get reply_token
        message_type = e['message']['type']  # to get type

        # reply for test
        if message_type == 'text':
            text = e['message']['text']  # to get message
            rep_meg = client.talk(text)["results"][0]["reply"]  # to get reply message by recuitTalk
            reply += reply_text(reply_token, rep_meg)

            # reply for image
        if message_type == 'image':
            messegeid = e['message']['id']  # to get messageID
            filename = save_image(messegeid)
            result = get_json(filename)

            if (result.get('candidates') != None):
              imageUrl,title,brand,releaseDate,maker,url,itemName =[],[],[],[],[],[],[]

              for i in range(0,3): # to get item info
                imageUrl.append(result['candidates'][i]['imageUrl'])
                title.append(result['candidates'][i]['sites'][0]['title'])
                brand.append(result['candidates'][i]['detail']['brand'])
                releaseDate.append(result['candidates'][i]['detail']['releaseDate'])
                maker.append(result['candidates'][0]['detail']['maker'])
                url.append(result['candidates'][i]['sites'][0]['url'])
                itemName.append(result['candidates'][i]['detail']['itemName'][0:29]) # to restrict length

              reply += post_carousel(reply_token,imageUrl,title,brand,releaseDate,maker,url,itemName)

            else:
                rep_meg = "お探しのものは見つかりませんでした。"
                reply += reply_text(reply_token, rep_meg)

    return HttpResponse(reply)  # for test
```

ユーザがテキストを送信したか画像を送信したか判定をし、それぞれにおいてrequest post
する処理です。

#まとめ
すでにたくさんのlinebotについて記事があり、一度自身で作成したいと思い作成に至った。docomoAPIの画像認識を用いて作成したところ、思っていた以上に性能が良かったため、それについて伝えたく記事を書いて見ました。