Python OpenCV.fillPoly() not filling my polygons. Why? - python

Using OpenCV for Python, I am trying to get a mask of the noise elements in a image to be later used as input for the cv.inpaint() function.
I am given a greyscale image (2D matrix with values from 0 to 255) in the input_mtx_8u variable, with noise (isolated polygons of very low values).
So far what I did was:
get the edges in which the gradient is above 25:
laplacian = cv2.Laplacian(input_mtx_8u, cv2.CV_8UC1)
lapl_bin, lapl_bin_val = cv2.threshold(laplacian, 25, 255, cv2.THRESH_BINARY)
get the contours of the artifacts
contours, _ = cv2.findContours(lapl_bin_val, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
fill the contours identified
filled_mtx = input_mtx_8u.copy()
cv2.fillPoly(filled_mtx, contours, (255, 255, 0), 4)
For some reason, my 'filled polygons' are not completely filled (see figure).
What can I be doing wrong?

As pointed by #fmw42 , a solution to get the contours filled is using drawContours() instead of fillPoly().
The final working code I got is:
# input_mtx_8u = 2D matrix with uint8 values from 0 to 255
laplacian = cv2.Laplacian(input_mtx_8u, cv2.CV_8UC1)
lapl_bin, lapl_bin_val = cv2.threshold(laplacian, 25, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(lapl_bin_val, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
inpaint_mask = np.zeros(input_mtx_8u.shape, dtype=np.uint8)
for contour in contours:
cv2.drawContours(inpaint_mask, [contour], -1, (255, 0, 0), thickness=-1)
# inpaint_mask = can be used as the mask for cv2.inpaint()
Note that for some reason:
cv2.drawContours(input_mtx_cont, contours, -1, (255, 0, 0), thickness=-1)
does not work. One must loop and draw contour by contour...

Related

Filling the outside of selected Hull Convex with black?

I created a convex hull of a hand from an X-ray. I did this by first creating contours along the edges of the hand. I am wanting to blacken the outside region of convex hull using OpenCV in Python. How do I approach this?
The code below creates the convex hull after doing contours:
img_path = 'sample_image.png'
# Getting the threshold of the image:
image = cv2.imread(img_path)
original = image.copy()
blank = np.zeros(image.shape[:2], dtype = np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 140, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Drawing the contours along the edges of the hand in X-ray:
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
contours = max(contours, key = lambda x: cv2.contourArea(x))
cv2.drawContours(image, [contours], -1, (255,255,0), 2)
# Drawing the hull along the digits along the edges:
hull = cv2.convexHull(contours)
cv2.drawContours(image, [hull], -1, (0, 255, 255), 2)
Input image: Sample_image.png
Result image: Output from the code
Update
Answer given by #AKX below proposed to draw the contour with filled polygon onto a new white blank and then use bitwise_and. The result code:
# Merge text into a single contour
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations = 2)
contours, hierarchy = cv2.findContours(dilate, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = max(contours, key = lambda x: cv2.contourArea(x))
cv2.drawContours(image, [contours], -1, (255,255,0), 2)
plt.imshow(image)
# Created a new mask and used bitwise_and to select for contours:
hull = cv2.convexHull(contours)
mask = np.zeros_like(image)
cv2.drawContours(mask, [hull], -1, (255, 255, 255), -1)
masked_image = cv2.bitwise_and(image, mask)
plt.imshow(masked_image)
Masking
Instead of drawing the hull contour as a line, draw it as a filled polygon onto a blank image with white, then bitwise_and it onto the original so everything outside the polygon is blanked out.
mask = np.zeros_like(image)
cv2.drawContours(mask, [hull], -1, (255, 255, 255), -1)
masked_image = cv2.bitwise_and(image, mask)
cv2.imwrite(r'dog_masked.jpg', masked_image)
input image
(no idea why I called it dog.jpg, it doesn't look like a dog)
result
(the line below the dog remains since it's inside the hull created by the dog's feet)
Cropping
To crop the image to only include the hull's area, use cv2.boundingRect and slice the image array:
x, y, w, h = cv2.boundingRect(hull)
cropped = image[y:y + h, x:x + w]
cv2.imwrite(r'dog_crop.jpg', cropped)
Result

Removing small black contours/unwanted contours in Python

I have been at this all day, how to we remove the small back noise in the red circle? I would need it to work on other samples of pictures like this.
The idea I used is to findContours and then add a mask with all the small black noise that is less than a certain area (trial and error).
Removing noise in red ellipse
image = cv2.imread("11_Image_after_noise_removal.png")
# copy image
img = image.copy()
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0, cv2.THRESH_BINARY)
thresh = 255 - thresh
# Use cv2.CCOMP for two level hierarchy
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_CCOMP,
cv2.CHAIN_APPROX_SIMPLE) # Use cv2.CCOMP for two level hierarchy
cv2.drawContours(img, contours, -1, (0, 255, 0), 1)
cv2.imshow("First detection", img)
# loop through the contours
for i, cnt in enumerate(contours):
# if the contour has no other contours inside of it
if hierarchy[0][i][3] != -1: # basically look for holes
# if the size of the contour is less than a threshold (noise)
if cv2.contourArea(cnt) < 70:
# Fill the holes in the original image
cv2.drawContours(img, [cnt], 0, (0, 0, 0), -1)
# display result
# Visualize the image after the Otsu's method application
cv2.imshow("Image after noise removal", img)
cv2.waitKey(0)
cv2.destroyAllWindows().destroyAllWindows()
You might check the contour area using area = cv.contourArea(cnt) and if it is below some threshold, ignore it.
Here is the OpenCV documentations:
https://docs.opencv.org/4.3.0/dd/d49/tutorial_py_contour_features.html

How to blackout the background of an image after doing contour detection using open in Python?

I've detected contours for an image using opencv python,now I should blackout the image outside the contour.Could anyone help me to do this?
Given your found contours, use drawContours to create a binary mask, in which your contours are filled. Dependent on how you do that (black image, white contours vs. white image, black contours), you set all pixels in your input image to 0 expect for the masked (or unmasked) ones. See the following code snippet for a visualization:
import cv2
import numpy as np
# Artificial input
input = np.uint8(128 * np.ones((200, 100, 3)))
cv2.rectangle(input, (10, 10), (40, 60), (255, 240, 172), cv2.FILLED)
cv2.circle(input, (70, 100), 20, (172, 172, 255), cv2.FILLED)
# Input to grayscale
gray = cv2.cvtColor(input, cv2.COLOR_RGB2GRAY)
# Simple binary threshold
_, gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)
# Find contours
cnts, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Generate mask
mask = np.ones(gray.shape)
mask = cv2.drawContours(mask, cnts, -1, 0, cv2.FILLED)
# Generate output
output = input.copy()
output[mask.astype(np.bool), :] = 0
cv2.imwrite("images/input.png", input)
cv2.imwrite("images/mask.png", np.uint8(255 * mask))
cv2.imwrite("images/output.png", output)
The artificial input image:
The mask generated during processing:
The final output:

OpenCV - visualize polygonal curve(s) extracted with cv2.approxPolyDP()

I want to visualize polygonal curve(s) extracted with cv2.approxPolyDP(). Here's the image I am using:
My code attempts to isolate the main island and define and plot the contour approximation and contour hull. I have plotted the contour found in green, the approximation in red:
import numpy as np
import cv2
# load image and shrink - it's massive
img = cv2.imread('../data/UK.png')
img = cv2.resize(img, None,fx=0.25, fy=0.25, interpolation = cv2.INTER_CUBIC)
# get a blank canvas for drawing contour on and convert img to grayscale
canvas = np.zeros(img.shape, np.uint8)
img2gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# filter out small lines between counties
kernel = np.ones((5,5),np.float32)/25
img2gray = cv2.filter2D(img2gray,-1,kernel)
# threshold the image and extract contours
ret,thresh = cv2.threshold(img2gray,250,255,cv2.THRESH_BINARY_INV)
im2,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# find the main island (biggest area)
cnt = contours[0]
max_area = cv2.contourArea(cnt)
for cont in contours:
if cv2.contourArea(cont) > max_area:
cnt = cont
max_area = cv2.contourArea(cont)
# define main island contour approx. and hull
perimeter = cv2.arcLength(cnt,True)
epsilon = 0.01*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
hull = cv2.convexHull(cnt)
# cv2.isContourConvex(cnt)
cv2.drawContours(canvas, cnt, -1, (0, 255, 0), 3)
cv2.drawContours(canvas, approx, -1, (0, 0, 255), 3)
## cv2.drawContours(canvas, hull, -1, (0, 0, 255), 3) # only displays a few points as well.
cv2.imshow("Contour", canvas)
k = cv2.waitKey(0)
if k == 27: # wait for ESC key to exit
cv2.destroyAllWindows()
Here are the resulting images:
The first image plots the contour in green. The second plots the approximation in red - how do I plot this approximation as a continuous closed curve?
The documentation isn't terribly clear and neither is the tutorial, but my understanding is that cv2.approxPolyDP() should define a continuous, closed curve, which I should be able to plot with cv2.drawContours(). Is that correct? If so, what am I doing wrong?
The problem is in visualization only: drawContours expects array (list in case of python) of contours, not just one numpy array (which is returned from approxPolyDP).
Solution is the following: replacing
cv2.drawContours(canvas, approx, -1, (0, 0, 255), 3)
to
cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 3)
cv2.approxPolyDP()
approx = cv2.approxPolyDP(cnt, 0.03 * cv2.arcLength(cnt, True), True)

OpenCv mean returns 4 element tuple

I have a set of contours defined and filled in OpenCV, and I'm trying to use this as a mask to find the mean intensity in each ROI. I thought I could do this using the cv2.mean function with a defined mask. My code is (im2 is an image read from file):
msk = np.zeros(im2.shape, np.uint8)
cv2.bilateralFilter(im2, 5, 200, 5)
im2 = cv2.GaussianBlur(im2,(5,5),0
binImg = cv2.adaptiveThreshold(im2, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 55, -5)
contours, heir = cv2.findContours(binImg, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(msk, contours, -1, 255, -1)
print len(contours)
print cv2.mean(im2, mask = msk)
This returns:
3361
(155.88012076286788, 0.0, 0.0, 0.0)
I thought that I would get a mean intensity per contour, but it looks like an overall mean intensity for each channel (the image is greyscale). Are my expectations incorrect, or is my code incorrect?
Just to follow up on this (and close it out), I did resolve it by iterating over contours, and using the contours as a mask for the original image. The code is:
msk = np.zeros(im2.shape, np.uint8)
cv2.bilateralFilter(im2, 5, 200, 5)
im2 = cv2.GaussianBlur(im2,(5,5),0)
binImg = cv2.adaptiveThreshold(im2, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 55, -5)
contours, heir = cv2.findContours(binImg, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(msk, contours, -1, 255, -1)
for cnt in contours:
res = np.zeros(img.shape, np.uint8)
(x,y), radius = cv2.minEnclosingCircle(cnt)
ctr = (int(x), int(y))
rad = int(radius)
circ = cv2.circle(res, ctr, rad,1,-1)
print "Area: " + str(cv2.contourArea(cnt)), "Mean: " + str(float(cv2.meanStdDev(img, mask=res)[0]))
It should be noted that I'm using the meanStdDev (I did some editing and wanted to return Std Dev as well), rather than mean, but either should work for finding means. It's still not clear why mean seemed to return 4 results (for 4 channels?) on a greyscale image in the original example.

Categories

Resources