user3158732
user3158732

Reputation: 3

OpenCV Python remove small contours, save child contours

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

Answers (2)

Alex Alex
Alex Alex

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 )

enter image description here

Upvotes: 0

iGian
iGian

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.


You can use the result you currently have as a mask for the original image, but the code need some fixes, starting from the linked image named 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

Related Questions