#Rekognitionとは
画像内に存在する物体や表情、文字等をAPIを介して解析するサービス。
そんなRekognitionにも無料枠が存在するらしいので、何とか使ってみたい。
無料枠期間は1 か月あたり 5,000 枚の画像分析が可能との事。
多いんだか少ないんだか…
https://aws.amazon.com/jp/rekognition/pricing/
#雑な構成図
お仕事でこのような構成図を出したら即鉄拳が飛んできそう。
この構成図に沿ってServerlessApplicationModel、略してSAMの定義を書いていきます。
Lambdaの言語はPythonを使います。
Slack側のイベントはfile sharedで発火させます。
Slackアプリのセットアップに関しては省略。
#SAM
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Slack is app
Globals:
Function:
Runtime: python3.7
Timeout: 15
MemorySize: 256
Environment:
Variables:
Bucket_Name: <適切なS3バケットを指定してください>
Dynamo_Table: !Ref SlackDynamo
Oauth_Token: <Slack側の認証トークンを入れてください>
Slack_Api_Url : https://slack.com/api/
Webhook_url : <Slack側のWebHookURLを入れてください>
Resources:
Slackbobobo:
Type: AWS::Serverless::Function
Properties:
CodeUri: 'Slack_app/'
Role: <"適切なIAMロールを入れてください">
Handler: slack_bobobo.lambda_handler
Events:
Api:
Type: Api
Properties:
Path: /images
Method: post
SlackDynamo:
Type: AWS::Serverless::SimpleTable
Properties:
PrimaryKey:
Name: Imageid
Type: String
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
留意事項としては、まずIAMロールですがRekognition,S3,Lambda,DynamoDBに書き込んだり読み込んだりするポリシーをアタッチする必要があります。私はちまちまポリシー用のJSON作るのが面倒なので雑にFullAccess付けました。それと、Timeoutの値ですが、結構処理に時間掛かるのでデフォルトの3秒のままだと
こんなエラーが出ます。
それと、今回LambdaのAPIの通信に外部ライブラリのrequestsを利用しますので、依存モジュール共々pyファイルの同階層に入れておきます。
そして、下記のコマンドでスタックをデプロイします。
aws cloudformation package \
--template-file template.yaml \
--s3-bucket <"パッケージ格納用S3バケット"> \
--output-template-file packaged-template.yaml
aws cloudformation deploy \
--template-file packaged-template.yaml \
--stack-name <"適当なスタック名を入れてください"> \
--capabilities CAPABILITY_IAM
#Lambda全ソース
#coding:utf-8
import boto3
import json
import os
import logging
import decimal
import requests
from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key
from datetime import datetime, date, timedelta
#ロギング
logger = logging.getLogger()
logger.setLevel(logging.INFO)
dynamodb = boto3.resource('dynamodb',region_name = 'ap-northeast-1')
trgt_table = dynamodb.Table(os.getenv('Dynamo_Table'))
#SlackApiにイベントの種類に応じてGetを投げる
def get_slack_api(base_url,token,file_id):
headers = {'Authorization': 'Bearer {}'.format(token)}
response = requests.get(base_url+'files.info'+'?file='+file_id,headers=headers)
return json.loads(response.text)
#画像ダウンロード
def get_image(image_url,token):
headers = {'Authorization': 'Bearer {}'.format(token)}
image = requests.get(image_url,headers=headers)
return image.content
#S3にアップロード
def image_put_s3(image,key,mimetype,bucket_name):
s3 = boto3.resource('s3',region_name = 'ap-northeast-1').Bucket(bucket_name)
s3_obj = s3.Object(key)
s3_obj.put(
Body=image,
StorageClass='STANDARD',
ContentType=mimetype
)
return
#Slackに投稿
def Slack_post(webhook_url,label,text,key):
message ='S3に'+key+'をアップロードしました。\n'
message +='解析結果\n'
message +='\n物体検出\n'
#物体検出
for lb_n in label:
message +=lb_n['Name']+':'+str(round(float(lb_n['Confidence']),1))+'%\n'
message +='\nテキスト検出\n'
#テキスト検出
for tex_n in text:
message +=tex_n['DetectedText']+':'+str(round(float(tex_n['Confidence']),1))+'%\n'
item= { 'text': message }
headers = {'Content-type': 'application/json'}
try:
requests.post(webhook_url,json=item,headers=headers)
except Exception as e:
logging.info("type:%s", type(e))
logging.error(e)
return
#rekogniton解析したやつをDynamoに投げる
def rekogniton_image(bucket_name,key):
rekogniton = boto3.client('rekognition',region_name ='ap-northeast-1')
#boto3の公式リファレンス
#https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rekognition.html
reko_label = rekogniton.detect_labels(Image={'S3Object':{'Bucket':bucket_name,'Name':key}},MinConfidence=75)['Labels']
reko_text = rekogniton.detect_text(Image={'S3Object':{'Bucket':bucket_name,'Name':key}})['TextDetections']
#DynamoDBはFloat型をサポートしていない為、Decimal型に変換する必要がある
item={
'Imageid':key,
'timestamp':int(datetime.utcnow().timestamp()),
'label':json.loads(json.dumps(reko_label),parse_float=decimal.Decimal),
'text':json.loads(json.dumps(reko_text),parse_float=decimal.Decimal)
}
res = trgt_table.put_item(Item=item)
if(res):
Slack_post(os.getenv('Webhook_url'),item['label'],item['text'],key)
return
def lambda_handler(event,context):
#イベント設定用
body = json.loads(event['body'])
if "challenge" in body:
response = {
'statusCode':'200',
'body':body,
'headers':{'Content-Type':'application/json'}
}
return response
else:
try:
#DynamoDB存在判定
res_get_item = trgt_table.get_item(Key={'Imageid':body['event']['file_id']})
if 'Item' not in res_get_item:
file_res = get_slack_api(os.getenv('Slack_Api_Url'),os.getenv('Oauth_Token'),body['event']['file_id'])
else:
raise Exception("Already Exist Same Imageid!")
except Exception as e:
logging.info("type:%s", type(e))
logging.error(e)
else:
try:
image = get_image(file_res['file']['url_private'],os.getenv('Oauth_Token'))
except Exception as e:
logging.info("type:%s", type(e))
logging.error(e)
else:
try:
s3_key = file_res['file']['id'] + '.' + file_res['file']['mimetype'].split('/')[1]
image_put_s3(image,s3_key,file_res['file']['mimetype'],os.getenv('Bucket_Name'))
except ClientError as e:
logging.info(e.response['Error']['Message'])
logging.error(e)
else:
try:
rekogniton_image(os.getenv('Bucket_Name'),s3_key)
except ClientError as e:
logging.info(e.response['Error']['Message'])
logging.error(e)
except Exception as e:
logging.info("type:%s", type(e))
logging.error(e)
else:
response = {
'statusCode':'200',
'body':'OK',
'headers':{'Content-Type':'application/json'}
}
return response
ご覧の通りS3のオブジェクトキーは、Slackにアップロードした際のfile_idをそのまま流用しています。
Botのメッセージもおかげで珍妙な感じに。
#実行
投稿します。
返ってきます。
単語の後ろにある謎の数字はConfidence値、つまり信頼度?です
しかし、HumanもPersonもどこにもいない筈なのに自信満々にこの結果をお出しされます。
テキストの検出は及第点といったところでしょうか。DEVILのIは読み込めてませんが。
紛れもなく人間が描かれているのですが、デビルガンダムより控えめな結果が返ってきました。
テキストは…肝心の題名、作者名が解析されていません。日本語に対応していないという事なのでしょうか。
総じてかなり微妙な感じになりました。
画像を投稿したと同時に何故か複数のAPIが発射されている事が判明します。
つまり…
これはひどい…
S3のPutアクションの単価はかなり割高で、折角の無料枠もたちまち擦り潰されてしまいます。
Dynamoの存在判定も同時にコンテナが起動している為、意味を成しません。
そこで、
Lambdaの同時実行数を弄る事で、多少は多重解析が緩和されます。
しかし、あくまで多少であって完全に無くなる事はありませんでした。
SlackイベントApiの仕様とかなんでしょうか。
#総括
Rekognitionの力を引き出せませんでした。
ラズパイに音波センサーと監視カメラ取り付けて、閾値を超えたらS3に画像アップロードしてRekognitionで人間の存在判定する、みたいな使い方がいいのでしょうか。