note.nkmk.me

Concatenate images with Python, Pillow

Posted: 2019-05-14 / Tags: Python, Pillow, Image Processing

Pillow (PIL) can be used to concatenate (combine) multiple images vertically and horizontally.

Create a background with Image.new() and paste the images with Image.paste().

There are several conceivable ways to concatenate images of different sizes. Here, three will be described.

  • Concatenate images with the same height or width
  • Concatenate images with different heights or widths
    • Cut extra area and concatenate
    • Make margins and concatenate
    • Resize and concatenate
  • Concatenate multiple images at once
  • Concatenate the same image repeatedly

Use the following two images as an example.

from PIL import Image

im1 = Image.open('data/src/lena.jpg')
im2 = Image.open('data/src/rocket.jpg')

The sample code is assumed to be a color image (mode='RGB'), but the flow is the same for monochrome images (mode='L').

Please refer to the following post for image concatenation in OpenCV.

Sponsored Link

Concatenate images with the same height or width

Create a background with Image.new() and paste the images with 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

Cut extra area and concatenate

In Image.paste(), the area out of the range of the image to paste is ignored (cut).

In the case of horizontal alignment, generate a background with smaller height, and in the case of vertical alignment, generate a smaller width, excess area is cut and concatenated.

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

In the example above, the top or left edge of each image is aligned. The centers can also be aligned.

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

Make margins and concatenate

Make margins and concatenate to prevent the original image from being cut.

Create a large background first. You can make the margin of any color by specifying the parameter color of Image.new().

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

Sponsored Link

Resize and concatenate

Resize the image to match width or height and connect them.

In the example, it is possible to select whether to resize a large image small or to resize a small image large. Basically, it is better to reduce the size of a large image to a smaller size, because the image quality decreases when the image is enlarged.

Also, the filter for resampling can be specified.

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

Concatenate multiple images at once

You can obtain a concatenated image in which multiple images are arranged in a row vertically and horizontally using the function defined above.

For example, pass a list of PIL.Image and concatenate them vertically and horizontally.

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

In the example above, we use the function to make margins. If you want to concatenate with other ways, use the corresponding function as appropriate.

When resizing and arranging multiple images, it is better to resize all the images to the final width or height first to prevent repeated resizing and image quality deterioration.

Here, create a large background with Image.new() first.

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

The function to arrange in a tile is also easy. Pass a 2D list of PIL.Image, and arrange in tiles vertically and horizontally.

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

List comprehensions is used to create a resized image list and to calculate the minimum width and height, the total width and height.

Concatenate the same image repeatedly

If you want to combine the same image repeatedly vertically and horizontally, it is troublesome to specify in the 2D list, so it is convenient to prepare a function to specify the number of repetitions.

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

Sponsored Link
Share

Related Categories

Related Posts