Pillow(Pythonライブラリ)を使ってS3に格納された画像を圧縮してみる

2022.09.16

はじめに

こんにちは。大阪オフィスの林です。

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にライブラリ追加する方法で対応してみたいと思います。

Lambdaのメニューから「レイヤー」を選択します。

インストール時に指定したアーキテクチャやランタイムを選択して作成します。

正常に作成されました。

次にLambda関数を作成します。

Lambda関数を作成後「レイヤーの追加」を選択します。

「カスタムレイヤー」を選択し、先ほど作成したレイヤーを選択し「追加」します。

レイヤーが追加されました。

今回、コードは下記のようなものを準備しました。

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つの対応案を検証させていただきました。
この記事がどなたかの参考になりましたら幸いです。

以上、大阪オフィスの林がお送りしました!