Reputation: 3
I'm attempting to remove all but the largest contours while retaining the child contours within the largest contour. Effectively I want to go from here: Input Image to: Output Image I don't know how to go about this though (I'm not a programmer by any stretch and had help putting together the below). The code I do have doesn't retain the child contours. Output of Below.
import cv2 as cv
import numpy as np
img = cv.imread('small_contour.jpg')
image_contours = np.zeros((img.shape[1], img.shape[0], 1), np.uint8)
image_binary = np.zeros((img.shape[1], img.shape[0], 1), np.uint8)
for channel in range(img.shape[2]):
ret, image_thresh = cv.threshold(img[:, :, channel], 127, 255, cv.THRESH_BINARY)
contours = cv.findContours(image_thresh, 1, 1)[0]
cv.drawContours(image_contours, contours, -1, (255,255,255), 3)
contours = cv.findContours(image_contours, cv.RETR_LIST,
cv.CHAIN_APPROX_SIMPLE)[0]
cv.drawContours(image_binary, [max(contours, key = cv.contourArea)],
-1, (255, 255, 255), -1)
cv.imwrite('fill_contour.jpg', image_binary)
cv.imshow('Small Contour', image_binary)
cv.waitKey(0)
Upvotes: 0
Views: 2197
Reputation: 2018
You can use morphological reconstruction, with erode image as marker.
import cv2
img = cv2.imread('VS0L9.jpg', cv2.IMREAD_GRAYSCALE)
thresh = cv2.threshold(img, 40, 255, cv2.THRESH_BINARY)[1]
kernel=cv2.getStructuringElement(cv2.MORPH_RECT, (17,17))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
marker = cv2.erode(thresh,kernel,iterations = 5)
while True:
tmp=marker.copy()
marker=cv2.dilate(marker, kernel2)
marker=cv2.min(thresh, marker)
difference = cv2.subtract(marker, tmp)
if cv2.countNonZero(difference) == 0:
break
cv2.imwrite('out.png', marker)
cv2.imshow('result', marker )
Upvotes: 0
Reputation: 11183
What your code does in this line cv.drawContours(image_binary, [max(contours, key = cv.contourArea)], -1, (255, 255, 255), -1)
is to set to color (255, 255, 255)
the pixels of the image_binary
corresponding to the boundary and inner pixels of the bigger contour found by cv.findContours
.
Creating the image_binary
you are also swapping width with height and this is an error.
You don't need to keep the children: you should use cv.RETR_TREE
for that, then find the index of the father and look for all contours with that father doing it recursively for the nested contours (children of children ...). See https://docs.opencv.org/4.1.0/d9/d8b/tutorial_py_contours_hierarchy.html But this is not useful for your goal.
VS0L9.jpg
.
Note that I'm using cv2
, just change to cv
.
Load the image
im = cv2.imread('VS0L9.jpg', cv2.IMREAD_GRAYSCALE)
Find the contours (I used RETR_TREE)
contours, hierarchy = cv2.findContours(im, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
Find the bigger contour:
bigger = max(contours, key=lambda item: cv2.contourArea(item))
Initialize the mask (what you tried with image_binary
):
the_mask = np.zeros_like(im)
Draw the fill the bigger contour on the mask image, the region of interest is set to (255, 255, 255):
cv2.drawContours(the_mask, [bigger], -1, (255, 255, 255), cv2.FILLED)
At this point the mask looks like the output of your code, but with proper width and height. Use the mask with the original image to show only the area of interest:
res = cv2.bitwise_and(im, im, mask = the_mask)
Or, alternatively:
res = im.copy()
res[the_mask == 0] = 0
Now res
is the desired result.
Upvotes: 2