Fill the holes in OpenCV

63,767

Solution 1

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

Solution 2

the cvDrawContours function has an option to fill the contours that you have drawn.

Here is a short example cvDrawContours( IplImage, contours, color, color, -1, CV_FILLED, 8 );

Here is the documentation

http://opencv.willowgarage.com/documentation/drawing_functions.html?highlight=cvdrawcontours#cvDrawContours

I guess you posted this a long time ago, but I hope it helps someone.

This is the source code (in C#):

        Image<Gray, byte> image = new Image<Gray, byte>(@"D:\final.bmp");
        CvInvoke.cvShowImage("image 1", image);

        var contours = image.FindContours();
        while (contours != null)
        {
            CvInvoke.cvDrawContours(image, contours, new Gray(255).MCvScalar, new Gray (255).MCvScalar, 0, -1, Emgu.CV.CvEnum.LINE_TYPE.CV_AA, new DPoint(0, 0));
            contours = contours.HNext;
        }
        CvInvoke.cvShowImage("image 2", image);

Result

Solution 3

I've been looking around the internet to find a proper imfill function (as the one in Matlab) but working in C++ with OpenCV. After some reaserches, I finally came up with a solution :

IplImage* imfill(IplImage* src)
{
    CvScalar white = CV_RGB( 255, 255, 255 );

    IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3);
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contour = 0;

    cvFindContours(src, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
    cvZero( dst );

    for( ; contour != 0; contour = contour->h_next )
    {
        cvDrawContours( dst, contour, white, white, 0, CV_FILLED);
    }

    IplImage* bin_imgFilled = cvCreateImage(cvGetSize(src), 8, 1);
    cvInRangeS(dst, white, white, bin_imgFilled);

    return bin_imgFilled;
}

For this: Original Binary Image

Result is: Final Binary Image

The trick is in the parameters setting of the cvDrawContours function: cvDrawContours( dst, contour, white, white, 0, CV_FILLED);

  • dst = destination image
  • contour = pointer to the first contour
  • white = color used to fill the contour
  • 0 = Maximal level for drawn contours. If 0, only contour is drawn
  • CV_FILLED = Thickness of lines the contours are drawn with. If it is negative (For example, =CV_FILLED), the contour interiors are drawn.

More info in the openCV documentation.

There is probably a way to get "dst" directly as a binary image but I couldn't find how to use the cvDrawContours function with binary values.

Solution 4

I made a simple function that is equivalent to matlab's imfill('holes'). I've not tested it for many cases, but it has worked so far. I'm using it on edge images but it accepts any kind of binary image, like from a thresholding operation.

A hole is no more than a set of pixels that cannot be "reached" when background is filled, so,

void fillEdgeImage(cv::Mat edgesIn, cv::Mat& filledEdgesOut) const
{
    cv::Mat edgesNeg = edgesIn.clone();

    cv::floodFill(edgesNeg, cv::Point(0,0), CV_RGB(255,255,255));
    bitwise_not(edgesNeg, edgesNeg);
    filledEdgesOut = (edgesNeg | edgesIn);

    return;
}

Here is an example result

enter image description here

Solution 5

Here's a quick and dirty approach:

  1. Perform canny on your input image so that the new binary image has 1's at the edges, and 0's otherwise
  2. Find the first 0 along a side of your edge image, and initiate a floodfill with 1's at that point on a blank image using your edge image as the mask. (We're hoping here that we didn't get unlucky and seed this first fill on the inside of a shape that is half-off the screen)
  3. This new floodfilled image is the 'background'. Any pixel here that has a 1 is the background, and any pixel that has a 0 is the foreground.
  4. Loop through the image and find any foreground pixels. Seed a floodfill on any you find.
  5. OR this new floodfilled image with your Canny image from step 1, and you're done.
Share:
63,767
Lily
Author by

Lily

A cheerful team player, a thinker and dreamer, a workaholic practitioner.

Updated on August 10, 2020

Comments

  • Lily
    Lily over 3 years

    I have an edge map extracted from edge detection module in OpenCV (canny edge detection). What I want to do is to fill the holes in the edge map.

    I am using C++, and OpenCV libraries. In OpenCV there is a cvFloodFill() function, and it will fill the holes with a seed (with one of the location to start flooding). However, I am trying to fill all the interior holes without knowing the seeds.(similar to imfill() in MATLAB)

    Q1: how to find all the seeds, so that I could apply 'cvFloodFill()'?
    Q2: how to implement a 'imfill()' equivalent?

    Newbie in OpenCV, and any hint is appreciated.