本エントリは、AWS #2 Advent Calendar 2018の12日目です。
こんにちは! 気付けばAcroquestに入社してもう10年目、最近はAWSを使った開発やDevOpsの活動に携わっているiidaです。
今回は、少し前に上司から「Amazon Rekognition Imageを使うことになるかも知れない」と言われて触ってみたら、意外なところで嵌った話をしたいと思います。
AWS Rekognitonとは
人工知能・機械学習に基づく画像認識・画像分析サービスです。こう書くと何やら難しそうですが、百聞は一見にしかず、AWSコンソールのデモを見てみましょう。
これは「オブジェクトとシーンの検出」で、画像中にどんなオブジェクト(CarやPersonなど)があるのか、あるいはどんなシーン(TransportationやSportsなど)なのかを検出し、その位置まで教えてくれるようです。
私は機械学習についてはほとんど知識がありませんが、それでもこのようは画像解析ができるようになるなんて、すごい世の中になったものですね!
Pythonから呼んでみる
さてさて、このようなサービスがあるということは分かりましたが、エンジニアとしてはやはり自分の書いたコードから使ってみたいものですよね!
ということで、早速PythonからRekognitionを呼び出すコードを書いてみました。
import json import boto3 # ローカルの画像ファイルを読み込む。 with open('sample.jpeg', 'rb') as image_file: image_bytes = image_file.read() # Rekognitionのラベル検出を呼び出す。 rekognition = boto3.client('rekognition', 'ap-northeast-1') response = rekognition.detect_labels(Image={'Bytes': image_bytes}) print(json.dumps(response, indent=2))
たったこれだけ。簡単!
試しにこの画像を解析してみましょう。先日のHappy360(全体査定)の時の一コマです。どんなレスポンスが返って来たのかというと…。
{ "Labels": [ { "Name": "Human", "Confidence": 99.71063232421875, "Instances": [], "Parents": [] }, { "Name": "Person", "Confidence": 99.71063232421875, "Instances": [ { "BoundingBox": { "Width": 0.31008321046829224, "Height": 0.6783191561698914, "Left": 0.626418948173523, "Top": 0.31440863013267517 }, "Confidence": 99.71063232421875 }, :(以下省略)
なるほど、Nameがオブジェクトやシーンの種類で、BoundingBoxが画像中の位置のようです。HumanとPersonの違いがよく分かりませんが、Humanは「人が写っている」ということを表しているのでしょうか?
結果を画像に描画してみる
これだけだと分かりづらいので、デモみたいにラベルを画像に描画してみましょう。
import cv2 # (ここに先ほどのコードが入ります。) # CV2で画像ファイルを読み込む。 np_image = cv2.imread('sample.jpeg') height, width = np_image.shape[:2] # ラベルの中から人と思われるものを探して四角で囲う。 for label in response['Labels']: if label['Name'] not in ['People', 'Person', 'Human']: continue for person in label['Instances']: box = person['BoundingBox'] x = round(width * box['Left']) y = round(height * box['Top']) w = round(width * box['Width']) h = round(height * box['Height']) cv2.rectangle(np_image, (x, y), (x + w, y + h), (255, 255, 255), 3) cv2.putText(np_image, label['Name'], (x, y - 9), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3) cv2.imwrite('./sample_result.jpeg', np_image)
BoundingBoxの値は、画像の幅と高さに対する割合となっているので、少し計算が必要です。これを実行してみると…。
Great! 手前の2人だけでなく、奥にひっそりといる人もちゃんと検出されていますね!
Lambdaから実行してみる
ここまで来たら、Lambda上でも動かしてみたくなりますよね!(えっ、ならないですか?)
S3にアップロードされた画像ファイルを自動で解析するLambda関数を書いてみました。
import os import boto3 import cv2 s3 = boto3.resource('s3') def handle_request(event, content): # S3にアップされた画像の情報を取得する。 bucket_name = event['Records'][0]['s3']['bucket']['name'] object_key = event['Records'][0]['s3']['object']['key'] file_name = os.path.basename(object_key) # 画像をRekognitionで解析する。 rekognition = boto3.client('rekognition') response = rekognition.detect_labels(Image={ 'S3Object': { 'Bucket': bucket_name, 'Name': object_key } }) # S3から画像をダウンロードする。 tmp_dir = os.getenv('TMP_DIR', '/tmp/') bucket = s3.Bucket(bucket_name) bucket.download_file(object_key, tmp_dir + file_name) # 検出した人物に枠を描画する。 image = cv2.imread(tmp_dir + file_name) height, width = image.shape[:2] for label in response['Labels']: if label['Name'] not in ['People', 'Person', 'Human']: continue for person in label['Instances']: box = person['BoundingBox'] x = round(width * box['Left']) y = round(height * box['Top']) w = round(width * box['Width']) h = round(height * box['Height']) cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), 3) cv2.putText(image, label['Name'], (x, y - 4), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3) cv2.imwrite(tmp_dir + file_name, image) # S3に描画後の画像をアップロードする。 bucket.upload_file(tmp_dir + file_name, 'result/' + file_name) os.remove(tmp_dir + file_name)
ちなみに、CV2(opencv-python)は非Pure Pythonなので、Amazon Linux上でビルドまたはpip installしたものをデプロイパッケージに含めるか、Serverless Framworkを使用する場合はServerless Python RequirementsプラグインでdockerizePipを有効にする必要があります(ここでも少し嵌った)。
デプロイパッケージが50MB近くになってしまいましたが、無事デプロイが完了したので、早速S3バケットに画像ファイルをアップロードしてみます。すると…。
'Instances': KeyError Traceback (most recent call last): File "/var/task/person_detector.py", line 44, in handle_request for person in label['Instances']: KeyError: 'Instances'
あれ? ラベルにInstancesが無いって怒られてしまいました。ローカルで動かした時にはこんなエラーは出なかったのですが、そんなこともあるんですかね?
if label['Name'] not in ['People', 'Person', 'Human'] or 'Instances' not in label: continue
突貫ですが、処理するラベルの条件式をこんな風に変えてみました。
すると、出力フォルダに画像はできたのですが、ローカルでは描画されていたラベルがありません。何故だ?!
response = rekognition.detect_labels(Image={'Bytes': image_bytes}) print(json.dumps(response, indent=2))
こうなったらログ出力しかありません。Rekognitionからのレスポンスを表示してみると…。
{ "Labels": [ { "Name": "Person", "Confidence": 99.57161712646484 }, { "Name": "Human", "Confidence": 99.57161712646484 }, :(以下省略)
なんと、ローカルで実行した時よりも取得できる情報が少ないではありませんか!
こんなことが起こり得るのかと思って調べてみたところ、どうやら古いバージョンのBoto3だとこれらの情報しか取得できないようです。あれ、そう言えばLambdaではデフォルトでBoto3が使えますが、あれってもしかして…。
これだ!
確かに言われてみれば当たり前ですが、今まで意識したことがなかったので盲点でした。バージョンによって関数の有無があるのは想像できますが、レスポンスが異なるなんてこともあるんですね。
その後、最新のboto3とbotocore、そしてurllib3もデプロイパッケージに含めたところ、ローカルと同じくラベルの描画された画像が出力されました。(デプロイパッケージが50MBを超えてしまいましたが。。)
Amazon Rekognitionだけでなく、普段使っているAWS Lambdaについても理解を深められ、とても良い勉強になったと思います。LambdaからRekognitionを使おうと思っている方はお気を付け下さい! 以上、AWS #2 Advent Calendar 2018の12日目でした!
Acroquest Technologyでは、キャリア採用を行っています。
- ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
- Elasticsearch等を使ったデータ収集/分析/可視化
- マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
- 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
ユーザに最高の検索体験を提供したいエンジニアWanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com