Concatenate Images with NumPy

Concatenate Images with NumPy

Introduction

Concatenation is a process of combining two things from the end. In formal language theory and computer programming, string concatenation is the operation of joining character strings end-to-end. To precisely explain this, we can perform concatenation for two words snow and ball resulting in snowball. It need not be from the end.

Credits of Cover Image - Photo by Bia Andrade on Unsplash

Concatenation of images is simply concatenating multiple matrices. But in Python, we need to understand that concatenation of lists is different from the concatenation of NumPy arrays.

Concatenating lists

For concatenating two or multiple lists, we can use the + operation that will combine two lists or multiple lists into one big list. Below is an example that can be understood easily.

>>> l1 = [1, 2, 3, 4]
>>> l2 = [4, 5, 6, 7]
>>> 
>>> # concatenation
>>> l3 = l1 + l2
>>> l3
[1, 2, 3, 4, 4, 5, 6, 7]
>>> 
>>> # or
>>> l3 = l1.extend(l2)
>>> l3
[1, 2, 3, 4, 4, 5, 6, 7]

Concatenating NumPy arrays

The scenario would be completely different for NumPy arrays if we were to perform the same + operation as we did for lists (above). NumPy automatically performs linear addition (broadcasting technique) considering the shape of each array is similar. Below is an example that can be understood easily.

For 1D arrays

>>> import numpy as np
>>> 
>>> l1 = np.array([1, 2, 3, 4])
>>> l2 = np.array([4, 5, 6, 7])
>>> 
>>> # using `+` operation
>>> l3 = l1 + l2
>>> print(l3)
[ 5  7  9 11]
>>>

For 2D arrays

>>> import numpy as np
>>> 
>>> l1 = np.array([[1, 2], [3, 4]])
>>> l2 = np.array([[4, 5], [6, 7]])
>>> 
>>> # using `+` operation
>>> l3 = l1 + l2
>>> print(l3)
[[ 5  7]
 [ 9 11]]
>>>

For both cases, the + operation does not work. Rather, we can use the concatenate() method of NumPy's module to combine the arrays. The only criteria here is that we should have all the arrays in the same dimension.

With the help of this method, we can either concatenate horizontally or vertically. Below is an example that can be understood easily.

>>> import numpy as np
>>> 
>>> l1 = np.array([[1, 2], [3, 4]])
>>> l2 = np.array([[4, 5], [6, 7]])
>>> 
>>> # concatenation
>>> # axis = 0 → concatenates vertically
>>> l3 = np.concatenate((l1, l2), axis=0)
>>> print(l3)
[[1 2]
 [3 4]
 [4 5]
 [6 7]]
>>> 
>>> # axis = 1 → concatenates horizontally
>>> l4 = np.concatenate((l1, l2), axis=1)
>>> print(l4)
[[1 2 4 5]
 [3 4 6 7]]
>>>

From the above example, we are clear that concatenation can be easily done and the same thing is performed on the images to combine two or multiple (different) images.

We are basically replicating the methods hconcat() (horizontal concatenation) and vconcat() (vertical concatenation) of the module cv2 in NumPy.

Time to Code

The packages that we mainly use are:

  • NumPy
  • Matplotlib
  • OpenCV → It is only used for reading the image (in this article).

python_packages.png

import the Packages

import numpy as np
import cv2
import json
from matplotlib import pyplot as plt

Read the Image

def read_this(image_file, gray_scale=False):
    image_src = cv2.imread(image_file)
    if gray_scale:
        image_src = cv2.cvtColor(image_src, cv2.COLOR_BGR2GRAY)
    else:
        image_src = cv2.cvtColor(image_src, cv2.COLOR_BGR2RGB)
    return image_src

The above function reads the image either in grayscale or RGB and returns the image matrix.

Image Re-sizing

While concatenating two different images, there can be a difference in the dimensions of the image matrices. We need to make sure that the dimensions are the same. There is a need to resize the image matrix (refer to this article for understanding image resizing). And, hence the below function.

def resize_image(image_matrix, nh, nw):
    image_size = image_matrix.shape
    oh = image_size[0]
    ow = image_size[1]   

    re_image_matrix = np.array([
        np.array([image_matrix[(oh*h // nh)][(ow*w // nw)] for w in range(nw)]) 
        for h in range(nh)
    ])

    return re_image_matrix

For this, we will need to take two different images.

Image 1

lena_original.png

Image 2

pinktree.jpg

Note - The dimensions of the above images are different.

Image Matrices

# grayscale mode is false by default

image1 = read_this(image_file='lena_original.png')
image2 = read_this(image_file='pinktree.jpg')

Code Implementation with Library

The function takes totally three arguments -

  • image_set → a list of image matrices.
  • how → concatenating on what basis, vertically or horizontally.
  • default arg with_plot → to plot the results or not.
def concat_lib(image_set, how, with_plot=False):
    # dimension of each matrix in image_set
    shape_vals = [imat.shape for imat in image_set]

    # length of dimension of each matrix in image_set
    shape_lens = [len(ishp) for ishp in shape_vals]

    # if all the images in image_set are read in same mode
    channel_flag = True if len(set(shape_lens)) == 1 else False

    if channel_flag:
        ideal_shape = max(shape_vals)
        images_resized = [
            # function call to resize the image
            resize_image(image_matrix=imat, nh=ideal_shape[0], nw=ideal_shape[1]) 
            if imat.shape != ideal_shape else imat for imat in image_set
        ]
    else:
        return False

    # cv2 library code to concatenate the image matrices
    # we use methods like
    #     - vconcat() → vertical concat
    #     - hconcat() → horizontal concat
    if (how == 0) or (how == 'vertical'):
        concats = cv2.vconcat(images_resized)
    elif (how == 1) or (how == 'horizontal'):
        concats = cv2.hconcat(images_resized)
    else:
        concats = cv2.hconcat(images_resized)

    if with_plot:
        cmap_val = None if len(concats.shape) == 3 else 'gray'
        plt.figure(figsize=(10, 6))
        plt.axis("off")
        plt.imshow(concats, cmap=cmap_val)
        return True
    return concats

In the case of image matrices with different dimensions, they are handled carefully by taking the ideal dimension whose matrix dimension is maximum. We are using the methods hconcat() and vconcat() based on the value of how that is passed.

Let's test the above function -

concat_lib(
    image_set=[image1, image2, image1, image1, image2], 
    how='horizontal', 
    with_plot=True
)

hconcat.png

image2 is automatically resized to the size of image1 and the same is converted into a giant matrix.

Code Implementation from Scratch

The function takes totally three arguments -

  • image_set → a list of image matrices.
  • how → concatenating on what basis, vertically or horizontally.
  • default arg with_plot → to plot the results or not.
def concat_images(image_set, how, with_plot=False):
    # dimension of each matrix in image_set
    shape_vals = [imat.shape for imat in image_set]

    # length of dimension of each matrix in image_set
    shape_lens = [len(ishp) for ishp in shape_vals]

    # if all the images in image_set are read in same mode
    channel_flag = True if len(set(shape_lens)) == 1 else False

    if channel_flag:
        ideal_shape = max(shape_vals)
        images_resized = [
            # function call to resize the image
            resize_image(image_matrix=imat, nh=ideal_shape[0], nw=ideal_shape[1]) 
            if imat.shape != ideal_shape else imat for imat in image_set
        ]
    else:
        return False

    images_resized = tuple(images_resized)

    if (how == 'vertical') or (how == 0):
        axis_val = 0
    elif (how == 'horizontal') or (how == 1):
        axis_val = 1
    else:
        axis_val = 1

    # numpy code to concatenate the image matrices
    # concatenation is done based on axis value
    concats = np.concatenate(images_resized, axis=axis_val)

    if with_plot:
        cmap_val = None if len(concats.shape) == 3 else 'gray'
        plt.figure(figsize=(10, 6))
        plt.axis("off")
        plt.imshow(concats, cmap=cmap_val)
        return True
    return concats

In the case of image matrices with different dimensions, they are handled carefully by taking the ideal dimension whose matrix dimension is maximum. We are using the method concatenate() based on the value of how that is passed.

Let's test the above function -

concat_images(
    image_set=[image1, image2, image1, image1, image2], 
    how='vertical', 
    with_plot=True
)

vconcat.png

image2 is automatically resized to the size of image1 and the same is converted into a giant matrix.

We can also play with the above functions to explore more.

gimg1 = concat_images(image_set=[image1, image2], how='vertical')
gimg2 = concat_images(image_set=[image1, image2], how='horizontal')

concat_images(
    image_set=[gimg1, gimg2],
    how=1, 
    with_plot=True
)

gg_giant.png


Here I take leave. If you have liked it consider visiting this page to read more on Image Processing. And make sure to buy coffee for me from here.