Before we dive into this post, let’s take a second and talk about Oscar, a dedicated PyImageSearch reader.
He is just getting started in computer vision — and he’s taken the best possible route to mastering the subject: creating your own projects and solving them.
Oscar picked up a copy of my book, Practical Python and OpenCV, read through it in a single weekend, and then decided to create a project for himself and solve it.
He is absolute, 100% proof that you can start with very limited (or no) computer vision knowledge and in single weekend learn the skills necessary to build and solve computer vision projects.
Today we are going to discuss Oscar’s first project. He emailed me the image at the top of this post and asked for a little guidance on how to:
- Recognize only figures with the black background
- If 2 or more figures overlap they all should be treated as one object
- Detect and draw contours around each of the black shapes
- Count the number of black shapes
Honestly, this is a great first project.
I replied to Oscar and gave him a few tips on how to solve the problem. I told him what chapters in Practical Python and OpenCV to read, and I suggested a high-level approach to solve the problem.
A day later I found that I had a reply in my inbox — Oscar had solved the problem!
We continued to tweak his code a little more and improve the results.
And today I am going to show the final product.
So let’s give a big hand to Oscar. He’s done a great job. And he’s proof that you can learn computer vision in just a single weekend using Practical Python and OpenCV.
Looking for the source code to this post?
Jump Right To The Downloads SectionFinding Shapes in Images using Python and OpenCV
Let’s go ahead and get started.
Open up a new file, name it find_shapes.py
, and we’ll get to work.
# import the necessary packages import numpy as np import argparse import imutils import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", help = "path to the image file") args = vars(ap.parse_args()) # load the image image = cv2.imread(args["image"])
The first thing we’ll do is import the Python packages we’ll need. We’ll use NumPy for numerical processing, argparse
to parse our command line arguments, and cv2
for our OpenCV bindings. My imutils package has a convenience function we’ll utilize.
Lines 8-10 handle parsing the command line arguments. We need just a single switch, --image
, which is the path to our image on disk.
Finally, we’ll load our image off disk on Line 13.
Now that we have loaded our image off disk, we can move on to detecting the black shapes in the image.
Our Goal: Detect the black shapes in the image.
Detecting these black shapes is actually very easy using the cv2.inRange
function:
# find all the 'black' shapes in the image lower = np.array([0, 0, 0]) upper = np.array([15, 15, 15]) shapeMask = cv2.inRange(image, lower, upper)
On Lines 16 and 17 we define a lower
and an upper
boundary points in the BGR color space. Remember, OpenCV stores images in BGR order rather than RGB.
Our lower
boundary consists of pure black, specifying zeros for each of the Blue, Green, and Red channels, respectively.
And our upper
boundary consists of a very dark shade of gray, this time specifying 15 for each of the channels.
We then find all pixels within the lower and upper range on Line 18.
Our shapeMask
now looks something like this:
As you can see, all the black shapes in the original image are now white on a black background.
The next step is to detect the contours in the shapeMask
. This is also very straightforward:
# find the contours in the mask cnts = cv2.findContours(shapeMask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) print("I found {} black shapes".format(len(cnts))) cv2.imshow("Mask", shapeMask) # loop over the contours for c in cnts: # draw the contour and show it cv2.drawContours(image, [c], -1, (0, 255, 0), 2) cv2.imshow("Image", image) cv2.waitKey(0)
We make a call to cv2.findContours
on Lines 21 and 22, instructing it to find all the external contours of the shapes (i.e. the boundaries).
Due to how various versions of OpenCV handle contours differently we parse the contours on Line 23.
From there, we print to the console the number of contours we found.
Then, we start looping over each of the individual contours on Line 28 and draw the outline of the shapes onto the original image on Line 30.
And that’s all there is to it!
To execute the script, fire up a shell, and issue the following command:
$ python find_shapes.py --image shapes.png I found 6 black shapes
If all goes well, you can now cycle through the black shapes, drawing a green outline around each of them:
As you can see, we have clearly found the black shapes in the image!
And all of this was under 30 lines of code, most of which is imports, comments, and parsing command line arguments.
What's next? We recommend PyImageSearch University.
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:
- ✓ 86 courses on essential computer vision, deep learning, and OpenCV topics
- ✓ 86 Certificates of Completion
- ✓ 115+ 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 540+ tutorials on PyImageSearch
- ✓ Easy one-click downloads for code, datasets, pre-trained models, etc.
- ✓ Access on mobile, laptop, desktop, etc.
Summary
In this post we discussed how to find shapes in images using the cv2.inRange
and cv2.findContours
functions.
This post was largely inspired by Oscar, a dedicated PyImageSearch reader who wrote in and asked how to solve this problem.
You see, Oscar picked up a copy of Practical Python and OpenCV, read through it, and then decided to develop projects on his own to sharpen and hone his skills.
This is by far the best method to learn — and I highly recommend it to anyone else who is interested in computer vision.
Anyway, if you want to be like Oscar and learn the secrets of computer vision (in a single weekend), just click here and pickup a copy of Practical Python and OpenCV. It worked for Oscar and I know it will work for you too!
Thanks again Oscar! And congrats on your first successful OpenCV project!
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!
Lúcio Corrêa
Hi.
There’s an indentation error in lines 29 and 30. That code should be out of the for loop.
Thanks for the articles!
Adrian Rosebrock
Hi Lucio, thanks for the comment.
The code is actually correct. Each of the individual segments are looped over, drawn one-by-one, and then displayed to the user so they can investigate each contour as it is drawn.
Elijah
i just want to ask, can i use this code, if im going to real-time detect an object. sorry a beginner here >.<
Adrian Rosebrock
Hey Elijah, you can definitely use this code to do some basic real-time object detection. I actually detail a similar method inside Practical Python and OpenCV that you would enjoy. Also, be sure to take a look at this post on drone detection where I use basic contour properties to detect targets in video streams of a quadcopter.
pons
hello adrian. i want to ask . im doing an arrow detection, i want to set a certain size of the shape to be detected, cause i need to detect it in a short distance and also im doing it in a video, i just want to set a parameter so it wont detect it when theres a long distance between the arrow and the camera.and thanks, you blog is helful
Adrian Rosebrock
A really easy way to add in a “distance” method is to compute the width or height (in pixels) of the arrow. Arrows that appear larger will be closer to the camera whereas arrows that appear smaller will be farther away from the camera. You could make a quick check on the dimensions of the arrow and if they are too small, then you’ll know they are long distance from your camera and ignore them.
Another more reliable approach would be to compute the distance to the camera directly. I cover that in this blog post.
sammy
can you provide same code (actually i need to find contours of golden color coins shapes) in python.
Adrian Rosebrock
Please take a look at my blog post on object tracking. Inside the post I make reference to the
range-detector
script that you can use to determine the RGB or HSV color of objects in images (in this case, your golden coins).sammy
thanks for the help.. problem is solved.
Adrian Rosebrock
Happy to help Sammy! 🙂
Duc
Hey Sammy,
could you send a link to your github project.
Would like to know how you solved your problem using the llink provided by Adrian.
Thanks a ton!
Adrian Rosebrock
Hey Duc — if you download and execute this script you can use it determine the threshold values for your particular image. I would suggest adding a
print
statement before theq
key is pressed to exit the script so you can see what the actual values are.Theodor
Hi,
I tried your example and on line 20, I got an error: too many values to unpack.
This answer
(http://stackoverflow.com/questions/25504964/opencv-python-valueerror-too-many-values-to-unpack)
suggests that this should be used instead:
(_, contours, _) = cv2.findContours
Adrian Rosebrock
Indeed, the
cv2.findContours
function changed in between OpenCV 2.4 and OpenCV 3. You can read more about this change here.Nicky Fandino
Hey Adrian, it’s me again. Supposed in a paper full of writings and 4 squares, how do I detect only the squares ? Do I need to threshold it so that the writings got “deleted” first before or…?
Adrian Rosebrock
I assume the four squares are on the paper? I would use either thresholding or edge detection to first determine where in the image the paper is. From there only process the paper region to locate the squares.
George
Hi, Adrian!
Really great blog and project, congrats! I’ve started learning CV with help of your blog and lessons.
I have a practical question about surface detection. What steps do I need to detect windows, doors, road? Just a quick example what I want to get http://prntscr.com/e3k9l6 ?
I assume that I can use some shape detection technics described in this post. Or I should train a HAAR object detection classifier for windows, then the same classifier for doors, am I right?
Or maybe I should dig in HOG + Linear SVM direction?
What about detection roads near the house? What is the best way to do this?
Would be great for any help.
Thanks a lot!
Adrian Rosebrock
There are many different ways to approach this problem, but the more important question here is how general of a solution are you looking for? Are you trying to obtain a window/door detector solution that works for every image you encounter across a wide variety of lighting conditions and environments? If so, you’ll want to create a massive training dataset and train a segmentation algorithm on it. Simple object detection will likely not work well here unless you have a fixed view camera that is always viewing a house at the same angle.
George
Thanks for a quick reply, Adrian!
I am looking for a general solution 🙂 But I understand, that this can be a tricky one.
So, as a very first step I want understand in what direction should I go to detect “windows” and “doors” at the same image with the same lightning condition and angle as in my example.
As I’ve read from your blog and book, I need to train classifier on positive/negative examples to detect window, then the same approach I can use for doors. Am I right? Simple Image processing won’t work here, right?
Thank you very much for your help.
Adrian Rosebrock
If you’re looking for a very general solution, then no, unfortunately simple image processing techniques won’t be able to solve this problem. You would need a dataset of positive/negative examples of doors and windows and then train a detector on these examples. The problem is that if you use the HOG + Linear SVM framework, the HOG filter does not handle changes in rotation or viewing angle well. Because of this you might want to consider a deep learning framework such as Faster R-CNN, YOLO, or SSD. I would also consider pixel-wise segmentation algorithms as well.
Aparna
Hi Adrian,
Can you help me modify the same program to detect objects of other colors as well.
Kumar
Hey Adrian,
I am currently working on a small project where I have to identify some irregular shaped impurities among some food grains. What would be a good way to go about doing that? I have already done programming to find out the number of grains and whether the grains are broken or not using an elliptical bounding box, aspect ratio and its minor and major axis lengths. The only thing in which I’m stuck is how to isolate the somewhat similarly shaped food grains from the irregularly shaped impurities, and to mark them as such in the image using the puttext command. Is there any way I can go about this? I have thought of Image segmentation in the RGB domain using Euclidean distance measurement to find the differences in color between them, but I am not sure how to implement it. I am sure there are better ways to do it, and I hope you could point me towards the right direction.
Thank you so much in advance.
Adrian Rosebrock
I don’t have any experience working with food grains, so it would be helpful to see what a “normal food grain” versus an “irregular food grain” looks like. From there I can try to recommend techniques that might be helpful to this problem.
Kumar
https://usercontent1.hubstatic.com/3213720_f520.jpg
The above link would be an accurate shape of a grain. My main objective is to have around 20-30 of these grains, and 3-4 little stones which may be of any shape, and classify them as a grain and stone respectively. The stones will probably be of a comparable or smaller size than the grain.
Adrian Rosebrock
I can see a few ways to go about this problem. If you know the shape of the grain and can segment them, computing basic contour contour properties (aspect ratio, minimum size, maximum size, extent, solidity) should be enough to segment grains from stones. A feature based approach could be used Zernike Moments.
Jing Kai
Hi Adrian,
Would you kindly give me some idea about how to inner join multiple contours? My purpose is to join two ROI and calculate it’s area. Thank you so much!
Adrian Rosebrock
Can you elaborate more on what you mean by “inner join” two contours? Typically you would take the two contours and compute the bounding box of the regions. Or you could simply compute the area of the two contours separately.
Toni
Hello! I would like to ask you if there is another way to extract contours except for using open cv. Also I want to ask if there is any differences between using open cv on my raspberry pi 3 and windows 10. Thank you in advance!
Adrian Rosebrock
There should be no difference between using OpenCV on your Raspberry Pi versus Windows 10 (as long as you’re running the same OpenCV version, of course). As for extracting contours, why do you want to avoid OpenCV for this?
Hiren Shethna
I am looking at reading some predefined shapes, as well as surrounding text to shapes, is such material referenced in your book. Or if you could point me to other places where I could find some.
Adrian Rosebrock
Hey Hiren — I cover the fundamentals of how to detect and extract shapes inside Practical Python and OpenCV. If you have example images of what you’re working with I can provide additional suggestions as well.
Katy
hey Adrian ..im getting an error in line 20 telling that “The lower boundary is neither an array of the same size and same type as src, nor a scalar in function cv::inRange” can you help me sort out this problem.Thank you…!
Adrian Rosebrock
I’m willing to bet that your path to the input image is invalid and “cv2.imread” is returning “None”. Double-check your path to the input image and be sure to read this post on command line arguments.
Katy
i just changed the lower and upper values to 0 and 15 in line 20 and it outputs nothing…no image nor it prints anything can you pls help me…?????
Adrian Rosebrock
Is there a reason why you changed the lower and upper values?
Diego
Thanks for the article!
While I was trying to run the script, there was an error:
File “.\find_shapes.py”, line 24, in
cv2.CHAIN_APPROX_SIMPLE)
ValueError: too many values to unpack
This seems to be because something changed in OpenCV, I fixed by editing line:
(_, cnts, _) = cv2.findContours(shapeMask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
(The edit was to add an _ before the cnts var)
source: http://answers.opencv.org/question/40329/python-valueerror-too-many-values-to-unpack/
And it worked, hope this can be of use to someone. Maybe would be good to edit the article itself.
Adrian Rosebrock
Hey Diego — this is a difference in how the “cv2.findContours” function in OpenCV 2.4 and OpenCV 3 works. I have a more detailed blog post on it here.
ali
Hi adrian
i have a detected object with bounding box. i want to get coordinates of bounding box . how do this work?
Adrian Rosebrock
Did you use the method discussed in this blog post to find the object? If so, you can use the “cv2.boundingRect” function to compute the bounding box.
Daniel
Hey Adrian,
Can something like this work to detect boxes on a shelf? For example boxes of toothpaste? Or is there a better way.
Adrian Rosebrock
This method would only work if you can cleanly segment the foreground objects from the background. This likely isn’t the case for boxes of household goods. For that you should consider training a custom object detector.
Prashant
Hi Adrian,
Any help on detecting hollow shapes or shapes with something else filled in it(like other shapes or text) but not solid colors(only outline of the shape) is appreciated.
Adrian Rosebrock
It really depends on the shape and without seeing exactly what you’re working with it can be hard to provide an exact suggestion but based on what you said you may need a more advanced method, such as object detection. HOG + Linear SVM would be a good start.
Prashant
The shape outlines are actually rectangles with some text in them.
Similar to rectangles in an invoice.
This can also represent tablular data.
Adrian Rosebrock
There are a few ways to handle this but without seeing visual examples you may want to train a simple HOG + Linear SVM detector to see how it performs.
Adnan
Hi Adrian,
Is their any advantage of loading the image using argparse()? We could also do it simply passing the name of the image to the cv2.imread().
Thanks
Adrian Rosebrock
Yes, the advantage is that you don’t need to edit your code if you ever wanted to use a new image. Command line arguments and argument parsing is a skill you really need to have. Read this tutorial for more information.
Geoffrey Landry
Adrian,
I’m trying to find the coordinates of the green triangle that is drawn around the shape. In my project I have a color on screen that I need to click with my mouse. using scaling as you have shown before and other methods (pyautogui) is not great for this, however using this method I can draw the green rectangle around the shape with this specific color every time. Now I just need the coordinates. I can get the center coords from the min max coordinate of the rectangle.
Regards.
Geoff