#Flask + LINE Messaging APIでの人工知能LINEボットの作り方
ナカノヒトシさんの書籍
Python + LINEで作る人工知能開発入門 - Flask + LINE Messaging APIでの人工知能LINEボットの作り方
まあまあ面白かった。
サンプル演習の最後はGoogle Vision APIを使った顔認識でスタンプで顔隠すボットアプリ。この手のアプリはたくさんあるがLINEボットにすると使う方はラクかも。
そして最後に顔認識を複数対応させてみようという宿題を出される。書籍のアプリでは1人のみ対応。回答はないです簡単です自力でがんばりましょうと厳しいナカノさん。
少し苦労して回答作ったので参考までに。
ユーザの送った画像をGoogle Vison APIで顔検出しcat.pngで隠した合成写真をリプライ(顔複数対応)
#app.py
# ユーザの送った画像をGoogle Vison APIで顔検出しcat.pngで隠した合成写真をリプライ(顔複数対応)
import io
import os
import base64
import json
import requests
from flask import Flask, request, abort
from PIL import Image #Pillowをインストール pip3 install pillow
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage, ImageMessage, ImageSendMessage
)
# LINEアクセストークンとアプリケーションシークレット
ACCESS_TOKEN = ''
SECRET = ''
# Google Vision APIキー
API_KEY = ''
app = Flask(__name__)
line_bot_api = LineBotApi(ACCESS_TOKEN)
handler = WebhookHandler(SECRET)
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/callback',methods=['POST'])
def callback():
signature = request.headers['X-Line-Signature']
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
try:
handler.handle(body, signature)
except InvalidSignatureError:
print("Invalid signature. Please check your channel access token/channel secret.")
abort(400)
return 'OK'
@handler.add(MessageEvent,message=ImageMessage)
def handle_message(event):
try:
message_content = line_bot_api.get_message_content(event.message.id)
# event.message.idを指定することで画像本体データを読み出せる
# message_content.content #取得した画像ファイル本体
image_base64 = base64.b64encode(message_content.content) #画像ファイルをbase64に変換
#リクエストボディを作成(json.dumps()でJSONに変換してる)
req_body = json.dumps({
'requests': [{
'image': {
'content': image_base64.decode('utf-8')
},
'features': [{
'type': 'FACE_DETECTION',
'maxResults': 20,
}]
}]
})
# Vison APIのエンドポイント↓
res = requests.post("https://vision.googleapis.com/v1/images:annotate?key=" + API_KEY, data=req_body)
#print('res内容は、' + res.text)
result = res.json()
vertices = result["responses"][0]["faceAnnotations"]
#print('vertices内容は、' + json.dumps(vertices))
## response内容は、レスポンス.jsonを参照.
if vertices:
print('取得できた')
image_base = Image.open(io.BytesIO(message_content.content))
for face in vertices:
corner = face["boundingPoly"]['vertices'][0]
print('cornerは、' + json.dumps(corner))
print('face["boundingPoly"]["vertices"][1]["x"]は、' + json.dumps(face["boundingPoly"]['vertices'][1]["x"]))
width = face["boundingPoly"]['vertices'][1]["x"] - face["boundingPoly"]['vertices'][0]["x"]
height = face["boundingPoly"]['vertices'][2]["y"] - face["boundingPoly"]['vertices'][1]["y"]
image_cover = Image.open('static/cat.png') # cat.pngはアルファチャンネル画像でないとダメ。ValueError: bad transparency maskエラー
image_cover = image_cover.resize((width,height))
image_base.paste(image_cover, (corner['x'],corner['y']), image_cover)
# Image.paste(im, box=None, mask=None)
print('forループおわり')
image_base.save('static/' + event.message.id + '.jpg')
line_bot_api.reply_message(
event.reply_token,
ImageSendMessage(
original_content_url = "https://hidden-savannah-xxxxx.herokuapp.com/static/" + event.message.id + ".jpg",
preview_image_url = "https://hidden-savannah-xxxxx.herokuapp.com/static/" + event.message.id + ".jpg"
)
)
except:
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text="顔認識できませんでした(動物ダメです人間だけです。横顔やアップすぎるのも厳しいです")
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
######参照:
念の為、80行目のprint('vertices内容は、' + json.dumps(vertices))
のレスポンス内容
[
{
"boundingPoly": {
"vertices": [
{
"x": 917,
"y": 318
},
{
"x": 1174,
"y": 318
},
{
"x": 1174,
"y": 616
},
{
"x": 917,
"y": 616
}
]
},
"fdBoundingPoly": {
"vertices": [
{
"x": 971,
"y": 396
},
{
"x": 1163,
"y": 396
},
{
"x": 1163,
"y": 588
},
{
"x": 971,
"y": 588
}
]
},
"landmarks": [
{
"type": "LEFT_EYE",
"position": {
"x": 1031.1968,
"y": 456.0161,
"z": 0.0003030986
}
},
{
"type": "RIGHT_EYE",
"position": {
"x": 1112.0862,
"y": 460.92987,
"z": 28.232975
}
},
{
"type": "LEFT_OF_LEFT_EYEBROW",
"position": {
"x": 1008.84607,
"y": 436.544,
"z": -1.8571037
}
},
{
"type": "RIGHT_OF_LEFT_EYEBROW",
"position": {
"x": 1060.1007,
"y": 440.86813,
"z": -7.585352
}
},
{
"type": "LEFT_OF_RIGHT_EYEBROW",
"position": {
"x": 1095.2485,
"y": 442.76245,
"z": 5.0468025
}
},
{
"type": "RIGHT_OF_RIGHT_EYEBROW",
"position": {
"x": 1131.141,
"y": 444.30832,
"z": 41.595203
}
},
{
"type": "MIDPOINT_BETWEEN_EYES",
"position": {
"x": 1075.8728,
"y": 455.9283,
"z": -1.5975293
}
},
{
"type": "NOSE_TIP",
"position": {
"x": 1080.8457,
"y": 504.33997,
"z": -20.247692
}
},
{
"type": "UPPER_LIP",
"position": {
"x": 1071.2343,
"y": 531.5437,
"z": -1.6211907
}
},
{
"type": "LOWER_LIP",
"position": {
"x": 1069.6505,
"y": 551.9242,
"z": 4.4038887
}
},
{
"type": "MOUTH_LEFT",
"position": {
"x": 1035.7985,
"y": 538.815,
"z": 8.222528
}
},
{
"type": "MOUTH_RIGHT",
"position": {
"x": 1101.0676,
"y": 541.8905,
"z": 30.981604
}
},
{
"type": "MOUTH_CENTER",
"position": {
"x": 1070.1655,
"y": 541.40643,
"z": 4.1978736
}
},
{
"type": "NOSE_BOTTOM_RIGHT",
"position": {
"x": 1092.8889,
"y": 510.94235,
"z": 16.238985
}
},
{
"type": "NOSE_BOTTOM_LEFT",
"position": {
"x": 1049.6199,
"y": 507.50146,
"z": 0.9902145
}
},
{
"type": "NOSE_BOTTOM_CENTER",
"position": {
"x": 1072.0765,
"y": 515.82806,
"z": -2.7877321
}
},
{
"type": "LEFT_EYE_TOP_BOUNDARY",
"position": {
"x": 1037.2472,
"y": 452.2355,
"z": -4.3320293
}
},
{
"type": "LEFT_EYE_RIGHT_CORNER",
"position": {
"x": 1047.4124,
"y": 459.2465,
"z": 6.317641
}
},
{
"type": "LEFT_EYE_BOTTOM_BOUNDARY",
"position": {
"x": 1030.3141,
"y": 461.9699,
"z": 0.34013578
}
},
{
"type": "LEFT_EYE_LEFT_CORNER",
"position": {
"x": 1018.07513,
"y": 455.93164,
"z": 2.3924496
}
},
{
"type": "LEFT_EYE_PUPIL",
"position": {
"x": 1034.6456,
"y": 457.22366,
"z": -1.4217875
}
},
{
"type": "RIGHT_EYE_TOP_BOUNDARY",
"position": {
"x": 1109.9236,
"y": 456.6617,
"z": 21.767094
}
},
{
"type": "RIGHT_EYE_RIGHT_CORNER",
"position": {
"x": 1119.8134,
"y": 462.12448,
"z": 38.996845
}
},
{
"type": "RIGHT_EYE_BOTTOM_BOUNDARY",
"position": {
"x": 1110.3936,
"y": 466.81308,
"z": 26.98832
}
},
{
"type": "RIGHT_EYE_LEFT_CORNER",
"position": {
"x": 1094.9646,
"y": 462.28857,
"z": 22.470396
}
},
{
"type": "RIGHT_EYE_PUPIL",
"position": {
"x": 1109.2263,
"y": 461.79114,
"z": 25.238665
}
},
{
"type": "LEFT_EYEBROW_UPPER_MIDPOINT",
"position": {
"x": 1037.4519,
"y": 429.95596,
"z": -10.386488
}
},
{
"type": "RIGHT_EYEBROW_UPPER_MIDPOINT",
"position": {
"x": 1116.0272,
"y": 434.71762,
"z": 18.003847
}
},
{
"type": "LEFT_EAR_TRAGION",
"position": {
"x": 954.1669,
"y": 484.3548,
"z": 76.21559
}
},
{
"type": "RIGHT_EAR_TRAGION",
"position": {
"x": 1119.6852,
"y": 494.08078,
"z": 135.9113
}
},
{
"type": "FOREHEAD_GLABELLA",
"position": {
"x": 1078.9543,
"y": 441.30212,
"z": -4.084726
}
},
{
"type": "CHIN_GNATHION",
"position": {
"x": 1062.5234,
"y": 589.9864,
"z": 16.94458
}
},
{
"type": "CHIN_LEFT_GONION",
"position": {
"x": 968.6994,
"y": 536.28186,
"z": 52.295383
}
},
{
"type": "CHIN_RIGHT_GONION",
"position": {
"x": 1117.5015,
"y": 545.4246,
"z": 105.74548
}
}
],
"rollAngle": 4.5907497,
"panAngle": 19.758451,
"tiltAngle": -3.1237326,
"detectionConfidence": 0.91960925,
"landmarkingConfidence": 0.5607769,
"joyLikelihood": "VERY_UNLIKELY",
"sorrowLikelihood": "VERY_UNLIKELY",
"angerLikelihood": "VERY_UNLIKELY",
"surpriseLikelihood": "VERY_UNLIKELY",
"underExposedLikelihood": "VERY_UNLIKELY",
"blurredLikelihood": "VERY_UNLIKELY",
"headwearLikelihood": "LIKELY"
},
・・・・・・・・・・以上でひとり分・・・・・・・・・・・・
]
書籍の中でうまく行かなかったところは以下の通り。 [Flaskアプリをherokuにデプロイ(苦苦々)](https://qiita.com/atomyah/items/91196f5fda95e4b9c7a6)
参考:[PythonでGoogle Cloud Visionを使った顔検出](https://vaaaaaanquish.hatenablog.com/?page=1471257396)