1

I have 15 images (from 1 to 15). I would like to stitch these images together so that it forms one single image. What I tried so far?

import numpy as np
import PIL
from PIL import Image
import os
filenames = [os.path.abspath(os.path.join(directory, p)) for p in os.listdir(directory) if p.endswith(('jpg', 'png'))]

imgs = [PIL.Image.open(i) for i in filenames]
 
min_shape = sorted( [(np.sum(i.size), i.size ) for i in imgs])[0][1]
imgs_comb = np.hstack( (np.asarray( i.resize(min_shape) ) for i in imgs ) )
 
imgs_comb = PIL.Image.fromarray( imgs_comb)
imgs_comb.save( 'stitched_image.jpg' )

This stitches the image horizontally and not as a perfect image. The output looks like the following:

Output_from_the_script

But the desired output should be:

Desired_output

How do I do that?

disukumo
  • 321
  • 6
  • 15

3 Answers3

1

Since you've mentioned they are all the same size you can create a new image:

c, h, w = image.shape

new_image = np.zeros((5 * h, 3 * w))

Now we have an empty image in the correct size.

The next step is copying the images into the big image (forgive me cause i didnt test this bit of code but with minor changes / fixes it should work, the important part is the idea)

row = -1
for i, img in enumerate(list_of_images):
    if i % 3:
        col = 0
        row += 1
    
    new_image[:, row * h: (row + 1) * h, col * w: (col + 1) * w] = img
    col += 1

essentially you are tiling the images into the big image and the result should be as you hoped for.

Dr. Prof. Patrick
  • 1,280
  • 2
  • 15
  • 27
1

These are stitched together horizontally because you have stuck them together with np.hstack() when you actually want to hstack only three at a time into rows and then vstack them together vertically. Replacing that one line with the below should do what you need.

img_rows = []
for min_id in range(0, 15, 3):
    img_row = np.hstack( (np.asarray( i.resize(min_shape) ) for i in imgs[min_id: min_id+3] ) )
    img_rows.append(img_row)
imgs_comb = np.vstack( ( i for i in img_rows ) )
1

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.

ffsedd
  • 186
  • 9
  • Thanks! That's really helpful. Just out of curiosity, is it possible to implement the same approach for images with different shape? – disukumo Jul 26 '21 at 14:25
  • 1
    What output you expect? Leave empty space between smaller images? Or resize images before stitching? Either way, it may be best to resize images first and then apply stitching. – ffsedd Jul 26 '21 at 14:43