Create a montage of images with Python, scikit-image (skimage.util.montage)
Using scikit-image's skimage.util.montage()
, you can create a montage by concatenating multiple images of the same size horizontally and vertically.
This article describes the following contents.
- Basic usage of
skimage.util.montage()
- Specify the value (color) of the padding and extra areas:
fill
- Specify the layout:
grid_shape
- Specify the width of the borders:
padding_width
- Apply to color images (3D array):
multichannel
For more information on concatenation of images using Pillow and OpenCV, see the following article. These articles also explain the case of different image sizes.
- Concatenate images with Python, Pillow
- Concatenate images with Python, OpenCV (hconcat, vconcat, np.tile)
Note that the version of scikit-image in this article is 0.16.2
.
Basic usage of skimage.util.montage()
The following three two-dimensional arrays (numpy.ndarray
) of the same shape are used as examples. Examples using color images (3D arrays) are described later.
import numpy as np
import skimage.util
a = np.arange(1, 7).reshape(2, 3)
print(a)
# [[1 2 3]
# [4 5 6]]
b = a * 10
print(b)
# [[10 20 30]
# [40 50 60]]
c = a * 100
print(c)
# [[100 200 300]
# [400 500 600]]
By passing a list of these arrays to the first argument of skimage.util.montage()
, a vertically and horizontally concatenated array is returned.
m = skimage.util.montage([a, b, c])
print(m)
# [[ 1 2 3 10 20 30]
# [ 4 5 6 40 50 60]
# [100 200 300 129 129 129]
# [400 500 600 129 129 129]]
print(m.shape)
# (4, 6)
In this example, the three arrays are arranged in 2 x 2 layout, with the extra area (lower right) filled with 129
, the average value of all arrays. The layout and the values to fill in the extra area can be set with the parameters described below.
In skimage.util.montage()
, the list of arrays is processed as a numpy.ndarray
of shape (K, M, N)
consisting of K
arrays with M
rows and N
columns. You can specify it in that form.
abc = np.array([a, b, c])
print(abc)
# [[[ 1 2 3]
# [ 4 5 6]]
#
# [[ 10 20 30]
# [ 40 50 60]]
#
# [[100 200 300]
# [400 500 600]]]
print(abc.shape)
# (3, 2, 3)
print(skimage.util.montage(abc))
# [[ 1 2 3 10 20 30]
# [ 4 5 6 40 50 60]
# [100 200 300 129 129 129]
# [400 500 600 129 129 129]]
If each array does not have the same shape, it cannot be converted to numpy.ndarray
of (K, M, N)
, and an error is raised.
d = a[:, :2]
print(d)
# [[1 2]
# [4 5]]
# skimage.util.montage([a, b, c, d])
# ValueError: could not broadcast input array from shape (2,3) into shape (2)
Specify the value (color) of the padding and extra areas: fill
By default, the extra area is filled with the average value of all arrays.
m = skimage.util.montage([a, b, c])
print(m)
# [[ 1 2 3 10 20 30]
# [ 4 5 6 40 50 60]
# [100 200 300 129 129 129]
# [400 500 600 129 129 129]]
print(np.mean(np.array([a, b, c])))
# 129.5
You can specify a value to fill with fill
.
print(skimage.util.montage([a, b, c], fill=0))
# [[ 1 2 3 10 20 30]
# [ 4 5 6 40 50 60]
# [100 200 300 0 0 0]
# [400 500 600 0 0 0]]
Specify the layout: grid_shape
By default, it concatenates into a square layout with the same number of rows and columns.
You can specify a layout with grid_shape
as a tuple (number of rows, number of columns)
.
print(skimage.util.montage([a, b, c], grid_shape=(1, 3)))
# [[ 1 2 3 10 20 30 100 200 300]
# [ 4 5 6 40 50 60 400 500 600]]
print(skimage.util.montage([a, b, c], grid_shape=(3, 1)))
# [[ 1 2 3]
# [ 4 5 6]
# [ 10 20 30]
# [ 40 50 60]
# [100 200 300]
# [400 500 600]]
If the number of rows * columns is less than the number of arrays, an error is raised.
# print(skimage.util.montage([a, b, c], grid_shape=(1, 2)))
# IndexError: list index out of range
If the number of rows * columns is more than the number of arrays, the extra area is filled with the value of fill
. As mentioned above, by default, it is filled with the average value of all the arrays.
print(skimage.util.montage([a, b, c], grid_shape=(2, 3)))
# [[ 1 2 3 10 20 30 100 200 300]
# [ 4 5 6 40 50 60 400 500 600]
# [129 129 129 129 129 129 129 129 129]
# [129 129 129 129 129 129 129 129 129]]
print(skimage.util.montage([a, b, c], grid_shape=(2, 3), fill=0))
# [[ 1 2 3 10 20 30 100 200 300]
# [ 4 5 6 40 50 60 400 500 600]
# [ 0 0 0 0 0 0 0 0 0]
# [ 0 0 0 0 0 0 0 0 0]]
Specify the width of the borders: padding_width
You can specify the width of the borders by specifying padding_width
. The default value is padding_width=0
, so there is no border.
print(skimage.util.montage([a, b, c], padding_width=1))
# [[129 129 129 129 129 129 129 129 129]
# [129 1 2 3 129 10 20 30 129]
# [129 4 5 6 129 40 50 60 129]
# [129 129 129 129 129 129 129 129 129]
# [129 100 200 300 129 129 129 129 129]
# [129 400 500 600 129 129 129 129 129]
# [129 129 129 129 129 129 129 129 129]]
The border is also filled with the value of fill
.
print(skimage.util.montage([a, b, c], padding_width=1, fill=0))
# [[ 0 0 0 0 0 0 0 0 0]
# [ 0 1 2 3 0 10 20 30 0]
# [ 0 4 5 6 0 40 50 60 0]
# [ 0 0 0 0 0 0 0 0 0]
# [ 0 100 200 300 0 0 0 0 0]
# [ 0 400 500 600 0 0 0 0 0]
# [ 0 0 0 0 0 0 0 0 0]]
Apply to color images (3D array): multichannel
Color images are treated as three-dimensional arrays.
To handle three-dimensional arrays with skimage.util.montage()
, set multichannel
to True
.
import skimage.io
import skimage.util
a = skimage.io.imread('data/src/lena.jpg')
print(a.shape)
# (225, 400, 3)
b = a // 2
c = a // 3
m = skimage.util.montage([a, b, c], multichannel=True)
print(m.shape)
# (450, 800, 3)
skimage.io.imsave('data/dst/skimage_montage_default.jpg', m)
Original image:
Result:
Note that the default value is multichannel=False
, so omitting it will result in an error.
# skimage.util.montage([a, b, c])
# ValueError: Input array has to be either 3- or 4-dimensional
As you can see from the result image above, by default, the extra area (lower right) is filled with the average color.
You can specify any color in fill
with a tuple of (R, G, B)
.
m_fill = skimage.util.montage([a, b, c], fill=(255, 128, 0), multichannel=True)
skimage.io.imsave('data/dst/skimage_montage_fill.jpg', m_fill)
The usage of grid_shape
and padding_width
is the same as for two-dimensional arrays.
m_1_3_pad = skimage.util.montage([a, b, c],
fill=(0, 0, 0),
grid_shape=(1, 3),
padding_width=10,
multichannel=True)
print(m_1_3_pad.shape)
# (245, 1240, 3)
skimage.io.imsave('data/dst/skimage_montage_1_3_pad.jpg', m_1_3_pad)