Python, Pillowで画像を縦・横に連結(結合)
Pythonの画像処理ライブラリPillow(PIL) を利用して、複数の画像を縦や横に並べて連結(結合)できる。
Image.new()
で背景を生成しImage.paste()
で画像を並べて貼り付け、という流れ。
サイズが異なる画像を連結する場合、いくつかの処理方法が考えられるが、ここでは3通りについて説明する。
- 高さまたは幅が同じ画像の連結
- 高さまたは幅が異なる画像の連結
- 余分な部分をカットして連結
- 余白を作って連結
- リサイズして連結
- 複数の画像を一括で連結
- 同じ画像を繰り返して連結
以下の2枚の画像を例にする。
from PIL import Image
im1 = Image.open('data/src/lena.jpg')
im2 = Image.open('data/src/rocket.jpg')
なお、サンプルコードはカラー画像(mode='RGB'
)を前提にしているが、モノクロ画像(mode='L'
)でも流れは同じ。
入力画像のmode
に合わせてImage.new()
で背景を生成することもできる。ただし、異なるmode
の入力画像を連結する場合にmode
を変換したりする処理が必要。
OpenCV, scikit-imageでの画像の連結については以下の記事を参照。同じサイズの場合はscikit-imageが便利。枠線や境界線も追加できる。
高さまたは幅が同じ画像の連結(結合)
Image.new()
で背景を生成しImage.paste()
で画像を並べて貼り付ける。
def get_concat_h(im1, im2):
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (im1.width, 0))
return dst
def get_concat_v(im1, im2):
dst = Image.new('RGB', (im1.width, im1.height + im2.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (0, im1.height))
return dst
get_concat_h(im1, im1).save('data/dst/pillow_concat_h.jpg')
get_concat_v(im1, im1).save('data/dst/pillow_concat_v.jpg')
余分な部分をカットして連結(結合)
PillowのImage.paste()
では貼り付け先の画像の範囲外にはみ出した部分は無視される(カットされる)。
横に並べる場合は高さを小さい方に合わせた背景を生成、縦に並べる場合は幅を小さい方に合わせた背景を生成すると、余分な部分がカットされて連結される。
def get_concat_h_cut(im1, im2):
dst = Image.new('RGB', (im1.width + im2.width, min(im1.height, im2.height)))
dst.paste(im1, (0, 0))
dst.paste(im2, (im1.width, 0))
return dst
def get_concat_v_cut(im1, im2):
dst = Image.new('RGB', (min(im1.width, im2.width), im1.height + im2.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (0, im1.height))
return dst
get_concat_h_cut(im1, im2).save('data/dst/pillow_concat_h_cut.jpg')
get_concat_v_cut(im1, im2).save('data/dst/pillow_concat_v_cut.jpg')
上の例ではそれぞれの画像の上端または左端が揃っているが、中心を揃えることもできる。
def get_concat_h_cut_center(im1, im2):
dst = Image.new('RGB', (im1.width + im2.width, min(im1.height, im2.height)))
dst.paste(im1, (0, 0))
dst.paste(im2, (im1.width, (im1.height - im2.height) // 2))
return dst
def get_concat_v_cut_center(im1, im2):
dst = Image.new('RGB', (min(im1.width, im2.width), im1.height + im2.height))
dst.paste(im1, (0, 0))
dst.paste(im2, ((im1.width - im2.width) // 2, im1.height))
return dst
get_concat_h_cut_center(im1, im2).save('data/dst/pillow_concat_h_cut_center.jpg')
get_concat_v_cut_center(im1, im2).save('data/dst/pillow_concat_v_cut_center.jpg')
余白を作って連結(結合)
元の画像がカットされてしまうのを防ぐために余白を作って連結する。
最初に大きい背景を作成しておけばOK。Image.new()
の引数color
を指定することで任意の色の余白を作ることが可能。
def get_concat_h_blank(im1, im2, color=(0, 0, 0)):
dst = Image.new('RGB', (im1.width + im2.width, max(im1.height, im2.height)), color)
dst.paste(im1, (0, 0))
dst.paste(im2, (im1.width, 0))
return dst
def get_concat_v_blank(im1, im2, color=(0, 0, 0)):
dst = Image.new('RGB', (max(im1.width, im2.width), im1.height + im2.height), color)
dst.paste(im1, (0, 0))
dst.paste(im2, (0, im1.height))
return dst
get_concat_h_blank(im1, im2).save('data/dst/pillow_concat_h_blank.jpg')
get_concat_v_blank(im1, im2, (0, 64, 128)).save('data/dst/pillow_concat_v_blank.jpg')
リサイズして連結(結合)
画像をリサイズして幅や高さを合わせて連結する。。
例では、大きい画像を小さくリサイズ(縮小)するか、小さい画像を大きくリサイズ(拡大)するかを選択できるようにしてある。拡大すると画質が低下するので、基本的には大きい画像を小さくリサイズ(縮小)したほうがいい。
また、リサンプリングのフィルターも指定できるようにしている。
def get_concat_h_resize(im1, im2, resample=Image.BICUBIC, resize_big_image=True):
if im1.height == im2.height:
_im1 = im1
_im2 = im2
elif (((im1.height > im2.height) and resize_big_image) or
((im1.height < im2.height) and not resize_big_image)):
_im1 = im1.resize((int(im1.width * im2.height / im1.height), im2.height), resample=resample)
_im2 = im2
else:
_im1 = im1
_im2 = im2.resize((int(im2.width * im1.height / im2.height), im1.height), resample=resample)
dst = Image.new('RGB', (_im1.width + _im2.width, _im1.height))
dst.paste(_im1, (0, 0))
dst.paste(_im2, (_im1.width, 0))
return dst
def get_concat_v_resize(im1, im2, resample=Image.BICUBIC, resize_big_image=True):
if im1.width == im2.width:
_im1 = im1
_im2 = im2
elif (((im1.width > im2.width) and resize_big_image) or
((im1.width < im2.width) and not resize_big_image)):
_im1 = im1.resize((im2.width, int(im1.height * im2.width / im1.width)), resample=resample)
_im2 = im2
else:
_im1 = im1
_im2 = im2.resize((im1.width, int(im2.height * im1.width / im2.width)), resample=resample)
dst = Image.new('RGB', (_im1.width, _im1.height + _im2.height))
dst.paste(_im1, (0, 0))
dst.paste(_im2, (0, _im1.height))
return dst
get_concat_h_resize(im1, im2).save('data/dst/pillow_concat_h_resize.jpg')
get_concat_v_resize(im1, im2, resize_big_image=False).save('data/dst/pillow_concat_v_resize.jpg')
複数の画像を一括で連結(結合)
これまで定義した関数を利用して、複数枚の画像を一括で縦・横に並べた連結画像を取得できる。
例えば以下のような関数。引数としてPIL.Image
のリスト(配列)を渡して縦・横に並べる
def get_concat_h_multi_blank(im_list):
_im = im_list.pop(0)
for im in im_list:
_im = get_concat_h_blank(_im, im)
return _im
get_concat_h_multi_blank([im1, im2, im1]).save('data/dst/pillow_concat_h_multi_blank.jpg')
上の例では余白を作る関数を使っているが、ほかのパターンで連結したい場合は適宜対応する関数を使えばOK。
複数枚をリサイズして並べる場合は、繰り返しリサイズして画質が劣化するのを防ぐために、最初にすべての画像を最終的な幅や高さに合わせてリサイズしておいたほうがいい。
ここでは最初にImage.new()
で大きい背景を作成する方法を用いる。
def get_concat_h_multi_resize(im_list, resample=Image.BICUBIC):
min_height = min(im.height for im in im_list)
im_list_resize = [im.resize((int(im.width * min_height / im.height), min_height),resample=resample)
for im in im_list]
total_width = sum(im.width for im in im_list_resize)
dst = Image.new('RGB', (total_width, min_height))
pos_x = 0
for im in im_list_resize:
dst.paste(im, (pos_x, 0))
pos_x += im.width
return dst
def get_concat_v_multi_resize(im_list, resample=Image.BICUBIC):
min_width = min(im.width for im in im_list)
im_list_resize = [im.resize((min_width, int(im.height * min_width / im.width)),resample=resample)
for im in im_list]
total_height = sum(im.height for im in im_list_resize)
dst = Image.new('RGB', (min_width, total_height))
pos_y = 0
for im in im_list_resize:
dst.paste(im, (0, pos_y))
pos_y += im.height
return dst
get_concat_h_multi_resize([im1, im2, im1]).save('data/dst/pillow_concat_h_multi_resize.jpg')
get_concat_v_multi_resize([im1, im2, im1]).save('data/dst/pillow_concat_v_multi_resize.jpg')
タイル状に並べる関数も簡単。引数としてPIL.Image
の2次元リスト(配列)を渡して縦・横にタイル状に並べる。
def get_concat_tile_resize(im_list_2d, resample=Image.BICUBIC):
im_list_v = [get_concat_h_multi_resize(im_list_h, resample=resample) for im_list_h in im_list_2d]
return get_concat_v_multi_resize(im_list_v, resample=resample)
get_concat_tile_resize([[im1],
[im1, im2],
[im1, im2, im1]]).save('data/dst/pillow_concat_tile_resize.jpg')
リサイズした画像のリストを作成したり、最小の幅や高さ・トータルの幅や高さを算出したりするところでリスト内包表記を使っている。
- 関連記事: Pythonリスト内包表記の使い方
同じ画像を繰り返して連結(結合)
同じ画像を繰り返して縦横に連結する場合は、2次元リスト(配列)で指定するのが面倒なので、繰り返し数を指定する関数を用意しておくと便利。
def get_concat_h_repeat(im, column):
dst = Image.new('RGB', (im.width * column, im.height))
for x in range(column):
dst.paste(im, (x * im.width, 0))
return dst
def get_concat_v_repeat(im, row):
dst = Image.new('RGB', (im.width, im.height * row))
for y in range(row):
dst.paste(im, (0, y * im.height))
return dst
def get_concat_tile_repeat(im, row, column):
dst_h = get_concat_h_repeat(im, column)
return get_concat_v_repeat(dst_h, row)
im_s = im1.resize((im1.width // 2, im1.height // 2))
get_concat_tile_repeat(im_s, 3, 4).save('data/dst/pillow_concat_tile_repeat.jpg')
縮小画像を並べた例を示す。