Pythonで写真をアスペクト比4:3にトリミングする

前回、Excelに16:9の写真を4:3にトリミングしつつセルに合わせて貼り付けるマクロを作りました。今回はそのトリミング処理をPythonでやってみます。前回記事はこちら。

Excelの写真台帳に貼り付ける前処理として実行する想定で、写真がたくさん入ったフォルダをドラッグ&ドロップすると、その中の写真ファイルを全部4:3にトリミングして、ついでにスマホのクソデカサイズ写真を適当な大きさまでリサイズするようにします。

探せばそういうアプリがありそうな気がしますが、あえて今回のコードを書いたのは、これを発展させてあることを目論んでいるからです。詳しくは最後にお話しします。

16:9の画像を4:3にトリミングするPythonスクリプト

さっそくソースコードです。画像処理には超有名どころPillowを使います。

import os
import sys
from PIL import Image, UnidentifiedImageError

WH_RATIO = 4 / 3
RESIZE_W = 1280


def main(argv):
    for tgt in argv[1:]:
        if os.path.isdir(tgt):
            for f in os.listdir(tgt):
                tp = os.path.join(tgt, f)
                if not os.path.isdir(tp):
                    edit_image(tp)
        else:
            edit_image(tgt)


def edit_image(fp):
    try:
        img = Image.open(fp)
        img_ratio = img.width / img.height

        if img_ratio > WH_RATIO:
            crop_pixel = (img.width - img.height * WH_RATIO) / 2
            left = int(crop_pixel)
            right = img.width - int(crop_pixel)
            upper = 0
            lower = img.height

            img = img.crop((left, upper, right, lower))

        if img.width > RESIZE_W:
            img = img.resize((RESIZE_W, int(RESIZE_W * img.height / img.width)))

        dst_dir = os.path.join(os.path.dirname(fp), 'trim')

        if not os.path.exists(dst_dir):
            os.mkdir(dst_dir)

        img.save(os.path.join(dst_dir, os.path.basename(fp)))

    except UnidentifiedImageError:
        pass
    except FileNotFoundError:
        pass


if __name__ == '__main__':
    main(sys.argv)

トリミング後のアス比を4:3ではなく3:2にしたい場合は定数WH_RATIOを3/2にします。リサイズは横幅基準で実行され、定数RESIZE_Wより大きい場合、RESIZE_Wまでリサイズされます。

ファイルまたはフォルダを複数同時にドラッグ&ドロップすることで、それらを全部まとめて一度に処理できます。ただしフォルダ内にあるフォルダは対象外です。

うごかしてみる

次のような写真を保存したフォルダがあります。

スマホで撮影した1920×1080のアス比16:9のファイルです。検証のために追加で1:1と4:3の画像ファイルも入れました。下の方にあるやつです。

このフォルダをスクリプトにドラッグ&ドロップすると、フォルダ内にtrimフォルダが作成されます。

中身はフォルダ内の画像ファイルを4:3の横幅1280pxにトリミング&リサイズしたものです。

はなから4:3の画像や、4:3より小さい比率の画像はリサイズだけが適用されます。

解説

基本はVBA版をPythonに翻訳しただけになっております。

Python版では対象のファイルを単体、複数、フォルダごと、と違ったパターンで受け取ります。ドラッグ&ドロップされたファイルはsys.argvでパスがリストでわたってきます。すべてのパターンに対応するためisdirlistdirを駆使してargvを回します。

sys.argv[0]はドラッグ&ドロップされたファイルとは無関係の値が入っているのでスライスで1から取り出すようにしています。

目標とするアスペクト比から必要なトリミング幅を計算してPIL.Image.cropメソッドでトリミングを実行します。

VBAではトリミングの幅をCrop~プロパティにPixelで指定することでトリミングされました。左右を切るだけであればCropLeftとCropRightだけでOKでした(下図)。

Pillowでは残したい範囲を左上を0とした座標で指定する必要があります。なので、VBAと同じのりでやると失敗します。必ず4点が必要です。左右を切るだけであればupperが0でlowerは画像の高さと同じにします(下図)。

リサイズはPIL.Image.resizeメソッドに縦横のPixel値をわたせば縮小してくれます。

cropもresizeも、メソッドを呼んだImageオブジェクトが変更されるのではなく、メソッドの処理が適用された別のオブジェクトをリターンします。メソッドを呼ぶだけではだめでリターンを変数で受け取る必要があります。つまり

Image.resize(1280, 960)

これでは何も起こらず

result = Image.resize(1280, 960)

こうして、以降resultを取り回していかなければなりません。

ここがすげー直感に反すると感じたのは私だけでしょうか。最初すこしはまりました。

最後に処理した画像を保存先のフォルダがなければ作成し、その中へ保存します。

【次回】写真内QRコードによる写真台帳自動作成

ということで、トリミング&リサイズは超簡単にできましたが、ここからさらに踏み込んでやりたいことがあります。それは撮影した写真にうつっているバーコードorQRコードから、なんの写真かを特定してExcelの写真台帳に自動的にはめ込んでいくシステムです。

やっつけに撮った次のような写真があります。

これを次のコードを使いpyzbarで処理すると

from pyzbar.pyzbar import decode
from PIL import Image

img = Image.open(r"D:\photo.jpg")

d = decode(img)
for r in d:
    print(r.data.decode('utf-8'))

画像内のQRコードの値(当サイトURL)が取り出せます。

この要領で写真に管理用QRコードをうつすようにすれば、そこから読み取った情報を使ってファイル名をつけたり、被写体を判定して保存フォルダを振り分けたり、Excel写真台帳の特定の位置に挿入したりできますね。

次回、いろいろ検証しながらやってみたいと思います。

つづく。