Python, Pillowで画像を縦・横に連結(結合)

Posted: | Tags: 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')

python pillow concat h

python pillow concat v

余分な部分をカットして連結(結合)

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')

python pillow concat h cut

python pillow concat v cut

上の例ではそれぞれの画像の上端または左端が揃っているが、中心を揃えることもできる。

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')

python pillow concat h cut center

python pillow concat v cut center

余白を作って連結(結合)

元の画像がカットされてしまうのを防ぐために余白を作って連結する。

最初に大きい背景を作成しておけば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')

python pillow concat h blank

python pillow concat v blank

リサイズして連結(結合)

画像をリサイズして幅や高さを合わせて連結する。。

例では、大きい画像を小さくリサイズ(縮小)するか、小さい画像を大きくリサイズ(拡大)するかを選択できるようにしてある。拡大すると画質が低下するので、基本的には大きい画像を小さくリサイズ(縮小)したほうがいい。

また、リサンプリングのフィルターも指定できるようにしている。

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')

python pillow concat h resize

python pillow concat v resize

複数の画像を一括で連結(結合)

これまで定義した関数を利用して、複数枚の画像を一括で縦・横に並べた連結画像を取得できる。

例えば以下のような関数。引数として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')

python pillow concat h multi blank

上の例では余白を作る関数を使っているが、ほかのパターンで連結したい場合は適宜対応する関数を使えば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')

python pillow concat h multi resize

python pillow concat v multi resize

タイル状に並べる関数も簡単。引数として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 pillow concat tile resize

リサイズした画像のリストを作成したり、最小の幅や高さ・トータルの幅や高さを算出したりするところでリスト内包表記を使っている。

同じ画像を繰り返して連結(結合)

同じ画像を繰り返して縦横に連結する場合は、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')

縮小画像を並べた例を示す。

python pillow concat tile repeat

関連カテゴリー

関連記事