Python 複数の画像ファイルを連結して1枚の画像ファイルを作成する「OpenCV, matplotlib, numpy」

'23/11/16更新:図例を見やすいのに差し替えたのとコードの可読性を若干向上しました。
 本記事では、Pythonで複数の画像ファイル(jpg, png)を連結して1つの画像ファイルに保存する雛形コードを載せました。
下図は、8枚の画像ファイルを3列3行の位置に配置して、1枚の画像ファイルにした例です。下図例では列数は3ですが他の数字を指定できます。そして各行の左端と各列の上にテキストを挿入できます。

 ライブラリは、matplotlibに加えて、OpenCV(cv2)とPillow(PIL)とNumpy(numpy)を使用します。
 特に、Pillowを使用する理由は、OpenCVでは日本語ファイルパスを読み込めずにエラーになるためです(Noneを返す)。そして、Numpyを使用するのはひとつの画像に複数の画像を行列に整列する際に、上図の2行目の2列の2箇所のように埋めきらない場合に無地で代替するためです。

■本プログラム

import os, glob, re
import math
import cv2
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

def main():
    # 画像ファイルがあるフォルダパス
    myimg_list = glob.glob(r'./pictures/*.png')
    
    # 保存するファイル名
    output_file_path = 'test.jpg' # jpg、pngのどちらでもOK

    # 列数の指定
    n_col = 3

    # ファイル名(番号)でソート
    filepath_list = sorted(myimg_list, key=lambda x:int((re.search(r"[0-9]+", x)).group(0)))

    # 行数を算出(math.ceilで小数点を切り上げ)
    n_row = math.ceil(len(filepath_list) / n_col)
    
    # 行列の枠を設ける
    _, axs = plt.subplots(n_row, n_col, figsize=(8,6), tight_layout=True) # (x, y)
    axs = axs.flatten()
    
    # 画像ファイルを貼り付けてゆく
    sum = n_row * n_col
    j = 1
    for i in range(sum):
        print(i+1, '/', sum)
        if i < len(filepath_list):
            print(filepath_list[i])
            img = Image.open(filepath_list[i])
        else:
            img = np.zeros((1, 1, 3)) # 無地黒
            img += 255 # 無地白
        img = np.asarray(img)
        axs[i].imshow(img)
        axs[i].axis('off') # 目盛り表記をオフる
        if (i+1) <= n_col:
            # 1行目の上に列名をサブタイトルで記載する
            axs[i].set_title(f'{(i+1)}列目')
        if i%n_col == 0:
            # 各行の左にテキストを挿入
            axs[i].text(0, 0.5, f'{(j)}行目', rotation=90, ha='right', va='center', transform=axs[i].transAxes)
            j += 1

    # レイアウトを整える
    plt.tight_layout()
    #plt.show()
    plt.savefig(output_file_path, # 出力ファイル名
                format = os.path.splitext(output_file_path)[1][1:], # 拡張子の指定
                dpi=360)

if __name__ == "__main__":
    main()
    print('finished')

以上

<広告>