Title: Montages with OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2017/05/29/montages-with-opencv/

Published Time: 2017-05-29T14:00:07+00:00

Markdown Content:
Today’s blog post is inspired by an email I received from PyImageSearch reader, Brian.

![Image 1](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20430'%3E%3C/svg%3E)

Brian asks:

> Hi Adrian,
> 
> I’m really enjoying the PyImageSearch blog. I found your site a few days ago and I’ve been hooked on your tutorials ever since.
> 
> I followed your tutorial on [building an image search engine](https://pyimagesearch.com/2014/12/01/complete-guide-building-image-search-engine-python-opencv/), but instead of displaying the result images one-by-one (like you did), I want to display the top-20 results in a montage.
> 
> Is there a way to do with OpenCV?
> 
> Thanks,
> 
> Brian

Great question Brian, thanks for asking.

One of my favorite aspects of running the PyImageSearch blog is being able to chat with you, the reader, and discover the projects you’re working on.

**It’s _especially exciting_ when I can take questions or comments and turn them into blog posts — that way the entire PyImageSearch community is able to benefit from the answer.**

Today we will learn how to build montages of images using OpenCV and the [imutils package](https://github.com/jrosebr1/imutils). A big thank you to [Kyle Hounslow](https://github.com/kylehounslow) who contributed the `build_montages` function to imutils.

**To learn more about building an image montage with OpenCV, _just keep reading_.**

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

There are four primary pieces to today’s blog post.

In the first part, we’ll learn how to build a list of image paths from an image dataset residing on disk.

From there, we’ll use the `build_montages` function to take this list of images and create the actual montage.

Next, we’ll display the montage to our screen.

Finally, I’ll provide an example of using montages to display images with OpenCV.

To download the source code + example images to this blog post, be sure to use the **_“Downloads”_** section below.

### Creating a montage with OpenCV

To get started, open up a new file, name it `montage_example.py` , and insert the following code:

\# import the necessary packages
from imutils import build\_montages
from imutils import paths
import argparse
import random
import cv2

**Lines 2-6** import our required Python packages. Notice how `build_montages` is imported from the [imutils package](https://github.com/jrosebr1/imutils).

If you do not have `imutils` installed on your system (_v0.4.3_ as of this writing), then make sure you install/upgrade it via `pip` :

$ pip install --upgrade imutils

_**Note:** If you are using Python virtual environments (as all of my [OpenCV install tutorials](https://pyimagesearch.com/opencv-tutorials-resources-guides/) do), make sure you use the `workon` command to access your virtual environment first and **then** install/upgrade `imutils` ._

From there, we can parse our command line arguments:

\# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--images", required=True,
	help="path to input directory of images")
ap.add\_argument("-s", "--sample", type=int, default=21,
	help="# of images to sample")
args = vars(ap.parse\_args())

Our script requires one command line argument, followed by a second optional one, each detailed below:

*   `--images` : The path to your directory containing the images you want to build a montage out of.
*   `--samples` : An optional command line argument that specifies the number of images to sample (we default this value to `21` total images).

Next, we can use the `--images` path to randomly select some input images:

\# grab the paths to the images, then randomly select a sample of
# them
imagePaths = list(paths.list\_images(args\["images"\]))
random.shuffle(imagePaths)
imagePaths = imagePaths\[:args\["sample"\]\]

To obtain a listing of all image paths inside the `--images` directory, we make a call to the `list_images` function (**Line 18**).

For the purpose of this exercise we randomly shuffle the image paths on **Line 19**, followed by taking a _sample_ of these images to display to our screen (**Line 20**). The set of `imagePaths` returned by this sampling will be used to build our montage.

For your own applications you likely will not have to bother with randomly shuffling and selecting a set of image paths — **you will _already_ have your image paths.**

In the context of Brian’s original question, he is looking to display the results of his image search engine.

The _results_ therefore contain his image paths.

Again, keep in mind that we are simply _demonstrating_ how to build a montage with OpenCV — how you actually _use_ this example is _entirely_ up to you.

Given our `imagePaths` , we are ready to build the montage:

\# initialize the list of images
images = \[\]

# loop over the list of image paths
for imagePath in imagePaths:
	# load the image and update the list of images
	image = cv2.imread(imagePath)
	images.append(image)

# construct the montages for the images
montages = build\_montages(images, (128, 196), (7, 3))

On **Line 23** we initialize our list of `images` .

We then loop through the `imagePaths` on **Lines 26-29**, loading each `image` from disk, and then appending the `image` to our `images` list.

To actually construct the montage, we make a call to the `build_montages` function on **Line 32** — this is where all of the heavy lifting is done. If you’re curious about the internals of the `build_montages` method and what is going on under the hood, be sure to check out the source code [implementation on GitHub](https://github.com/jrosebr1/imutils/blob/master/imutils/convenience.py).

The `build_montages` function requires three arguments:

*   `image_list` : This parameter is a list of images loaded via OpenCV. In our case, we supply the `images` list built on **Lines 26-29**.
*   `image_shape` : A tuple containing the _width_ and _height_ of each image in the montage. Here we indicate that all images in the montage will be resized to _129 x 196_. Resizing every image in the montage to a fixed size is a _requirement_ so we can properly allocate memory in the resulting NumPy array. _**Note:** Empty space in the montage will be filled with black pixels._
*   `montage_shape` : A second tuple, this one specifying the number of columns and rows in the montage. Here we indicate that our montage will have 7 columns (7 images wide) and 3 rows (3 images tall).

The `build_montages` method returns a list of montage images in NumPy array format.

If there are _more_ images in the `images` list than the `montage_shape` can hold, _a new montage is created for the extra `images` ._ This process is repeated until all `images` have been added to a montage. **This process is identical to displaying search results over multiple pages.**

Our final code block handles displaying the `montages` to our screen:

\# loop over the montages and display each of them
for montage in montages:
	cv2.imshow("Montage", montage)
	cv2.waitKey(0)

On **Line 35** we loop over each of the `montages` (again, similar to displaying _N_ number of (faux) “search results” on a page).

**Lines 36 and 37** then display the current `montage` to our screen. The `cv2.waitKey` call pauses execution of our script until we select the currently active window and press any key on our keyboard. This will cause the `for` loop to advance.

Once we reach the end of the `montages` list, the script exits.

### Displaying the Montage

Approximately two years ago I was involved in a computer vision project that required me to build a simple image fashion search engine. To accomplish this, I built a simple web crawler to spider [Nordstrom.com](http://shop.nordstrom.com/) and download all the product images and associated meta data.

We are are going to use a tiny sample of this data today when demoing the `build_montages` function.

Once you’ve used the _**“Downloads”**_ section below to download the source code + example images, you can execute the following command to see the results:

$ python montage\_example.py --images nordstrom\_sample

After executing the script you should see output similar to the following:

![Image 3](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20375'%3E%3C/svg%3E)

**Figure 1:** Building a montage with OpenCV and Python.

_**Note:** The_ exact _images that you see in the montage will vary from mine since we are **randomly sampling** from the input directory._

As we can see in **Figure 1** above, we have three rows, each row containing seven images. Each image in the montage has been resized to a fixed size of _128 x 196_ pixels.

In the context of Brian’s question at the top of this blog post, this montage could be search results from his image search engine algorithm.

As a second example, let’s increase the `--sample` such that we create _multiple montages_ since all images will not fit in a three row, seven column format:

$ python montage\_example.py --images nordstrom\_sample --sample 33

Since _3 x 7 = 21_, we know that sampling 33 images cannot possibly fit into a 21 image montage.

Luckily for us, the `build_montages` function realizes that there are too many images to fit into a _single_ montage and thus creates _two montages._

The first montage can be seen below with all 21 spaces in the montage occupied:

![Image 4](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20375'%3E%3C/svg%3E)

**Figure 2:** The first montage generated with OpenCV is completely filled.

The second montage holds the remaining 12 images that could not fit in the first montage:

![Image 5](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20375'%3E%3C/svg%3E)

**Figure 3:** The second montage displays images that could not fit in the first montage.

Notice how empty spaces in the montage are filled with black pixels.

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

Summary
-------

In today’s blog post I demonstrated how to build a montage with OpenCV and Python to visualize a collection of images. This is a handy tool you can use in your own image processing projects, such as in Brian’s image search engine project detailed at the top of this blog post.

I would also like to take a second and give a big shoutout to [Kyle Hounslow](https://github.com/kylehounslow) who contributed the `build_montages` function to the [imutils package](https://github.com/jrosebr1/imutils) — thanks again Kyle!

In next week’s blog post I’ll demonstrate how to use this montage functionality in an _actual application_ where we sort images in a dataset according to how “colorful” they are.

**To be notified when this next blog post goes live, _be sure to enter your email address in the form below._**

![Image 6](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!