I have this python code that supposedly fills the contours of an image, but leaves the holes contained in it unfilled. This is what I want:
But this is what I get:
I've tried specifying the contour hierarchies for filling with cv2, but I can't get the result I want.
This is what I've tried:
import numpy as np
import cv2
# Load the PNG image
img = cv2.imread('slice.png')
# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
# Find the contours in the binary image
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)
# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
# Check if the contour has a parent
if hierarchy[0][i][3] == -1:
# If the contour doesn't have a parent, fill it with pixel value 255
cv2.drawContours(filled_img, [contour], -1, 255, cv2.FILLED)
# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I've tried modifying the -1, 0, 1 values for the 'if hierarchy[0][i][3] == -1:' part, but it either fills the smaller holes, or fills the entire bigger contour like the first pic I posted.
Update
I also would like to fill with white the inside of lesser hierarchy contours, like this:
The issue is that cv2.drawContours
fills the entire inner part of a closed contour, regardless if there is an inner contour.
Instead of filling the contours without a parent with white, we may start with white contour, and fill the contours without a child with black.
Assuming we know that the inner part should be black, we may apply the following stages:
Code sample:
import numpy as np
import cv2
# Load the PNG image
img = cv2.imread('slice.png')
# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
# Find the outer contours in the binary image (using cv2.RETR_EXTERNAL)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)
# Fill the outer contour with white color
cv2.drawContours(filled_img, contours, -1, 255, cv2.FILLED)
# Find contours with hierarchy, this time use cv2.RETR_TREE
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
# Check if the contour has no child
if hierarchy[0][i][2] < 0:
# If contour has no child, fill the contour with black color
cv2.drawContours(filled_img, [contour], -1, 0, cv2.FILLED)
# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Note:
In case we don't know the color of the most inner contour, we may draw a white contour on a black background, and use the result as a mask - use the mask for copying the original content of the input image.
Support contours that don't have a child:
For supporting both contours that have a child and contours without a child, we may fill with black color, only contours that match both conditions:
Code sample:
import numpy as np
import cv2
# Load the PNG image
img = cv2.imread('slice.png')
# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
# Find the outer contours in the binary image (using cv2.RETR_EXTERNAL)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)
# Fill the outer contour with white color
cv2.drawContours(filled_img, contours, -1, 255, cv2.FILLED)
# Find contours with hierarchy, this time use cv2.RETR_TREE
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
has_grandparent = False
has_parent = hierarchy[0][i][3] >= 0
if has_parent:
# Check if contour has a grandparent
parent_idx = hierarchy[0][i][3]
has_grandparent = hierarchy[0][parent_idx][3] >= 0
# Check if the contour has no child
if hierarchy[0][i][2] < 0 and has_grandparent:
# If contour has no child, fill the contour with black color
cv2.drawContours(filled_img, [contour], -1, 0, cv2.FILLED)
# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Fill with white the inside of lesser hierarchy contours:
Before filling the contour with black color, we may check is the nominated contour has black pixels inside.
Fill with black only if it has no child, has a grandparent and has black inside.
For testing if has black pixels inside we may draw the contour (with white color) over temporary image.
Then check if the minimum value is 0 (value where drawn contour is white).
tmp = np.zeros_like(thresh)
cv2.drawContours(tmp, [contour], -1, 255, cv2.FILLED)
has_innder_black_pixels = (thresh[tmp==255].min() == 0)
Code sample:
import numpy as np
import cv2
# Load the PNG image
img = cv2.imread('slice.png')
# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
# Find the outer contours in the binary image (using cv2.RETR_EXTERNAL)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)
# Fill the outer contour with white color
cv2.drawContours(filled_img, contours, -1, 255, cv2.FILLED)
# Find contours with hierarchy, this time use cv2.RETR_TREE
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
has_grandparent = False
has_parent = hierarchy[0][i][3] >= 0
if has_parent:
# Check if contour has a grandparent
parent_idx = hierarchy[0][i][3]
has_grandparent = hierarchy[0][parent_idx][3] >= 0
# Draw the contour over temporary image first (for testing if it has black pixels inside).
tmp = np.zeros_like(thresh)
cv2.drawContours(tmp, [contour], -1, 255, cv2.FILLED)
has_innder_black_pixels = (thresh[tmp==255].min() == 0) # If the minimum value is 0 (value where draw contour is white) then the contour has black pixels inside
if hierarchy[0][i][2] < 0 and has_grandparent and has_innder_black_pixels:
# If contour has no child and has a grandparent and it has black inside, fill the contour with black color
cv2.drawContours(filled_img, [contour], -1, 0, cv2.FILLED)
# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()