Title: Sorting Contours using Python and OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2015/04/20/sorting-contours-using-python-and-opencv/

Published Time: 2015-04-20T14:00:33+00:00

Markdown Content:
[![Image 1: Sorting contours left-to-right using Python and OpenCV.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20552'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/04/sorted_contours_top_to_bottom.jpg)

Alright, so at this point you have been exposed to contours pretty heavily on the PyImageSearch blog.

We used contours to build a [kick-ass mobile document scanner](https://pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/ "How to Build a Kick-Ass Mobile Document Scanner in Just 5 Minutes").

Contours enabled us [detect barcodes in images](https://pyimagesearch.com/2014/11/24/detecting-barcodes-images-python-opencv/ "Detecting Barcodes in Images with Python and OpenCV").

And we even leveraged the power of contours to [find the distance from a camera to object or marker](https://pyimagesearch.com/2015/01/19/find-distance-camera-objectmarker-using-python-opencv/ "Find distance from camera to object/marker using Python and OpenCV").

But there still remains a sticking question that we have not addressed: **_how in the world do we_ sort _contours from left-to-right, top-to-bottom, etc._**

Oddly enough OpenCV _does not_ provide a built-in function or method to perform the actual sorting of contours.

But no worries.

In the rest of this blog you’ll be sorting contours using Python and OpenCV like a pro.

Read on to find out more…

![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)

**OpenCV and Python versions:**  
In order to run this example, you’ll need **Python 2.7** and **OpenCV 2.4.X**.

By the end of this blog article you’ll be able to:

1.  Sort contours according to their _size/area_, along with a template to follow to sort contours by any other arbitrary criteria.
    
2.  Sort contoured regions from _left-to-right_, _right-to-left_, _top-to-bottom_, and _bottom-to-top_ using only a **_single function_**.
    

So let’s go ahead and get started. Open up your favorite code editor, name it `sorting_contours.py` and let’s get started:

\# import the necessary packages
import numpy as np
import argparse
import imutils
import cv2

def sort\_contours(cnts, method="left-to-right"):
	# initialize the reverse flag and sort index
	reverse = False
	i = 0

	# handle if we need to sort in reverse
	if method == "right-to-left" or method == "bottom-to-top":
		reverse = True

	# handle if we are sorting against the y-coordinate rather than
	# the x-coordinate of the bounding box
	if method == "top-to-bottom" or method == "bottom-to-top":
		i = 1

	# construct the list of bounding boxes and sort them from top to
	# bottom
	boundingBoxes = \[cv2.boundingRect(c) for c in cnts\]
	(cnts, boundingBoxes) = zip(\*sorted(zip(cnts, boundingBoxes),
		key=lambda b:b\[1\]\[i\], reverse=reverse))

	# return the list of sorted contours and bounding boxes
	return (cnts, boundingBoxes)

We’ll start off by importing our necessary packages: NumPy for numerical processing, `argparse` to parse our command line arguments, and `cv2` for our OpenCV bindings.

Instead of starting off by parsing arguments, loading images, and taking care of other normal procedures, let’s skip these steps for the time being and jump immediately in to defining our `sort_contours` function which will enable us to sort our contours.

The actual `sort_contours` function is defined on **Line 7** and takes two arguments. The first is `cnts` , the list of contours that the we want to sort, and the second is the sorting `method` , which indicates the _direction_ in which we are going to sort our contours (i.e. left-to-right, top-to-bottom, etc.).

From there we’ll initialize two important variables on **Lines 9 and 10**. These variables simply indicate the sorting order (ascending or descending) and the _index_ of the bounding box we are going to use to perform the sort (more on that later). We’ll initialize these variables to sort in _ascending_ order and along to the _x_\-axis location of the bounding box of the contour.

If we are sorting right-to-left or bottom-to-top, we’ll need to sort in **_descending_** order, according to the location of the contour in the image (**Lines 13 and 14**).

Similarly, on **Lines 18 and 19** we check to see if we are sorting from top-to-bottom or bottom-to-top. If this is the case, then we need to sort according to the _y_\-axis value rather than the _x_\-axis (since we are now sorting vertically rather than horizontally).

The actual sorting of the contours happens on **Lines 23-25**.

We first compute the _bounding boxes_ of each contour, which is simply the starting _(x, y)_\-coordinates of the bounding box followed by the width and height (hence the term “bounding box”). (**Line 23**)

The `boundingBoxes` enable us to sort the actual contours, which we do on **Line 24 and 25** using some Python magic that sorts two lists together. Using this code we are able to sort **_both_** the contours and bounding boxes according to the criteria that we provided.

Finally, we return the (now sorted) list of bounding boxes and contours to the calling function on **Line 28**.

While we’re at it, let’s go ahead and define another helper function, `draw_contour` :

def draw\_contour(image, c, i):
	# compute the center of the contour area and draw a circle
	# representing the center
	M = cv2.moments(c)
	cX = int(M\["m10"\] / M\["m00"\])
	cY = int(M\["m01"\] / M\["m00"\])

	# draw the countour number on the image
	cv2.putText(image, "#{}".format(i + 1), (cX - 20, cY), cv2.FONT\_HERSHEY\_SIMPLEX,
		1.0, (255, 255, 255), 2)

	# return the image with the contour number drawn on it
	return image

This function simply computes the **center** _(x, y)_\-coordinate of the supplied contour `c` on **Lines 33-35** and then uses the center coordinates to draw the contour ID, `i` , on **Lines 38 and 39**.

Finally, the passed in `image` is returned to the calling function on **Line 42**.

Again, this is simply a helper function that we’ll leverage to draw contour ID numbers on our actual image so we can visualize the results of our work.

Now that the helper functions are done, let’s put the driver code in place to take our actual image, detect contours, and sort them:

\# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--image", required=True, help="Path to the input image")
ap.add\_argument("-m", "--method", required=True, help="Sorting method")
args = vars(ap.parse\_args())

# load the image and initialize the accumulated edge image
image = cv2.imread(args\["image"\])
accumEdged = np.zeros(image.shape\[:2\], dtype="uint8")

# loop over the blue, green, and red channels, respectively
for chan in cv2.split(image):
	# blur the channel, extract edges from it, and accumulate the set
	# of edges for the image
	chan = cv2.medianBlur(chan, 11)
	edged = cv2.Canny(chan, 50, 200)
	accumEdged = cv2.bitwise\_or(accumEdged, edged)

# show the accumulated edge map
cv2.imshow("Edge Map", accumEdged)

**Lines 45-48** aren’t very interesting — they simply parse our command line arguments, `--image` which is the path to where our image resides on disk, and `--method` which is a text representation of the direction in which we want to sort our contours.

From there we load our image off disk on **Line 51** and allocate memory for the edge map on **Line 52**.

Constructing the actual edge map happens on **Lines 55-60**, where we loop over each Blue, Green, and Red channel of the image (**Line 55**), blur each channel slightly to remove high frequency noise (**Line 58**), perform edge detection, (**Line 59**), and update the accumulated edge map on **Line 60**.

We display the accumulated edge map on **line 63** which looks like this:

[![Image 3: Figure 1: (Left) Our original image. (Right) The edge map of the Lego bricks.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20544'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/04/sorted_contours_edge_map.jpg)

**Figure 1:** _(Left)_ Our original image. _(Right)_ The edge map of the Lego bricks.

As you can see, we have detected the actual edge outlines of the Lego bricks in the image.

Now, let’s see if we can (1) find the contours of these Lego bricks, and then (2) sort them:

\# find contours in the accumulated image, keeping only the largest
# ones
cnts = cv2.findContours(accumEdged.copy(), cv2.RETR\_EXTERNAL,
	cv2.CHAIN\_APPROX\_SIMPLE)
cnts = imutils.grab\_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)\[:5\]
orig = image.copy()

# loop over the (unsorted) contours and draw them
for (i, c) in enumerate(cnts):
	orig = draw\_contour(orig, c, i)

# show the original, unsorted contour image
cv2.imshow("Unsorted", orig)

# sort the contours according to the provided method
(cnts, boundingBoxes) = sort\_contours(cnts, method=args\["method"\])

# loop over the (now sorted) contours and draw them
for (i, c) in enumerate(cnts):
	draw\_contour(image, c, i)

# show the output image
cv2.imshow("Sorted", image)
cv2.waitKey(0)

Quite obviously, the first step here is to find the actual contours in our accumulated edge map image on **Line 67-69**. We are looking for the _external_ contours of the Lego bricks, which simply corresponds to their _outlines_.

Based on these contours, we are now going to **_sort them according to their size_** by using a combination of the Python `sorted` function and the `cv2.contourArea` method — this allows us to sort our contours according to their area (i.e. size) from largest to smallest (**Line 70**).

We take these sorted contours (in terms of size, _not_ location), loop over them on **Line 74** and draw each individual contour on **Line 76** using our `draw_contour` helper function.

This image is then displayed to our screen on **Line 78**.

However, as you’ll notice, our contours have been sorted only according to their _size_ — no attention has been paid to their actual _location_ in the image.

We address this problem on **Line 81** where we make a call to our custom `sort_contours` function. This method accepts our list of contours along with sorting direction method (provided via command line argument) and sorts them, returning a tuple of sorted bounding boxes and contours, respectively.

Finally, we take these sorted contours, loop over them, draw each individual one, and finally display the output image to our screen (**Lines 84-89**).

Results
-------

Let’s put our hard work to the test.

Open up a terminal, navigate to your source code and execute the following command:

$ python sorting\_contours.py --image images/image\_01.png --method "top-to-bottom"

Your output should look like this:

[![Image 4: Sorting our Lego bricks from top-to-bottom.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20552'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/04/sorted_contours_top_to_bottom.jpg)

**Figure 2:** Sorting our Lego bricks from top-to-bottom.

On the _left_ we have our original unsorted contours. Clearly, we can see that the contours are very much out of order — the first contour is appearing at the very bottom and the second contour at the very top!

However, by applying our `sorted_contours` function we were able to sort our Lego bricks from top-to-bottom.

Let’s take a look at another example.

$ python sorting\_contours.py --image images/image\_01.png --method "bottom-to-top"

[![Image 5: Sorting our contours from bottom-to-top.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20551'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/04/sorting_contours_bottom_to_top.jpg)

**Figure 3:** Sorting our contours from bottom-to-top.

And here we have sorted on contours from bottom-to-top.

Of course, we can also sort contours horizontally as well:

$ python sorting\_contours.py --image images/image\_02.png --method "left-to-right"

[![Image 6: Figure 4: Sorting our contours from left-to-right.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20652'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/04/sorting_contours_left_to_right.jpg)

**Figure 4:** Sorting our contours from left-to-right.

Again, in the _top_ image our contours are not in order. But in the _bottom_ image we are able to successfully sort our contours without an issue.

One last example:

$ python sorting\_contours.py --image images/image\_02.png --method "right-to-left"

[![Image 7: Sorting our contours from right-to-left.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20641'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/04/sorting_contours_right_to_left.jpg)

**Figure 5:** Sorting our contours from right-to-left.

As you can see, there’s nothing to it — we’re simply leveraging the bounding box of each object in the image to sort the contours by direction using Python and OpenCV.

In the future all you need is our trusty `sorted_contours`  function and you’ll always be able to sort contours in terms of direction without a problem.

### 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 this blog article we learned how to sort contours from left-to-right, right-to-left, top-to-bottom, and bottom-to-top.

Realistically, we only needed to leverage two key functions.

The first critical function was the `cv2.boundingRect`  method which computes the bounding box region of the contour. And based on these bounding boxes, we leveraged some Python magic and used our second critical function, `sorted` , to actually “sort” these bounding boxes in the direction we want.

And that’s all there is to it!

If you would like to play with the examples included in this post, just enter your email address in the form below and I’ll email you the code instantly.

![Image 8](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!