【Shopifyストア運用支援】画像サイズの一括変換(後編)
前回は Python の対話モードで PIL.Image.resize および PIL.Image.thumbnail を使って画像の表示サイズを変更する方法を記事にしましたが、今回は複数のファイルを一気に変更できるようにします。また、Python スクリプトを再利用できるように、Python の対話モードではなく、Microsoft Visual Studio Code(以下、VSCODE)でプログラミングします。
画像ファイルのパスを確認する
まずは画像ファイルがどんな風に整理されているか見てみましょう。
コマンドプロンプトで「dir 画像フォルダ名 /b /s」と実行すると、指定したフォルダ内のファイルを、サブフォルダを含めて一覧表示することができます。(キレイに整理されていますね。)エクスプローラーの検索機能で「*」や「*.*」を検索しても同様の結果が得られます。
画像リサイズプログラムの仕様を整理する
それでは、プログラミングの前に少しプログラムの仕様を整理してみましょう。仕様といっても堅苦しく考える必要はありません。プログラムへのインプットとアウトプット、そして処理内容を整理するくらいの感覚で構いません。
画像リサイズプログラムのインプット
プログラムには画像ファイルの場所(パス)を教えてあげる必要があります。しかし、大量の画像ファイルを1つずつ指定するのは大変なので、画像ファイルが保存されているフォルダを指定したら、その中の画像ファイルをすべて対象にするというのが良さそうです。
そして、画像の表示サイズを指定できるようにしたいですね。アスペクト比(縦横比)は元の画像と変えたくないので、幅、または高さのどちらかが指定されたら、他方を計算して求めるという考え方にしたいと思います。今回は、幅を指定して高さを自動計算するようにしましょう。
画像リサイズプログラムのアウトプット
出力は画像ファイルを保存するだけですが、ファイルを上書きするか、別の名前で保存するか、あるいは別のフォルダに保存するかを考える必要があります。オプションで切り替える方法も考えられますが、今回はファイル名の先頭に「resized_」や「thumbnail_」を付けて別名で保存することにしましょう。
画像リサイズプログラムのプロセス(処理内容)
これは以前の記事で、コマンドプロンプトでやったように画像の表示サイズをリサイズし、サムネイルを作成するだけですね。しかし、サムネイルは予め用意されている場合もありますし、1商品につき 10枚の画像が用意されているような場合に 10枚のサムネイルを作っても意味がありません。そう考えると、サムネイルを同時に作るかどうかをオプションで切り替えられるようにしたほうが良さそうです。プログラムへの入力の1つとして、サムネイルを作るオプションが増えました。サムネイルは、元の画像のアスペクト比を維持したまま、指定した幅×高さに最大限フィットする形に生成されますので、1つのピクセル数のみを指定するようにします。
仕様を整理すると下表のようになります。
インプット |
プロセス |
アウトプット |
|
|
|
画像リサイズプログラムをプログラミングする
さて、概ね仕様が決まったのでさっそくプログラミングしていきましょう。
VSCODEの準備
作業用フォルダを開く
まずは Microsoft Visual Code(以下、VSCODE)を起動します。そして、 Ctrl+K 、 Ctrl+O で「フォルダを開く」ウィンドウが開くので、ここで作業用フォルダを選びます。作業用フォルダが無いときは、「フォルダを開く」ウィンドウで「新しいフォルダ」をクリックする等の方法で作成してください。
新規Pythonファイルを開く
続いて Ctrl+N で新しいファイルを開きます。新しいファイルを開くと「言語の選択」と表示されますので、それをクリックして「言語モードの選択」のテキストボックスに pythonと入力、 Enterキーを押します。これで VSCODE のサジェストやコードチェックの機能が働くようになります。
VSCODEのバージョン違いなどで「言語の選択」が表示されない場合は、空ファイルのままで適当な Pythonファイル名(ファイルの拡張子が「.py」)を付けて保存することで言語モードの選択と同様になります。
画像リサイズプログラムのコーディング
必要なパッケージをインポートする
今回使うパッケージ、os、sys、argparse、そして Pillow(PIL.Image)をインポートします。各パッケージの概要は下表のとおりです。(Pillowについては前回記事をご覧ください)
OS |
Windows10 や macOS 11 等のオペレーティングシステムに依存する機能を利用する汎用的なパッケージです。今回はパス関連の処理を行うため os.path モジュールを使用します。 |
sys |
Python のインタプリタが管理する変数やインタプリタの動作に関連する関数を提供するパッケージです。 Python コードの標準入力を sys.stdin 、標準出力を sys.stdout 、標準エラー出力を sys.stderr と表します。コマンドプロンプトで Python コードを実行したとき、標準入力、標準出力、標準エラー出力はいずれもコマンドプロンプトの画面です。 |
argparse |
Python のプログラムをコマンドプロンプトで実行する際のオプションを簡単に定義でき、またその定義に則って、ヘルプや使用方法のメッセージを自動的に生成してくれます。 |
コマンドラインオプション(プログラムのインプット)を定義する
続いて argparse でコマンドラインのオプションについて定義していきます。まず、ArgumentParser クラスをインスタンス化します。クラスとはオブジェクトの定義で、それを実体化することをインスタンス化と言います。例えば「人間」という「名前」や「性別」、「生年月日」という属性(プロパティ)や「笑う」、「泣く」、「怒る」という行動(メソッド)を持つクラスを「田中太郎」や「佐藤花子」というインスタンスにすることで、田中太郎の生年月日を取得したり、佐藤花子が笑う動作を実行するということができるようになる、そのようなイメージです。
インスタンス p に add_argument でオプションを定義していきます。
add_argument の第一引数はオプションの名称を表す文字列です。この文字列にハイフンを含まないときは必須オプションになります。「help」パラメータは、下図のようにコマンドラインでヘルプを要求したときに表示されるメッセージを指定します。
ハイフンと 1文字、およびハイフン 2つと 1単語で定義されるオプションは、コマンドラインのスイッチと呼ばれる任意のオプションを示します。Windows の標準コマンドのスイッチはスラッシュで指定しますが、 argparse は Linux コマンドのスイッチの記法に準じていて、ハイフンで指定します。なお、任意のオプションといいましたが、上図の「-w」、「--width」オプションのように、 add_argument のパラメータに「required=True」を指定することで必須のオプションとすることも可能です。また「type=int」とデータの型を指定することで、異なるデータ型を指定されたときは自動的にエラー処理されるようになり、自分でエラー処理をコーディングする必要がなくなります。
インスタンス p の parse_args() 関数で実際にコマンドラインに指定された引数・オプションを取得します。前述のヘルプ表示やデータ型の自動チェックはこの parse_args() で実行されます。引数無しで実行したときや、「-w」パラメータに数字以外を渡したときのエラーは下図のようにエラー表示されます。
argparse についての詳細は下記の URL を参照してください。
https://docs.python.org/ja/3/howto/argparse.html
コマンドラインオプションのエラー処理
指定された画像フォルダが正しいかどうかを調べるため os.path.isdir に args.dir を渡し、結果が False のときエラーメッセージを表示して exit で終了します。 print に「file」パラメータを指定しない場合は標準出力(sys.stdout)に出力されますが、ここではあえて標準エラー出力(sys.stderr)に出力するようにしています。
指定された画像フォルダを再帰的に走査する
os.walk にフォルダを指定すると、ディレクトリツリーを表す、ディレクトリ、サブディレクトリ、ファイルのタプルを生成(yield)することができます。 yield とは、一般的なプログラミング言語の関数で、関数の結果をすべて一括で返す return 命令とは違い、イテラブル(繰り返し処理可能な)なオブジェクトを yield 命令で順次返す処理をいいます。もし、 os.walk が指定されたディレクトリ内のサブディレクトリ、ファイルをすべて一括で返す関数だとしたら膨大なメモリを消費する可能性があります。しかし os.walk は yield される関数なので、これを for 文でループすると、(カレントディレクトリ、サブディレクトリのリスト、カレントディレクトリ直下のファイルのリスト)というタプルを順次取得することができ、その分、メモリ消費が抑えられるようになっています。
下図は return と yield を使った関数の例です。
最初の get_char 関数は引数で受け取った変数 text の for ループで最初の 1文字を変数 a に代入して直ちに return しており、メイン処理の print で「日」を 1文字だけ出力して終了していますが、 2つ目の get_char 関数では yield で text の内容を 1文字ずつ返しているため、メイン処理の print ですべての文字を書きだすことができています。
話を戻します。os.walk はカレントディレクトリ、サブディレクトリのリスト、カレントディレクトリ直下のファイルのリストを返すといいましたが、画像リサイズの処理ではサブディレクトリのリストは必要がないので、「_」で破棄しています。
取得したファイルリストを元に1ファイルずつ処理する
そして次にファイルのリストを処理するための for ループを記述します。
さらに、取得したファイルが画像ファイルかどうかをチェックします。
本来はファイルの内容を調べたうえで画像ファイルかどうかを判断するべきなのですが、ここでは簡易的にファイルの拡張子が「.png」か「.jpg」か、あるいは「.jpeg」であれば画像ファイルだという判定にしたいと思います。 os.path.splitext 関数でファイルパスを拡張子以外と拡張子に分解し、その拡張子を小文字に変換したうえで(’.png’, ‘.jpg’, ‘.jpeg’)のタプルのいずれかに該当するかを判定します。
画像ファイルだと判定されたらリサイズ処理に進みますが、その前に、リサイズ後の画像ファイル名に「resized_」、サムネイルの画像ファイル名に「thumbnail_」を付ける必要があるので、ファイルパスをディレクトリ名とファイル名に分解して、それぞれ dir_name 、 base_name という変数に代入します。
リサイズ処理
いよいよリサイズの処理になります。
PIL.Image.open で画像ファイルを開いて、そのオブジェクトを img に代入。 img の resize 関数でリサイズしたら、そのまま save 関数で保存します。保存先は、f’{dir_name}\\resized_{base_name}’というフォーマット済み文字列リテラルで記述しています。これは、波括弧でくくった変数の値が展開された文字列を生成する記法です。
サムネイルの処理
そして最後に、サムネイルを生成するオプション「-t」または「--thumbnail」が指定されていたとき、サムネイル画像を生成して保存します。
画像リサイズプログラムの全容
全体をまとめると下記のようなコードになります。なお、処理中のファイル名を確認したいので、23行目に「print(file)」を追加しています。
Pythonプログラムを保存していない場合は、Ctrl+Sなどでファイルを保存します。本記事では、ファイル名を image_resizer.pyとします。
画像リサイズプログラムを実行する
コマンドプロンプトで画像リサイズプログラムを実行します。試しに、幅 480ピクセル、サムネイルサイズを 200ピクセルとして実行します。
python image_resizer.py images -w 480 -t 200
すると imagesディレクトリの中の画像ファイルをすべて処理していきます。パソコンの性能に依存すると思いますが、15ファイルで 2秒程度でした。画像フォルダの中をエクスプローラーで確認すると、各画像のファイル名に「resized_」と「thumbnail_」がついた新たなファイルが生成されていることがわかります。
適切にリサイズされているかどうかはペイントブラシなどご確認ください。
各社バイクの画像は前述のプログラムのテストを目的として下記のURL(ドメイン名のアルファベット順)からお借りしました。また、画像ファイル名はテスト用に変更させていただいております。
https://www.honda.co.jp/motor-lineup/
https://www.kawasaki-motors.com/mc/
https://www1.suzuki.co.jp/motor/lineup/