初めに
こんにちは株式会社BFT新人エンジニアのないとうです。
今回はAWS Lambdaを使って画像からEXIF情報を取得する方法を紹介したいと思います。
やりたいことは、
①Amazon S3にjpeg画像がアップロードされたら関数を起動する
②jpeg画像からEXIFデータを取得する
③EXIFデータをAmazon DynamoDBに挿入する
④画像からEXIFデータを削除してリサイズしてからAmazon S3に保存する
以上の4つです。
- 初めに
- 前提条件
- ①S3にjpeg画像がアップロードされたら関数を起動する
- ②jpeg画像からEXIFデータを取得する
- ③EXIFデータをAmazon DynamoDBに挿入する
- ④画像からEXIFデータを削除してリサイズしてからAmazon S3に保存する
- 終わりに
- おまけ(コメントなしの全プログラム)
- 参照
前提条件
・今回使うS3のバケットとDynamoDBのテーブルがそれぞれ作成されていること
・Lambdaにアタッチするロールが作成可能であること
・Dockerが作業PCにインストールされていること
①S3にjpeg画像がアップロードされたら関数を起動する
Lambda関数の作成
1.Lambdaのコンソールから「関数の作成」をクリック

2.「1から作成」を選択、関数名を付け、ランタイムは「Python3.7」を選択し関数を作成する

実行ロールの設定
1.以下のような権限を持ったロールを作成する。
作成時信頼関係にLambdaを入れることを忘れないようにすること
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "image",
"Action": [
"s3:*Object*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::<バケット名>/*"
]
},
{
"Sid": "ListObjectsInBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::<バケット名>"
},
{
"Sid": "ReadWriteTable",
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem"
],
"Resource": "<DynamoDBテーブルのarn>"
},
{
"Sid": "GetStreamRecords",
"Effect": "Allow",
"Action": "dynamodb:GetRecords",
"Resource": "<DynamoDBテーブルのarn>/stream/* "
},
{
"Sid": "WriteLogStreamsAndGroups",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Sid": "CreateLogGroup",
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "*"
}
]
}
2.Lambdaコンソールの「設定」内にある「アクセス権限」を選択し、実行ロール右上の「編集」をクリックする

3.一番下にある「既存のロール」の選択肢から先ほど作成したロールを選び保存をクリック

S3トリガーを設定
1.Lambdaコンソールの「設定」内にある「トリガー」を選択し「トリガーの追加」をクリック

2.トリガーの設定にある選択肢からS3を選択し、バケットを選択してから、サフィックスに「.jpg」を設定する

②jpeg画像からEXIFデータを取得する
pythonのライブラリを使用できるようにする
1.DockerにアマゾンLinuxイメージをインストールする
docker pull amazonlinux
2.DockerでPillowとpiexifをインストールしzip化する
詳しい方法についてはこちら
【AWS】Lambda上のPythonで外部ライブラリを使用する方法 - BFT名古屋 TECH BLOG
3.Dockerから作業PCにファイルを送る
docker ps ##DockerのIDを調べる docker cp DockerID:zipファイルのパス 作業PCのパス
4.zipファイルを展開する
プログラム作成
作業PCの展開したファイルの中に「lambda_function.py」を作成し以下の内容を記載する
今回は
#ライブラリインポート
from decimal import Decimal
import json
import urllib.parse
import boto3
import uuid
from PIL import Image
from fractions import Fraction
import piexif
print('Loading function')
#S3クライアント呼び出し
s3 = boto3.client('s3')
def lambda_handler(event, context):
#S3トリガのバケットとkey(画像ファイル名)を取得
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(
event['Records'][0]['s3']['object']['key'], encoding='utf-8')
try:
#画像をLambdaの一時保存場所に保存
s3.download_file(bucket, key, "/tmp/img.jpg")
#画像からサイズとEXIFデータを取得
img = Image.open("/tmp/img.jpg")
exif = img._getexif()
exif_table = {}
w,h=img.size
#EXIFデータから座標情報を取得
for id, value in exif.items():
if id == 34853:
exif_table[id] = value
Lat_F=float(exif_table[id][2][0])+(float(exif_table[id][2][1])/60)+(float(exif_table[id][2][2])/3600)
Lng_F=float(exif_table[id][4][0])+(float(exif_table[id][4][1])/60)+(float(exif_table[id][4][2])/3600)
Time=exif_table[id][29]
Lat=str(Lat_F)
Lng=str(Lng_F)
return 0
#何かエラーが発生したら以下を起動
except Exception as e:
print(e)
print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
raise e
③EXIFデータをAmazon DynamoDBに挿入する
1.作成した「lambda_function.py」のs3 = boto3.client('s3')の下に以下の内容を追加
s3 = boto3.client('s3')
#####以下の内容を追加#####
#dynamoDBクライアント呼び出し
dynamo = boto3.client('dynamodb')
2.for id, value in exif.items():ループの下に以下の内容を追加
for id, value in exif.items():
if id == 34853:
exif_table[id] = value
Lat_F=float(exif_table[id][2][0])+(float(exif_table[id][2][1])/60)+(float(exif_table[id][2][2])/3600)
Lng_F=float(exif_table[id][4][0])+(float(exif_table[id][4][1])/60)+(float(exif_table[id][4][2])/3600)
Time=exif_table[id][29]
Lat=str(Lat_F)
Lng=str(Lng_F)
#####以下の内容を追加#####
#DynamoDBに入れるデータの作成
item = {
'filename':{'S':key},
'time':{'S':Time},
'lat':{'N':Lat},
'lng':{'N':Lng}
}
#DynamoDBにデータ挿入
dynamo.put_item(TableName='テーブル名', Item=item)
④画像からEXIFデータを削除してリサイズしてからAmazon S3に保存する
プログラム作成
dynamo.put_item(TableName='テーブル名', Item=item)の下に以下内容を追加する
dynamo.put_item(TableName='テーブル名', Item=item)
#####以下の内容を追加#####
#画像からEXIF情報を削除
piexif.remove("/tmp/img.jpg")
#画像をリサイズ
upload_img=img.resize((w//5,h//5))
#画像をLambdaの一時保存場所に保存
upload_img.save("/tmp/upload.jpg")
#S3に画像を保存
s3.upload_file("/tmp/upload.jpg",bucket,key)
Lambdaにプログラムを保存
1.展開したファイルと「lambda_function.py」を一つのzipファイルに圧縮する
2.Lambdaコンソールの「アップロード元」を選択する

3.「アップロード」をクリックしファイルを選択してから「保存をクリック」

Lambda関数起動確認
適当なjpeg画像をS3にアップロードしてDynamoDBに情報が保存されているか確認する
終わりに
今回は画像からEXIF情報を取得するLambdaの作成方法について紹介しました。
取得した情報は日時と座標だけでしたがほかの情報も同様の方法で取得することができます。
注意として今回の方法は同じバケットに画像を保存しているので一回のアップロードで2回Lambdaが起動しています。
これは画像を別バケットに保存することで防げます。
それでは。
おまけ(コメントなしの全プログラム)
from decimal import Decimal
import json
import urllib.parse
import boto3
import uuid
from PIL import Image
from fractions import Fraction
import piexif
print('Loading function')
s3 = boto3.client('s3')
dynamo = boto3.client('dynamodb')
def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(
event['Records'][0]['s3']['object']['key'], encoding='utf-8')
Status=str(key.split("_")[0])
try:
s3.download_file(bucket, key, "/tmp/img.jpg")
img = Image.open("/tmp/img.jpg")
exif = img._getexif()
exif_table = {}
w,h=img.size
for id, value in exif.items():
if id == 34853:
exif_table[id] = value
Lat_F=float(exif_table[id][2][0])+(float(exif_table[id][2][1])/60)+(float(exif_table[id][2][2])/3600)
Lng_F=float(exif_table[id][4][0])+(float(exif_table[id][4][1])/60)+(float(exif_table[id][4][2])/3600)
Time=exif_table[id][29]
Lat=str(Lat_F)
Lng=str(Lng_F)
item = {
'filename':{'S':key},
'time':{'S':Time},
'lat':{'N':Lat},
'lng':{'N':Lng},
'status':{'S':Status},
'w':{'N':str(w/5)},
'h':{'N':str(h/5)},
}
dynamo.put_item(TableName='テーブル名', Item=item)
piexif.remove("/tmp/img.jpg")
upload_img=img.resize((w//5,h//5))
upload_img.save("/tmp/upload.jpg")
s3.upload_file("/tmp/upload.jpg",bucket,key)
return 0
except Exception as e:
print(e)
print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
raise e