BFT名古屋 TECH BLOG

日々の業務で得た知識を所属するエンジニアたちがアウトプットしていきます。

【Lambda】【python】画像からEXIFデータを取得するLambdaを作成する

初めに

こんにちは株式会社BFT新人エンジニアのないとうです。
今回はAWS Lambdaを使って画像からEXIF情報を取得する方法を紹介したいと思います。
やりたいことは、
Amazon S3jpeg画像がアップロードされたら関数を起動する
jpeg画像からEXIFデータを取得する
EXIFデータをAmazon DynamoDBに挿入する
④画像からEXIFデータを削除してリサイズしてからAmazon S3に保存する
以上の4つです。

前提条件

・今回使うS3のバケットとDynamoDBのテーブルがそれぞれ作成されていること
・Lambdaにアタッチするロールが作成可能であること
・Dockerが作業PCにインストールされていること

①S3にjpeg画像がアップロードされたら関数を起動する

Lambda関数の作成

1.Lambdaのコンソールから「関数の作成」をクリック
f:id:bftnagoya:20210921172112p:plain
2.「1から作成」を選択、関数名を付け、ランタイムは「Python3.7」を選択し関数を作成する
f:id:bftnagoya:20210921172441p:plain

実行ロールの設定

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コンソールの「設定」内にある「アクセス権限」を選択し、実行ロール右上の「編集」をクリックする
f:id:bftnagoya:20210922160632p:plain
3.一番下にある「既存のロール」の選択肢から先ほど作成したロールを選び保存をクリック
f:id:bftnagoya:20210922161039p:plain

S3トリガーを設定

1.Lambdaコンソールの「設定」内にある「トリガー」を選択し「トリガーの追加」をクリック
f:id:bftnagoya:20210922162335p:plain
2.トリガーの設定にある選択肢からS3を選択し、バケットを選択してから、サフィックスに「.jpg」を設定する
f:id:bftnagoya:20210922163632p:plain

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コンソールの「アップロード元」を選択する
f:id:bftnagoya:20210927154707p:plain
3.「アップロード」をクリックしファイルを選択してから「保存をクリック」
f:id:bftnagoya:20210927155405p:plain

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

参照

qiita.com

tat-pytone.hatenablog.com

recipe.kc-cloud.jp