Pillow(Pythonライブラリ)を使ってS3に格納された画像を圧縮してみる
はじめに
こんにちは。大阪オフィスの林です。
Pillow
という画像処理のPythonライブラリを使ってS3に格納された画像データを圧縮する処理を試す機会がありましたので内容をまとめておきたいと思います。
やってみた
ライブラリの準備
基本的な進め方としては下記のドキュメントを参考にしていきます。下記ドキュメントの例ではpandas
をインストールする方法ですが、流れや手順などはほぼ流用してPillow
の準備を進めてみたいと思います。
手順に従い、ライブラリインストールに使うpip
コマンドのバージョンが19.3.0
以降であることを確認します。
$ pip --version pip 22.2.2 from /home/hayashi/.local/lib/python3.9/site-packages/pip (python 3.9)
19.3.0
より古いバージョンの場合、次のコマンドを実行してアップグレードしておきます。
$ python3.9 -m pip install --upgrade pip
下記コマンドを実行し、Lambdaの実行環境に合わせたモジュールをインストールします。
$ pip install \ --platform manylinux2014_x86_64 \ --target=python/ \ --implementation cp \ --python 3.9 \ --only-binary=:all: --upgrade \ Pillow Collecting Pillow Using cached Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB) Installing collected packages: Pillow Successfully installed Pillow-9.2.0
ターゲットに指定したディレクトリにモジュールが格納されていることを確認します。
$ ll python/ total 20 drwxr-xr-x 5 xxxxx xxxxx 4096 Sep 16 09:42 ./ drwxr-xr-x 3 xxxxx xxxxx 4096 Sep 16 09:42 ../ drwxr-xr-x 3 xxxxx xxxxx 4096 Sep 16 09:42 PIL/ drwxr-xr-x 2 xxxxx xxxxx 4096 Sep 16 09:42 Pillow-9.2.0.dist-info/ drwxr-xr-x 2 xxxxx xxxxx 4096 Sep 16 09:42 Pillow.libs/
python/
ディレクトリのモジュール一式をZipで固めます。
$ zip -r ./my-deployment-package.zip ./python/
my-deployment-package.zip
が作成されたことを確認します。
-rw-r--r-- 1 xxxxx xxxxx 3484180 Sep 16 09:47 my-deployment-package.zip
Lambdaにアップロード
前述で作成したモジュールのZipをLambdaにアップロードするのですが、Lambda関数を作成してアップロードするとコードがGUI上で編集できませんでした。
色々と調べてみるとLambda Layerにライブラリを追加することで必要なライブラリを参照可能にしつつGUI上でもコードの編集が出来そうでしたので、今回の検証ではLambda Layerにライブラリ追加する方法で対応してみたいと思います。
インストール時に指定したアーキテクチャやランタイムを選択して作成します。
「カスタムレイヤー」を選択し、先ほど作成したレイヤーを選択し「追加」します。
今回、コードは下記のようなものを準備しました。
from PIL import Image import urllib.parse import boto3 import os TARGET_BUCKET_PATH = os.environ['TARGET_BUCKET_PATH'] QUALITY_IMAGE = int(os.environ['QUALITY_IMAGE']) s3 = boto3.client('s3') def lambda_handler(event, context): bucket = event['Records'][0]['s3']['bucket']['name'] source_key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8') destination_key = source_key source_file = u'/tmp/' + os.path.basename(source_key) destination_file = source_file file_paths = os.path.splitext(source_key) file_path = file_paths[0] file_extension = file_paths[1][1:] try: s3.download_file(Bucket=bucket, Key=source_key, Filename=source_file) img = Image.open(source_file, 'r') img.save(destination_file, file_extension, quality = QUALITY_IMAGE) s3.upload_file(Filename=destination_file, Bucket=TARGET_BUCKET_PATH, Key=destination_key) return source_key except Exception as e: print(e) raise e
環境変数として、圧縮後の画像を格納するS3バケットを指定するTARGET_BUCKET_PATH
と、圧縮のレベル/クオリティ(1~100)を指定するQUALITY_IMAGE
を用意します。
LambdaにアタッチされているIAMロールに付与されているポリシーは、デフォルトのポリシーに加え、S3に対するGetとPutの許可を追加しています。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject" ], "Resource": "*" } ] }
また、処理対象の画像のサイズなどにもよるかもしれないのですが「タイムアウト」や「メモリ」もデフォルトの値のままだと処理が完了しないケースがありますしたので、実際に試して頂く際は動作を見ながらこの辺りもチューニング頂ければと思います。今回の検証ではタイムアウトを1分とし、メモリを512MBとして検証を行いました。
動作確認
今回の動作検証ではS3のPutイベントをトリガーに、前述で準備したLambdaを起動させて、環境変数で指定したS3バケットに圧縮後の画像を格納したいと思います。
まずは、オリジナルの画像を格納するS3バケットでイベント通知の設定を行います。今回はプレフィックスとし「images/」フォルダ配下を対象に「PUT」のイベントが発生したらLambdaを起動させたいと思います。
送信先の設定で作成したLambdaを指定して保存しておきます。
「images/」フォルダにサンプルの画像データをアップロードします。
圧縮後の画像格納先に指定したS3バケットに画像データが入ってきました。
サイズを見るとオリジナルのデータサイズが「2.1MB」に対し、Pillow
での処理後はデータサイズが「397.3KB」に変わっているので見た目上は圧縮できてそうです。実際の画質がどの程度落ちるのかなどについては本エントリでは触れませんのでLambdaの環境変数に指定したQUALITY_IMAGE
をチューニング頂きながら実際に試して頂ければと思います。
まとめ
S3のイベントをトリガーに何かしらの処理を行うケースの中でも今回は「画像の圧縮」というケースに絞って1つの対応案を検証させていただきました。
この記事がどなたかの参考になりましたら幸いです。
以上、大阪オフィスの林がお送りしました!