Removing contours from an image using Python and OpenCV

remove_contours_header

Awhile back I was going through /r/computervision when I stumbled across a question asking how to remove contours from an image using OpenCV.

Intrigued, I posted a reply. The basic algorithm for removing contours from an image goes something like this:

  • Step 1: Detect and find contours in your image.
  • Step 2: Loop over contours individually.
  • Step 3: Determine if the contour is “bad” and should be removed according to some criterion.
  • Step 4: Accumulate a mask of “bad” contours to be removed.
  • Step 5: Apply the accumulated mask of bad contours to the original image using a bitwise ‘and’.

And that’s it!

The algorithm itself is very straightforward, the main step that you need to pay attention to and consider is Step 3, determining if a contour should be removed.

This step could be very simple — or it also could be quite hard, it really depends on your application. And while it’s impossible for me to guess the criterion as to why you want to remove a contoured region from an image, the remainder of this blog post will demonstrate a toy example that you can use to remove contours from an image. From here, you’ll be able to take this code and modify the contour removal criterion according to your own needs.

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/Python 3.4+ and OpenCV 2.4.X.

Removing contours from an image using Python and OpenCV

shapes

Figure 1: Our example toy image. Our goal is to remove all circles/ellipses while retaining the rectangles.

In this toy example our goal is to remove the circles/ellipses from the image above while retaining the rectangles. We’ll accomplish this by applying a test to every contour to determine if it should be removed or not.

Anyway, let’s go ahead and get this example started. Open up a new file, name it remove_contours.py , and let’s get coding:

The first thing we’ll do is import our necessary packages. We’ll use NumPy for numerical processing and cv2  for our OpenCV bindings.

We then define our is_contour_bad  function on Line 6. This function handles the implementation of Step 3 above and defines the criterion under which a contour should be marked as “bad” and removed from an image.

The  is_contour_bad  function requires a single argument, c , which is the contour we are going to test to determine if it should be removed or not.

Remember, in our toy example image above, our goal is to remove the circles/ellipses, while keeping the rectangles intact.

So let’s take a second to consider if we can exploit the geometry of this problem.

A rectangle has 4 sides. And a circle has no sides.

So if we approximate the contour and then examine the number of points within the approximated contour, we’ll be able to determine if the contour is a square or not!

And that’s exactly what Lines 7-11 do. We first approximate the contour on Lines 8 and 9, while Line 12 returns a boolean, indicating whether the contour should be removed or not. In this case, the contour will be kept if the approximation has 4 points (vertices), indicating that the contour is a rectangle.

Let’s finish implementing the other steps to solve this problem:

In order to find and detect the contours in our image (Step 1), we preprocess our image on Lines 16-19 by loading it from disk, converting it to grayscale and detecting edges.

Finding the actual contours happens on Line 23 by making a call to cv2.findContours . Subsequently we handle grabbing contours with different versions of OpenCV (Line 24).

We then initialize a mask  on Line 25 to store our accumulated “bad” contours. We’ll be using this mask along with bitwise operations later on in the code to perform the actual removal of the contour.

Now we can move on to Step 2, looping over the individual contours which happens on Line 28.

For each of the contours we make a call to is_contour_bad  on Line 30, and if the contour is indeed “bad”, then we accumulate our contours to be removed on Line 31 by drawing the contour on our mask.

Finally, all we have to do is apply a bitwise ‘and’ to the image using the accumulated mask to remove the contours on Line 34Lines 35-37 then display our results.

Results

To execute our script, just issue the following command:

First, you’ll see our mask of accumulated contours that will be removed:

Figure 2: Our accumulated mask of contours to be removed. Shapes to be removed appear as black whereas the regions of the image to be retained are white.

Figure 2: Our accumulated mask of contours to be removed. Shapes to be removed appear as black whereas the regions of the image to be retained are white.

Notice how the contours appear as black shapes on a white background. This is because the black shapes will be removed from the original image while the white regions will be retained once we apply the cv2.bitwise_and  function.

And here is the output after applying the accumulated mask:

Figure 3: We have successfully removed the circles/ellipses while retaining the rectangles.

Figure 3: We have successfully removed the circles/ellipses while retaining the rectangles.

Clearly we have removed the circles/ellipses from the image while retaining the rectangles!

Summary

In this blog post I showed you how to remove contoured regions from an image using Python and OpenCV. Removing contours from an image is extremely straightforward and can be accomplished using the following 5 steps:

  1. Detecting and finding the contours in an image.
  2. Looping over each of the contours individually.
  3. Applying a “test” of some sort to determine if the contour should be removed.
  4. Accumulating a mask of contours to be removed.
  5. Applying the mask to the original image.

To apply this algorithm to your own images you’ll need to take a second and consider Step 3 and determine the criterion you are using to remove contours. From there, you can apply the rest of the algorithm as-is.

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!

42 Responses to Removing contours from an image using Python and OpenCV

  1. Nick October 8, 2015 at 4:07 pm #

    On line 22 of this example’s code:
    (cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    I was getting a ValueError: too many values to unpack

    Once I changed it to:
    (_, cnts, _) = cv2.findContours

    It worked fine. I was wondering what what exactly does findContours return?

    • Adrian Rosebrock October 9, 2015 at 6:46 am #

      I’ve covered this cv2.findContours question a lot on the PyImageSearch blog. Please see this post for more information.

  2. Delmira October 30, 2015 at 5:13 am #

    Thanks for the tutorial.

    After I have found my contour, e.g. an ellipse, how would I be able to access all the pixels witihin the contour?

    My goal is to slice/crop the original image; such that only the contour is displayed.

    • Adrian Rosebrock November 3, 2015 at 10:33 am #

      Hey Delma — in that case, simply loop over each contour, find the contour you want, construct a mask for the contour, and then grab all pixels from the mask. I demonstrate masking in a variety of posts on PyImageSearch, but I think the most comprehensive example is in Step 1 of of this post.

  3. qwerty1234 February 8, 2016 at 11:49 pm #

    Thanks for tutorial.
    Can you explain how to remove contours having areas smaller than 10pixels and coutours having aspect ratios over 3:1?

    • Adrian Rosebrock February 9, 2016 at 3:59 pm #

      Loop over the contours and compute the area using cv2.contourArea and see if the area is less than 10. The aspect ratio is simply the contour bounding box width divided by the height. You can see an example of computing the aspect ratio in this post.

  4. Gabe June 23, 2016 at 12:00 pm #

    Cool tutorial! I have just one question. How to eliminate the rectangles and leave only the circles?

    • Adrian Rosebrock June 23, 2016 at 1:03 pm #

      I’m not sure what you mean by “eliminate”, but if I understand your question correctly, you want to actually remove the rectangles from the image? If so, this blog post shows how to remove the rectangles from the image.

      • Gabe June 24, 2016 at 3:44 am #

        Sorry, my comment was confusing. So I would like to detect the circles.

        • Adrian Rosebrock June 25, 2016 at 1:34 pm #

          If you want to detect the circles rather than the squares, just change Line 11 to return len(approx) == 4 This will find squares, eliminate them, and leave you with the circles.

  5. stelaraag April 18, 2017 at 8:31 am #

    if posssible can u please send me this code in c++ .

    • Adrian Rosebrock April 19, 2017 at 12:48 pm #

      I only offer code in Python. If you need it in C++, you’ll need to convert the program.

  6. Tim September 26, 2017 at 4:46 pm #

    So how do you remove everything “except” the contour?

    • Adrian Rosebrock September 28, 2017 at 9:26 am #

      Construct a mask for the contour (i.e., draw it as a white blob on a binary image) and apply a cv2.bitwise_and. Masking is the key here.

  7. Aluizio Rocha October 1, 2017 at 9:37 am #

    Thanks for this tutorial, Adrian. If I write a code to count the number of contours found by the method cv2.findContours I get a total of 24. But, we have 12 shapes in figure only. Why this double counting?

    • Adrian Rosebrock October 2, 2017 at 9:43 am #

      It sounds like there is a problem with either (1) your segmentation of the image via edge detection or thresholding or (2) the flag parameters you are passing into cv2.findContours. Without seeing either I’m not sure what the exact issue is.

  8. SHOBA October 22, 2017 at 12:52 am #

    Hi, I want to remove the shadow of an image. Can you provide any suggestion pertaining to that?. Thanks

    • Adrian Rosebrock October 22, 2017 at 8:23 am #

      Shadow removal is a very complex task and not easily accomplished. Take a look at this thread for more information.

  9. YSS November 11, 2017 at 9:36 pm #

    Sir, i was having a problem that i want to compare two same object of different image and say weather they are equal,small or large for example there is a triangle compare with the different image and first find triangle and say weather is it is small, equal or large

    • Adrian Rosebrock November 13, 2017 at 2:04 pm #

      I would suggest detecting each shape (likely via simple image processing techniques such as edge detection, thresholding, etc.) and then compute the bounding box or bounding circle of the object. You’ll then be able to determine if the shape is smaller, larger, etc.

  10. Erik April 15, 2018 at 2:25 pm #

    Hello.
    How could I remove the largest contour or edge of some object (for example green circle with black edge http://bur.sk/inkscape/circle.png ) and get only inside area (in example: only green circle without edges).
    Thanks a lot.

    • Adrian Rosebrock April 16, 2018 at 2:24 pm #

      There are a few ways to accomplish this. Perhaps the simplest method is to find the black edge, compute the mask, and then apply a series of erosions via “cv2.erode” to remove the black edge.

  11. Caner April 30, 2018 at 3:56 pm #

    Hello Adrian, How can I achieve the same process as “white” shapes on a “black” background ?

    • Adrian Rosebrock May 3, 2018 at 10:09 am #

      You could use the “cv2.bitwise_not” function to invert the image, process it, then invert it again.

  12. AnRes August 9, 2018 at 11:31 am #

    Hi,

    I have a few image frames in which I have detected the moving objects and marked them inside green color rectangular boxes using the basic motion detection application provided by you. I would like to remove all the background outside those boxes and retain only the objects detected(which are inside the rectangular boxes). Could you please tell how do I do that?

    • Adrian Rosebrock August 9, 2018 at 2:37 pm #

      You can utilize masking. Draw the bounding box of the mask then use “cv2.bitwise_and” to mask out regions that are outside your bounding box. If you’re new to OpenCV basics, including how to perform masking, I would suggest you first work through Practical Python and OpenCV.

  13. SKR September 17, 2018 at 11:38 pm #

    Hi Adrian, thanks for the tutorial. I have three questions regarding removal of contours of non-solid non-classic shapes. Images are in the link https://imgur.com/21zWk4A
    1) I have a text enclosed inside a circle with one horizontal diameter shown. How can I remove the circle and the diameter but not the text?

    2) I have the text enclosed inside a circle which in turn is enclosed inside the smallest square. How can I remove the circle and the square but not the text?

    3) You explain about the classic shapes. What if there are custom shapes such as two different sized rectangles attached through lines OR a rectangle with semicircles on either sides? Do you know about any template matching method which can work or should we use shape descriptors?

    Thanks a lot for taking out time to respond.

    • Adrian Rosebrock September 18, 2018 at 7:17 am #

      1. You should look into the Hough Circles and Hough Lines algorithm. Both will help you detect and remove circles and lines.

      2. See above. Circle detection followed by square detection. This post on shape detection will also help you.

      3. Template matching is built into OpenCV. See this post for more details.

      • SKR September 20, 2018 at 1:47 pm #

        Hey Adrian, congratulations on your marriage and I wish you and your wife a very happy life ahead and enjoy your HM.

        1) Very helpful tutorial on circle detection, quick question: since it is really tricky to tune Hough circle parameters is there any way to accurately detect circles WITHOUT looking at the image? May be through some pre- or post-processing and doing multiple passes over the image, for example taking the mean of the radius of all circles and then changing the range in the next pass?

        2) Do you have by any chance a tutorial where you handle shapes which are embedded inside other shapes? For a circle inside a square, either is detected but not both and since they touch each other I risk removing a part of either shape.

        3) Do you have any resources on shape descriptors? If you look at the equipment 01001 and 01001A in the image do you think I should work on inferring out their shape descriptors in order to detect them?
        https://imgur.com/21zWk4A

        Many thanks for your responses.

        • Adrian Rosebrock October 8, 2018 at 1:14 pm #

          I would suggest you look into training your own custom object detector. HOG + Linear SVM can work very well for basic shape detection. For what it’s worth, I cover how to train your own custom object detectors inside the PyImageSearch Gurus course.

  14. Blesson October 19, 2018 at 5:55 am #

    Hey Adrian, Thanks for the tutorial

    Can u please explain why the np.zeros are multiplied with 255 in line number 23?
    thanks.

    • Adrian Rosebrock October 20, 2018 at 7:32 am #

      Double-check that line of code again. It’s not np.zeros, it’s np.ones. We are creating as mask with white (255) for all values in the mask.

  15. Aveshin Naidoo November 1, 2018 at 3:56 pm #

    Good day. I am trying to detect contours in an image that contains words, some of the words are intersected by borders of the image. I would like to remove the contours of words that are cutoff by the edges of the image and only keep the words that are whole. I read a suggestion that stated I should compare the contour bounding box to to the edges of the image, thus confirming if there is an overlap but I’m not entirely sure about how to actually implement this. Thank you.

    • Adrian Rosebrock November 2, 2018 at 7:14 am #

      Hey Aveshin — do you have an example image of what you are working with? That would be better to help understand exactly what you’re trying to accomplish.

  16. Esh November 6, 2018 at 2:49 am #

    Works like magic. Thanks. I am Building something using image processing that will help people. Was stuck in removing unwanted contours in my image for dayssss ! This worked ! The world needs more people like you ! 🙂

    • Adrian Rosebrock November 6, 2018 at 1:05 pm #

      Awesome, I’m glad it helped you Esh!

  17. Sourav Jyoti February 7, 2019 at 5:36 am #

    Hey Adrian Rosebrock, I am trying to remove smaller objects from a binary image(thresholded image) using python opencv but not yet finding a way. Can you please help me..!!

    • Adrian Rosebrock February 7, 2019 at 6:50 am #

      Can you be a bit more specific regarding the problem? Do you have any example images you are working with?

  18. Kevin March 16, 2019 at 1:24 pm #

    Hi Adrian,

    can you please update this code with OpenCV 3.X version, as I am not able to detect image border touch object with this code. Is there any other solution?

    • Adrian Rosebrock March 19, 2019 at 10:12 am #

      The code will work with OpenCV 3, OpenCV 4, etc. That isn’t the issue. What you need is the watershed algorithm.

  19. Adam Graham April 13, 2019 at 6:06 pm #

    How could I change the color of the mask to white?

    • Adrian Rosebrock April 18, 2019 at 7:31 am #

      You can use the cv2.bitwise_not function.

Before you leave a comment...

Hey, Adrian here, author of the PyImageSearch blog. I'd love to hear from you, but before you submit a comment, please follow these guidelines:

  1. If you have a question, read the comments first. You should also search this page (i.e., ctrl + f) for keywords related to your question. It's likely that I have already addressed your question in the comments.
  2. If you are copying and pasting code/terminal output, please don't. Reviewing another programmers’ code is a very time consuming and tedious task, and due to the volume of emails and contact requests I receive, I simply cannot do it.
  3. Be respectful of the space. I put a lot of my own personal time into creating these free weekly tutorials. On average, each tutorial takes me 15-20 hours to put together. I love offering these guides to you and I take pride in the content I create. Therefore, I will not approve comments that include large code blocks/terminal output as it destroys the formatting of the page. Kindly be respectful of this space.
  4. Be patient. I receive 200+ comments and emails per day. Due to spam, and my desire to personally answer as many questions as I can, I hand moderate all new comments (typically once per week). I try to answer as many questions as I can, but I'm only one person. Please don't be offended if I cannot get to your question
  5. Do you need priority support? Consider purchasing one of my books and courses. I place customer questions and emails in a separate, special priority queue and answer them first. If you are a customer of mine you will receive a guaranteed response from me. If there's any time left over, I focus on the community at large and attempt to answer as many of those questions as I possibly can.

Thank you for keeping these guidelines in mind before submitting your comment.

Leave a Reply

[email]
[email]