[Python] 自作の顔画像判別モデルを組み込んだLINE BOT を作ってみた

#＊＊きっかけ＊＊

https://blog.aidemy.net/entry/2017/12/17/214715

⬆︎こちらを参考に作った自作画像判別AIモデル をLINE BOTに組み込んでみたら面白いと思い作ってみました。


#＊＊ 苦労したこと ＊＊

「WEBアプリに自作AIモデルを載せている」
「LINEBOTに画像を送って外部API に読み込ませている」
ソースはあったものの、自作モデルを LINEBOTに載せているソースがなくmodelの扱いに苦労しました。（はじめの１回しか顔判別してくれなかった。）


#＊＊　作ったもの　＊＊

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Day42<br><br>やっと顔判別AI搭載のLINE Botが完成しました！！<br>塩顔しょうゆ顔ソース顔など教えてくれるやつで、制作期間は1週間程です。<br><br>三連休の旅行では友達の写真でやってすごく盛り上がったし、プログラミング始めて本当に良かった😁<a href="https://twitter.com/hashtag/100DaysOfCode?src=hash&amp;ref_src=twsrc%5Etfw">#100DaysOfCode</a> <a href="https://t.co/PIH67eXUKd">pic.twitter.com/PIH67eXUKd</a></p>&mdash; かえるるる🐸 (@kaeru_nantoka) <a href="https://twitter.com/kaeru_nantoka/status/1041705619705610240?ref_src=twsrc%5Etfw">2018年9月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


#** ソースコード **

```python:main.py

from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import ImageMessage, MessageEvent, TextMessage, TextSendMessage
import requests, json, os, io, cv2
from io import BytesIO
from PIL import Image
import numpy as np
from keras.models import load_model

app = Flask(__name__)

YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]

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

header = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + YOUR_CHANNEL_ACCESS_TOKEN
}

# model はグローバルで宣言し、初期化しておく
model = None

@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'

# テキストを受け取る部分
@handler.add(MessageEvent, message=TextMessage)
def handler_message(event):
     line_bot_api.reply_message(
         event.reply_token,
         TextSendMessage(text=event.message.text))

# オウム返しする部分。おまけ。
def reply_message(event, messages):
    line_bot_api.reply_message(
        event.reply_token,
        messages=messages,
    )

# 画像を受け取る部分
@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    print("handle_image:", event)

    message_id = event.message.id
    getImageLine(message_id)

    try:
        image_text = get_text_by_ms(image_url=getImageLine(message_id))

        messages = [
            TextSendMessage(text=image_text),
        ]

        reply_message(event, messages)

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

def getImageLine(id):

    line_url = 'https://api.line.me/v2/bot/message/' + id + '/content/'

    # 画像の取得
    result = requests.get(line_url, headers=header)
    print(result)

    # 画像の保存
    im = Image.open(BytesIO(result.content))
    filename = '/tmp/' + id + '.jpg'
    print(filename)
    im.save(filename)

    return filename


def get_text_by_ms(image_url):

    # 90行目で保存した url から画像を書き出す。
    image = cv2.imread(image_url)
    if image is None:
        print("Not open")
    b,g,r = cv2.split(image)
    image = cv2.merge([r,g,b])
    img = cv2.resize(image,(64,64))
    img=np.expand_dims(img,axis=0)
    face = detect_who(img=img)

    text = face
    return text

def detect_who(img):

    face=""
    # グローバル変数を取得する
    global model

    # 一番初めだけ model をロードしたい
    if model is None:
        model = load_model('./shiogao_model2.h5')

    predict = model.predict(img)
    faceNumLabel=np.argmax(predict)

    if faceNumLabel == 0:
        face = "オリーブオイル顔"
    elif faceNumLabel == 1:
        face = "塩顔"
    elif faceNumLabel == 2:
        face = "しょうゆ顔"
    elif faceNumLabel == 3:
        face = "ソース顔"
    return face

if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)

```

#＊＊　注意点等　＊＊

I )
・model = None と初期化するところ
・if model is None:
    model = load_model('./モデル名')
としているところ

ローカル環境の JupyterNotebook 上では起こらなかったんですが、
２回目以降の処理で、load_model が読み込まれないということがありました。

よって モデルを格納するグローバル変数 model を関数の外で宣言しておき、最初の処理だけ load_model を呼ぶ形にしました。

II )
・image = cv2.imread(image_url)

この行もローカル環境では、単に引数に画像のpathを通すだけなので問題ないところです。
しかし、実際にLINEBOT に組み込もうと思った時、LINEで投下した画像の path とはなんぞや
という問題に当たりました。
上記の getImageLine という関数は、送られてきたコンテンツ（今回は画像）の id を受け取って、画像の書き起こし〜保存をしてくれています。filename という変数に path を格納しました。
つまり、私たちが投下している画像は、 /tmp/ というところにPOSTしたものは保存されるらしいです。

#＊＊　参考にしたサイトなど　＊＊

以下のサイトは本当にわかりやすくてとても勉強になりました。
ありがとうございました。

・LINEで画像を受け取ってAPIに渡してテキストを取得する処理が分かりやすかった。
[@mikan3rd さん](https://qiita.com/mikan3rd/items/f0b6ff57dbcedc79a22d)

・LINE API でPOSTした画像がどのように内部で処理されているかが分かりやすく記述してある。
[AI人工知能テクノロジー さん](http://newtechnologylifestyle.net/docomolinebot/)

・ローカルで 画像判別モデル の作成について分かりやすく記述してある。
[Aidemy Blog さん](https://blog.aidemy.net/entry/2017/12/17/214715)