Introduction
In this tutorial we are going to learn how to concatenate images horizontally and vertically using Python and OpenCV.
Important: For simplicity, we are going to be concatenating images with themselves on the code examples below. Nonetheless, it is possible to concatenate different images as long as we respect the following:
- The image dimensions can only differ along the concatenation axis (horizontal or vertical, depending on the concatenation function we are using). In this tutorial we are not going to cover resizing of images to comply with this restriction;
- The number of channels needs to be the same. So, we cannot concatenate a BGR image with a grayscale one without doing some additional manipulation (explained below).
This tutorial was tested with version 4.0.0 of OpenCV and version 3.7.2 of Python.
Appending images
We will start by importing both the numpy and the cv2 modules. We will need numpy to perform the appending of the images and cv2 to read the original image and then to display the final result.
import numpy
import cv2
After this we will take care of reading the original image that we are going to use to do the concatenation. For this initial example, we will concatenate the image with itself.
To read the image, we simply need to call the imread function of the cv2 module, passing as input a string with the path to the image in the file system.
img = cv2.imread('C:/Users/N/Desktop/Test.jpg')
Note that, as we have seen in previous tutorials, the image returned by the imread function is represented as a ndarray. This means that we can use many of numpy functions to manipulate it.
Taking this in consideration, we will vertically append the image with itself by using the vstack function from the numpy module.
As input, this function receives a tuple with the ndarrays we want to concatenate. So, we will pass a tuple that contains our image in the first and second positions. As output, we will receive a new ndarray with the result, which corresponds to the two images concatenated vertically.
verticalAppendedImg = numpy.vstack((img,img))
We can also perform this concatenation horizontally by calling the hstack function. The argument is the same: a tuple with the ndarrays we want to concatenate. For illustration, our tuple will contain the image three times, to confirm that we can concatenate more than one image at each time.
horizontalAppendedImg = numpy.hstack((img,img,img))
To finalize, we will display both images in two different windows.
cv2.imshow('Vertical Appended', verticalAppendedImg)
cv2.imshow('Horizontal Appended', horizontalAppendedImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
The complete code can be seen below.
import numpy
import cv2
img = cv2.imread('C:/Users/N/Desktop/Test.jpg')
verticalAppendedImg = numpy.vstack((img,img))
horizontalAppendedImg = numpy.hstack((img,img,img))
cv2.imshow('Vertical Appended', verticalAppendedImg)
cv2.imshow('Horizontal Appended', horizontalAppendedImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
Testing the code
To test the code, simply run it using a tool of your choice. I’m using IDLE, a Python IDE.
Figure 1 illustrates what is expected from the horizontally appended images.

Figure 2 shows the vertically appended images.

Appending BGR and Grayscale images
One possible application of appending might be displaying an original image side by side with a image that was processed somehow and, many times, we process images in grayscale.
If we try to append an image in RBG with one in grayscale, then both the hstack and vstack functions will throw an error. This happens because both ndarrays have different dimensions (a BGR image has 3 dimensions and a grayscale image has 2 dimensions).
To be able to perform this concatenation, we need to convert the grey image back to the BGR color space. We will check how to do it below.
So, as before, we start by importing numpy and cv2. Followed by that, we will read the image and convert it to grayscale. For a detailed tutorial on how to convert an image to grayscale, please check this tutorial.
import numpy
import cv2
img = cv2.imread('C:/Users/N/Desktop/Test.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Now that we have our grayscale image, to convert it back to the BGR color space, we can call again the cvtColor function, passing as first input our grayscale image and as second input the color space conversion code: COLOR_GRAY2BGR.
Note that, in BGR, the image will remain gray, it won’t be converted back to a colored image. This is what we intended: to preserve the gray image but to have it in the BGR color space, so the ndarray representing it has also 3 dimensions, like the original image.
grayImageBGRspace = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
After that, we can take care of concatenating the original image and the gray image in the BGR space, and display it in a window.
horizontalAppendedIGrayImg = numpy.hstack((img, grayImageBGRspace))
cv2.imshow('Horizontal Appended Gray Img', horizontalAppendedIGrayImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
The complete code can be seen below.
import numpy
import cv2
img = cv2.imread('C:/Users/N/Desktop/Test.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## won't work
##horizontalAppendedIGrayImg = numpy.hstack((img,gray))
grayImageBGRspace = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
horizontalAppendedIGrayImg = numpy.hstack((img, grayImageBGRspace))
cv2.imshow('Horizontal Appended Gray Img', horizontalAppendedIGrayImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
Upon running the code, you should obtain a result similar to the one shown in figure 3.
