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.
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
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:
# import the necessary packages import numpy as np import imutils import cv2 def is_contour_bad(c): # approximate the contour peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) # the contour is 'bad' if it is not a rectangle return not len(approx) == 4
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:
# load the shapes image, convert it to grayscale, and edge edges in # the image image = cv2.imread("shapes.png") gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) edged = cv2.Canny(gray, 50, 100) cv2.imshow("Original", image) # find contours in the image and initialize the mask that will be # used to remove the bad contours cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) mask = np.ones(image.shape[:2], dtype="uint8") * 255 # loop over the contours for c in cnts: # if the contour is bad, draw it on the mask if is_contour_bad(c): cv2.drawContours(mask, [c], -1, 0, -1) # remove the contours from the image and show the resulting images image = cv2.bitwise_and(image, image, mask=mask) cv2.imshow("Mask", mask) cv2.imshow("After", image) cv2.waitKey(0)
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 34. Lines 35-37 then display our results.
Results
To execute our script, just issue the following command:
$ python remove_contours.py
First, you’ll see our mask of accumulated contours that will be removed:
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:
Clearly we have removed the circles/ellipses from the image while retaining the rectangles!
What's next? I recommend PyImageSearch University.
30+ total classes • 39h 44m video • Last updated: 12/2021
★★★★★ 4.84 (128 Ratings) • 3,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:
- ✓ 30+ courses on essential computer vision, deep learning, and OpenCV topics
- ✓ 30+ Certificates of Completion
- ✓ 39h 44m on-demand video
- ✓ Brand new courses released every month, 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 500+ tutorials on PyImageSearch
- ✓ Easy one-click downloads for code, datasets, pre-trained models, etc.
- ✓ Access on mobile, laptop, desktop, etc.
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:
- Detecting and finding the contours in an image.
- Looping over each of the contours individually.
- Applying a “test” of some sort to determine if the contour should be removed.
- Accumulating a mask of contours to be removed.
- 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.
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!
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?
I’ve covered this
cv2.findContours
question a lot on the PyImageSearch blog. Please see this post for more information.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.
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.
Thanks for tutorial.
Can you explain how to remove contours having areas smaller than 10pixels and coutours having aspect ratios over 3:1?
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.Cool tutorial! I have just one question. How to eliminate the rectangles and leave only the circles?
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.
Sorry, my comment was confusing. So I would like to detect the circles.
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.if posssible can u please send me this code in c++ .
I only offer code in Python. If you need it in C++, you’ll need to convert the program.
So how do you remove everything “except” the contour?
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.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?
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.Hi, I want to remove the shadow of an image. Can you provide any suggestion pertaining to that?. Thanks
Shadow removal is a very complex task and not easily accomplished. Take a look at this thread for more information.
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
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.
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.
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.
Hello Adrian, How can I achieve the same process as “white” shapes on a “black” background ?
You could use the “cv2.bitwise_not” function to invert the image, process it, then invert it again.
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?
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.
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.
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.
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.
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.
Hey Adrian, Thanks for the tutorial
Can u please explain why the np.zeros are multiplied with 255 in line number 23?
thanks.
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.
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.
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.
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 ! 🙂
Awesome, I’m glad it helped you Esh!
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..!!
Can you be a bit more specific regarding the problem? Do you have any example images you are working with?
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?
The code will work with OpenCV 3, OpenCV 4, etc. That isn’t the issue. What you need is the watershed algorithm.
How could I change the color of the mask to white?
You can use the
cv2.bitwise_not
function.Hello Adrian!
I’m working on diabetic retinopathy detection and to maximize the precision I need to remove the optic disk from retinal fundus images. Any idea of how could I do it? The optic disks look like an ellipse/circle but I’m not sure of what I need to change in the code.
Thank you!
That sounds like a great project; however, I don’t have any tutorials on that topic yet. I may consider it in the future though!
I have tried this code,i Like this but I want to remove black edges and background from my image.so what can I do?can you help me?