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:
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:
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:
Zoomed in part you were showing:
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)
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:
Out put:
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:
Giving you this bad segmentation:
Whereas you should be passing the corrected, filled in mask:
labels = watershed(-D, markers, mask=newimg)
Giving you the result you probably expect:
Related Topics
Why Can't Static_Cast Be Used to Down-Cast When Virtual Inheritance Is Involved
Difference in Performance Between Msvc and Gcc for Highly Optimized Matrix Multplication Code
Why Is the Sprite Not Rendering in Opengl
How Does Std::Move() Transfer Values into Rvalues
How to Read-Write Into/From Text File with Comma Separated Values
C++11 Initializer List Fails - But Only on Lists of Length 2
Calling a Function for Each Variadic Template Argument and an Array
Is It Legal to Redefine a C++ Keyword
Why Is It Illegal to Take the Address of an Rvalue Temporary
How to Set a Timeout on Blocking Sockets in Boost Asio
Why Doesn't Narrowing Conversion Used with Curly-Brace-Delimited Initializer Cause an Error
C++ Array Assignment of Multiple Values
Why Does "Extern Const Int N;" Not Work as Expected
Scope of Exception Object in C++