I tried the same approach as Dr. Prof. Patrick and made more universal function. Number of images can be less than rows * cols in this version.
#!/usr/bin/env python3
import numpy as np
from imageio import imread, imwrite
from pathlib import Path
import math
def tile_images(images, cols=None, bg_val=None, tile_size=None):
"""Tile images to grid with given number of columns.
Args:
images (list of np.arrays)
cols (int): 1 = vertical, None = horizontal stitch
bg_val (int or tuple): color of empty background
Returns:
np.array: stitched image
"""
im1 = np.atleast_3d(images[0]) # add 3rd dim to grey image
h, w, ch = im1.shape
if tile_size is not None:
h, w = tile_size
if not cols:
cols = len(images)
rows = math.ceil(len(images) / cols)
# make empty array
new = np.zeros((h * rows, w * cols, ch), dtype=im1.dtype)
# change bg color
if len(images) < rows * cols and bg_val is not None:
new = np.add(new, bg_val)
# paste images into array
c, r = 0, 0
for i, im in enumerate(images):
x, y = r * h, c * w
new[x : x + im.shape[0], y : y + im.shape[1]] = np.atleast_3d(im)
c += 1
if not (i+1) % cols:
r += 1 # next row
c = 0
return new
def main():
paths = sorted(f for f in Path().glob("*.*") if f.suffix in (".jpg", ".png") and f.stem != "new")
images = [imread(f) for f in paths]
new = tile_images(images, cols=3, bg_val=(127,150,127))
imwrite("new.png", new)
if __name__ == "__main__":
main()
EDIT: I have removed the need to check if image has 2 or 3 dim with np.atleast_3d function.
Parameter tile_size allows to set grid size manually, if omitted, first image will be used.
In this version input images need not to be of the same size. Images can overlay, but they must not overflow.