Get area within contours Opencv Python?
Asked Answered
S

2

18

I have used an adaptive thresholding technique to create a picture like the one below:

enter image description here

The code I used was:

image = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 45, 0)

Then, I use this code to get contours:

cnt = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]

My goal is to generate a mask using all the pixels within the outer contour, so I want to fill in all pixels within the object to be white. How can I do this?

I have tried the code below to create a mask, but the resulting mask seems no different then the image after applying adaptive threshold

mask = np.zeros(image.shape[:2], np.uint8)
cv2.drawContours(mask, cnt, -1, 255, -1)
Speedometer answered 4/12, 2014 at 16:46 Comment(0)
V
22

What you have is almost correct. If you take a look at your thresholded image, the reason why it isn't working is because your shoe object has gaps in the image. Specifically, what you're after is that you expect that the shoe has its perimeter to be all connected. If this were to happen, then if you extract the most external contour (which is what your code is doing), you should only have one contour which represents the outer perimeter of the object. Once you fill in the contour, then your shoe should be completely solid.

Because the perimeter of your shoe is not complete and broken, this results in disconnected white regions. Should you use findContours to find all of the contours, it will only find the contours of each of the white shapes and not the most outer perimeter. As such, if you try and use findContours, it'll give you the same result as the original image, because you're simply finding the perimeter of each white region inside the image, then filling in these regions with findContours.


What you need to do is ensure that the image is completely closed. What I would recommend you do is use morphology to close all of the disconnected regions together, then run a findContours call on this new image. Specifically, perform a binary morphological closing. What this does is that it takes disconnected white regions that are close together and ensures that they're connected. Use a morphological closing, and perhaps use something like a 7 x 7 square structuring element to close the shoe. This structuring element you can think of as the minimum separation between white regions to consider them as being connected.

As such, do something like this:

import numpy as np
import cv2 
image = cv2.imread('...') # Load your image in here
# Your code to threshold
image = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 45, 0)    

# Perform morphology
se = np.ones((7,7), dtype='uint8')
image_close = cv2.morphologyEx(image, cv2.MORPH_CLOSE, se)

# Your code now applied to the closed image
cnt = cv2.findContours(image_close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
mask = np.zeros(image.shape[:2], np.uint8)
cv2.drawContours(mask, cnt, -1, 255, -1)

This code essentially takes your thresholded image, and applies morphological closing to this image. After, we find the external contours of this image, and fill them in with white. FWIW, I downloaded your thresholded image, and tried this on my own. This is what I get with your image:

enter image description here

Voorhis answered 4/12, 2014 at 18:56 Comment(8)
This is awesome, I can't believe this works! In one afternoon I was able to blur the background of an image. Opencv is pretty cool.Sloven
@Sloven - thanks for the upvote :) Yes, OpenCV is an awesome platform. I use it every day for work. Have fun learning!Voorhis
@Voorhis I tried this code but on the last line it throws the following error drawing.cpp:2380: error: (-215) npoints > 0 in function cv::drawContours - I'm using OpenCV 3.1 and Python 2.7. Any ideas?Taciturnity
@g491. That means there are no contours to draw.Voorhis
@Voorhis I get the error when running it on the image that you ran it on above using the code you posted. Maybe something has changed in the latest OpenCV so it no longer works?Taciturnity
Make sure that image is converted to binary and white is set to 255. Also make sure the image is single channel. The code I wrote assumes any general image but the one posted was already thresholded. Besides that it may be a problem with the new version. I can't say for sure. This code ran fine on OpenCV 2.4.Voorhis
Wow, nice and simple! However, today (2020) thickness=cv2.FILLED doesn't work like that when you have already isolated a single contour. In this case, we will have to pass that contour as ...drawContours(..., [cnt]...) to make it an array again. See this answer.Insanitary
@Insanitary - Thanks for this. This answer was based off of OpenCV 2. This answer is out of date so I will adjust accordingly. Thanks for the upvote!Voorhis
L
3

A simple approach would be to close the holes in the foreground to form a single contour with cv2.morphologyEx() and cv2.MORPH_CLOSE

enter image description here

Now that the external contour is filled, we can find the outer contour with cv2.findContours() and use cv2.fillPoly() to fill in all pixels with white

enter image description here

import cv2

# Load in image, convert to grayscale, and threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Close contour
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)

# Find outer contour and fill with white
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(close, cnts, [255,255,255])

cv2.imshow('close', close)
cv2.waitKey()
Limy answered 2/10, 2019 at 22:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.