Fill the Holes in Opencv

Filling holes inside a binary object

There are two methods to do this:

1) Contour Filling:

First, invert the image, find contours in the image, fill it with black and invert back.

des = cv2.bitwise_not(gray)
contour,hier = cv2.findContours(des,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contour:
cv2.drawContours(des,[cnt],0,255,-1)

gray = cv2.bitwise_not(des)

Resulting image:

Sample Image

2) Image Opening:

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
res = cv2.morphologyEx(gray,cv2.MORPH_OPEN,kernel)

The resulting image is as follows:

Sample Image

You can see, there is not much difference in both cases.

NB: gray - grayscale image, All codes are in OpenCV-Python


Reference. OpenCV Morphological Transformations

OpenCV fill holes gives a white image

I used a different approach by finding the contours on the image and using the hierarchy to determine if the contours found are children (there are no holes/contours inside them) then used those contours to fill in the holes. I used the screenshots you uploaded here, please next time try to upload the actual image you are using and not a screenshot.

import cv2
img = cv2.imread('vmHcy.png',0)

cv2.imshow('img',img)

# Find contours and hierarchy in the image
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
drawing = []
for i,c in enumerate(contours):
# Add contours which don't have any children, value will be -1 for these
if hierarchy[0,i,1] < 0:
drawing.append(c)
img = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
# Draw filled contours
cv2.drawContours(img, drawing, -1, (255,255,255), thickness=cv2.FILLED)
# Draw contours around filled areas with red just to indicate where these happened
cv2.drawContours(img, drawing, -1, (0,0,255), 1)
cv2.imshow('filled',img)

cv2.waitKey(0)

Result image with contours shown of the filled areas in red:

Sample Image

Zoomed in part you were showing:

Sample Image

Fill the holes in OpenCV

According to the documentation of imfill in MATLAB:

BW2 = imfill(BW,'holes');

fills holes in the binary image BW.
A hole is a set of background pixels that cannot be reached by filling in the background from the edge of the image.

Therefore to get the "holes" pixels, make a call to cvFloodFill with the left corner pixel of the image as a seed. You get the holes by complementing the image obtained in the previous step.

MATLAB Example:

BW = im2bw( imread('coins.png') );
subplot(121), imshow(BW)

% used here as if it was cvFloodFill
holes = imfill(BW, [1 1]); % [1 1] is the starting location point

BW(~holes) = 1; % fill holes
subplot(122), imshow(BW)

screenshot1
screenshot2

Filling holes in image with OpenCV or Skimage

Here is a function that replaces the color of each pixel with the color that majority of its neighbor pixels have.

import numpy as np
import cv2

def remove_noise(gray, num):
Y, X = gray.shape
nearest_neigbours = [[
np.argmax(
np.bincount(
gray[max(i - num, 0):min(i + num, Y), max(j - num, 0):min(j + num, X)].ravel()))
for j in range(X)] for i in range(Y)]
result = np.array(nearest_neigbours, dtype=np.uint8)
cv2.imwrite('result2.jpg', result)
return result

Demo:

img = cv2.imread('mCOFl.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

remove_noise(gray, 10)

Input image:

Sample Image

Out put:

Sample Image

Note: Since this function replace the color of corner pixels too, you can sue cv2.goodFeaturesToTrack function to find the corners and restrict the denoising for that pixels

corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 30)
corners = np.squeeze(np.int0(corners))

Hole-Filling Filter in OpenCV C++

@QuangHoang advised a fantastic kernel-based approach. Unfortunately, the result is not matching with the expected output.

Therefore, based on various comments from @CrisLuengo, I managed to design time efficient version of the filter, as shown below:

void inPlaceHoleFillingExceptBorderFast(cv::Mat& img) {
typedef uchar T;
const size_t elemStep = img.step1();
const size_t margin = 1;

for (size_t i = margin; i < img.rows - margin; ++i) {
T* ptr = img.data + i * elemStep;
for (size_t j = margin; j < img.cols - margin; ++j, ++ptr) {
T& curr = ptr[margin];
if (curr != 0) {
continue;
}

T& east = ptr[margin + 1];
T& west = ptr[margin - 1];
T& north = ptr[margin - elemStep];
T& south = ptr[margin + elemStep];

// convert to 0/1 and sum them up
uchar count = static_cast<bool>(north) + static_cast<bool>(south) +
static_cast<bool>(east) + static_cast<bool>(west);

// we do not want to divide by 0
if (count > 0) {
curr = (north + south + east + west) / count;
}
}
}
}

Filling holes of an image in python with cv2 not working

Your problem lies in the following line:

labels = watershed(-D, markers, mask=thresh)

You're passing as mask an inverted, uncorrected result from thresholding:

Bad mask

Giving you this bad segmentation:

bad result

Whereas you should be passing the corrected, filled in mask:

labels = watershed(-D, markers, mask=newimg)

Good mask

Giving you the result you probably expect:

good result



Related Topics



Leave a reply



Submit