Finding Shapes in Images using Python and OpenCV

Finding Shapes in Images using Python and OpenCV

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.

Congratulations, Oscar!

Looking for the source code to this post?
Jump right to the downloads section.

OpenCV and Python versions:
This example will run on Python 2.7 and OpenCV 2.4.X.

Finding 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.

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.

Lines 7-9 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 12.

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:

On Lines 15-16 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 17.

Our shapeMask  now looks something like this:

Figure 1: Finding all the black shapes in the image.

Figure 1: Finding all the black shapes in the image.

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:

We make a call to cv2.findContours  on Line 20, instructing it to find all the external contours of the shapes (i.e. the boundaries).

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 26 and draw the outline of the shapes onto the original image on Line 28.

And that’s all there is to it!

To execute the script, fire up a shell, and issue the following command:

The output on your console should look like this:

If all goes well, you can now cycle through the black shapes, drawing a green outline around each of them:

Figure 2: We have successfully found the black shapes in the image.

Figure 2: We have successfully found the black shapes in the image.

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.

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!

Downloads:

If you would like to download the code and images used in this post, please enter your email address in the form below. Not only will you get a .zip of the code, I’ll also send you 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! Sound good? If so, enter your email address and I’ll send you the code immediately!

, , , ,

45 Responses to Finding Shapes in Images using Python and OpenCV

  1. Lúcio Corrêa October 29, 2014 at 6:56 pm #

    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 November 4, 2014 at 6:53 am #

      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.

  2. Elijah July 25, 2015 at 12:30 pm #

    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 July 26, 2015 at 5:35 am #

      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.

  3. pons August 9, 2015 at 2:42 pm #

    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 August 10, 2015 at 6:34 am #

      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.

  4. sammy November 9, 2015 at 10:10 pm #

    can you provide same code (actually i need to find contours of golden color coins shapes) in python.

    • Adrian Rosebrock November 10, 2015 at 6:20 am #

      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).

  5. sammy November 11, 2015 at 12:59 pm #

    thanks for the help.. problem is solved.

    • Adrian Rosebrock November 12, 2015 at 5:47 am #

      Happy to help Sammy! 🙂

  6. Duc November 26, 2015 at 7:00 am #

    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 November 26, 2015 at 7:35 am #

      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 the q key is pressed to exit the script so you can see what the actual values are.

  7. Theodor March 7, 2016 at 4:29 pm #

    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 March 8, 2016 at 4:17 pm #

      Indeed, the cv2.findContours function changed in between OpenCV 2.4 and OpenCV 3. You can read more about this change here.

  8. Nicky Fandino October 7, 2016 at 3:39 am #

    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 October 7, 2016 at 7:19 am #

      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.

  9. George February 2, 2017 at 8:38 am #

    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 February 3, 2017 at 11:13 am #

      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 February 6, 2017 at 5:41 am #

        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 February 7, 2017 at 9:14 am #

          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.

  10. Aparna February 8, 2017 at 8:21 pm #

    Hi Adrian,
    Can you help me modify the same program to detect objects of other colors as well.

  11. Kumar March 23, 2017 at 7:05 am #

    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 March 23, 2017 at 9:21 am #

      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 March 24, 2017 at 1:13 am #

        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 March 25, 2017 at 9:32 am #

          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.

  12. Jing Kai April 21, 2017 at 2:25 am #

    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 April 21, 2017 at 10:47 am #

      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.

  13. Toni July 28, 2017 at 11:47 am #

    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 August 1, 2017 at 9:53 am #

      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?

  14. Hiren Shethna February 15, 2018 at 9:06 pm #

    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 February 18, 2018 at 9:55 am #

      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.

  15. Katy March 17, 2018 at 10:55 am #

    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 March 19, 2018 at 5:22 pm #

      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.

  16. Katy March 17, 2018 at 11:25 am #

    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 March 19, 2018 at 5:21 pm #

      Is there a reason why you changed the lower and upper values?

  17. Diego March 18, 2018 at 10:48 pm #

    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 March 19, 2018 at 5:02 pm #

      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.

  18. ali May 14, 2018 at 10:34 am #

    Hi adrian
    i have a detected object with bounding box. i want to get coordinates of bounding box . how do this work?

    • Adrian Rosebrock May 14, 2018 at 11:43 am #

      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.

  19. Daniel June 8, 2018 at 10:04 pm #

    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 June 13, 2018 at 6:11 am #

      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.

  20. Prashant July 11, 2018 at 4:30 am #

    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 July 13, 2018 at 5:19 am #

      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 July 16, 2018 at 1:45 am #

        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 July 17, 2018 at 7:23 am #

          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.

Leave a Reply