Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Ho do you define the area to be filled from the area that wont ? Using shape, size, position ?

Here my attempts to fills all holes that are smaller than a given size. Add border would be required if black objects touch image border... and many optimization can be done

Get a mask for small details with TOP HAT. size

void FillSmallHolesTopHat()
{
    cv::Mat src, dst, gray, bw, mask;
    src = imread("../img/holes.png");
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

    int size = 9;  // greater than largest hole to fill
    cv::Point anchor = cv::Point(size, size);
    cv::Mat element = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(bw, mask, MORPH_TOPHAT, element, anchor);
    imwrite("../img/topHat.jpg", mask);

image description

    //clear a bit
    cv::Mat kernel = Mat::ones(3, 3, CV_8UC1);
    kernel.at<uchar>(1, 1) = 0;
    morphologyEx(mask, mask, MORPH_ERODE, kernel, cv::Point(1, 1));
    morphologyEx(mask, mask, MORPH_DILATE, kernel, cv::Point(1, 1));
    imwrite("../img/removeIsolated.jpg", mask);

image description

    //Fill holes and mask
    FillHoles(bw, bw);
    imwrite("../img/fillholes.jpg", bw);

image description

    // Get result with logical OR
    bitwise_or(bw, mask, dst);
    imwrite("../img/result-tophat.jpg", dst);
}

image description

alternative using contours

void FillSmallHolesContours()
{
    cv::Mat src, dst, gray, bw, mask;
    src = imread("../img/holes.png");
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

    // Closing the inter space using morph
    int size = 8; // greater than largest hole to fill
    cv::Point anchor = cv::Point(size, size);
    cv::Mat kernel = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(bw, mask, MORPH_ERODE, kernel, anchor);
    imwrite("../img/erode.jpg", mask);

image description

    //Find and and fill inner contours in black
    std::vector<std::vector<cv::Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(mask, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
    int idx = 0;
    mask = 255;//white background mask 
    Scalar color = 0;
    for (; idx >= 0; idx = hierarchy[idx][0])
    {
        bool hasSon = (hierarchy[idx][2]> 0);
        bool hasFather = (hierarchy[idx][3] > 0);
        if ((hasFather == false) && (hasSon == true))continue;
        drawContours(mask, contours, idx, color, CV_FILLED, 8, hierarchy);
    }

    //restore size for filled holes
    size += 2;
    anchor = cv::Point(size, size);
    kernel = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(mask, mask, MORPH_ERODE, kernel, anchor);
    imwrite("../img/innerfill.jpg", mask);

image description

    // get the result using logical and
    bitwise_and(bw, mask, dst);
    imwrite("../img/result-contour.jpg", dst);
}

image description

Ho How do you define the area holes to be filled from the area that wont ? Using shape, size, position ?

Here my attempts to fills all fill in black those white holes that are smaller larger than a given size. Add border would be is required if black objects touch image border... and many optimization can be done

Modified source image (small holes won't be filled) source image:

1st solution

Create a mask for small holes (holes in white) than add them to flooded source:

void FillSmallHolesTopHat(const cv::Mat &src)
{
    cv::Mat dst, gray, bw, mask;
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

    // Get a white mask for small details with TOP HAT. size

void FillSmallHolesTopHat()
{
    cv::Mat src, dst, gray, bw, mask;
    src = imread("../img/holes.png");
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

HAT
    int size = 9;  // hole_to_keep_size = 16;   // just greater than largest smallest hole to do not fill
    int size = hole_to_keep_size / 2; 
    cv::Point anchor = cv::Point(size, size);
    cv::Mat element = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(bw, mask, MORPH_TOPHAT, element, anchor);
    imwrite("../img/topHat.jpg", mask);

image descriptionimage description

 //clear a bit
// Remove very small objects  from the mask
    cv::Mat kernel = Mat::ones(3, 3, CV_8UC1);
    kernel.at<uchar>(1, 1) = 0;
    morphologyEx(mask, mask, MORPH_ERODE, kernel, cv::Point(1, 1));
    morphologyEx(mask, mask, MORPH_DILATE, kernel, cv::Point(1, 1));
    imwrite("../img/removeIsolated.jpg", mask);

image descriptionimage description

 //Fill holes and mask
// FloodFill on source image
    FillHoles(bw, bw);
    imwrite("../img/fillholes.jpg", bw);

image descriptionimage description

    // Get the result with logical OR
    bitwise_or(bw, mask, dst);
    imwrite("../img/result-tophat.jpg", dst);
}

image description

alternative using contoursimage description

2nd solution

Create a mask for large holes (holes in black) and remove them from source.

void FillSmallHolesContours()
FillSmallHolesContours(const cv::Mat &src)
{
    cv::Mat src, dst, gray, bw, mask;
    src = imread("../img/holes.png");
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

    // Closing the inter space Remove small holes (fill them using morph
morph)
    int size = 8; // hole_to_keep_size = 16;   // just greater than largest smallest hole to do not fill
    int size = hole_to_keep_size / 2; 
    cv::Point anchor = cv::Point(size, size);
    cv::Mat kernel = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(bw, mask, MORPH_ERODE, kernel, anchor);
    imwrite("../img/erode.jpg", mask);

image descriptionimage description

 //Find and and fill inner //Get contours in black
of the remaining (large) holes
    std::vector<std::vector<cv::Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(mask, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);

    // Fill inner holes with black
    int idx = 0;
    mask = 255;//white background mask 
    Scalar color = 0;
    for (; idx >= 0; idx = hierarchy[idx][0])
    {
        bool hasSon = (hierarchy[idx][2]> 0);
        bool hasFather = (hierarchy[idx][3] > 0);
        if ((hasFather == false) && (hasSon == true))continue;
        drawContours(mask, contours, idx, color, CV_FILLED, 8, hierarchy);
    }

    //restore // restore size for filled holes
    size += 2;
    anchor = cv::Point(size, size);
    kernel = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(mask, mask, MORPH_ERODE, kernel, anchor);
    imwrite("../img/innerfill.jpg", mask);

image descriptionimage description

    // get the result using logical and
AND
    bitwise_and(bw, mask, dst);
    imwrite("../img/result-contour.jpg", dst);
}

image description

image description

How do you define holes to be filled ? Using shape, size, position ?

EDIT: after user specification:

If there are multiple holes in a blob, verify if one of them is enclosed by the others, in which case do not fill

"is enclosed by the others" isn't so clear to me, btw my idea is to find a convex hull around holes from same blob and fill those holes that touch the hull. Easy to say, less easy to do...

Source image

image description

convex hull in red (single or double holes in blue)

image description

result

image description

here my implementation, some optimization could be done ... I hope it's useful

void FillHolesHull(const cv::Mat &src, cv::Mat &dstBw)
{
    string funName = "FillHolesHull";
    cv::Mat gray, bw, contourMask, hullMask;
#ifdef _DEBUG
    Mat dbg;
    src.copyTo(dbg);
#endif
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    int thr = 200;
    bw = gray < 200;  //THRESH_BINARY_INV: holes in black
    dstBw = 255 - bw; //THRESH_BINARY

    //find contours
    contourMask = Mat::zeros(bw.size(), CV_8UC1);
    hullMask = Mat::zeros(bw.size(), CV_8UC1);
    vector<vector<cv::Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);

    //for each contour create a vector of children
    //and draw the parents contour in its map
    map<int, vector<int> > children;
    int maskTh = 2; //IMPORTANT !!! use 2px to flow contour line into the hole
    for (int i = 0; i < contours.size(); i++)
    {
#ifdef _DEBUG
        //putText(dbg, to_string(i), contours[i][0], 1, 1, color);
#endif
        int child = hierarchy[i][2];
        int parent = hierarchy[i][3];

        if (parent < 0)
            continue;
        children[parent].push_back(i);
        drawContours(contourMask, contours, i, 255, maskTh);
    }

    //for each parent contour 
    map<int, vector<int>>::iterator it;
    for (it = children.begin(); it != children.end(); it++)
    {
        int idx = it->first;
        int cnt = it->second.size();
        //fill contours if have 1 or 2 children
        if ((cnt > 0) && (cnt < 3))
        {
            drawContours(dstBw, contours, idx, 0, CV_FILLED);
#ifdef _DEBUG
            drawContours(dbg, contours, idx, Scalar(255, 0, 0), CV_FILLED);
#endif
            continue;
        }
        // HERE WHEN 3 OR MORE CHILDREN

        // utility: list of children contours for this parent
        vector<int> *brothers = &(it->second); 

        // group all brothers as single contour
        vector<cv::Point> allHolesContour;
        for (int i = 0; i < brothers->size(); i++)
        {
            idx = brothers->at(i); //contour index for i-th brother
            allHolesContour.insert(allHolesContour.end(), contours[idx].begin(), contours[idx].end());
        }
        //create convex hull around all brothers
        vector<Point> hull;
        convexHull(allHolesContour, hull, false);
        //draw the hull in its mask
        cv::polylines(hullMask, hull, true, 255, maskTh);
#ifdef _DEBUG
        cv::polylines(dbg, hull, true, Scalar(0, 0, 255), 1);
#endif
    }

    // locate brothers that touch the convexhull
    // logical AND between thee convexhull and holes itself
    bitwise_and(hullMask, contourMask, contourMask);
    // cut out black pixel in the source 
    bitwise_and(dstBw, contourMask, contourMask);

    // use this mask as seed for flood fill
    uchar *pMask;
    int nr = contourMask.rows;
    int nc = contourMask.cols;
    for (int r = 0; r < nr; ++r)
    {
        pMask = contourMask.ptr<uchar>(r);
        for (int c = 0; c< nc; ++c)
        {
            if (pMask[c]>0)
                floodFill(dstBw, Point(c, r), 0);
        }
    }

    imshow(funName + ":SRC", src);
    imshow(funName + ":DST-BW", dstBw);
#ifdef _DEBUG
    imshow(funName + ":dbg", dbg);
#endif
    return;
}

Old answer and solutions below.

Here my attempts to fill in black those white holes that are larger than a given size. Add border is required if black objects touch image border... and many optimization can be done

Modified source image (small holes won't be filled) source image:

1st solution

Create a mask for small holes (holes in white) than add them to flooded source:

void FillSmallHolesTopHat(const cv::Mat &src)
{
    cv::Mat dst, gray, bw, mask;
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

    // Get a white mask for small details with TOP HAT
    int hole_to_keep_size = 16;   // just greater than smallest hole to do not fill
    int size = hole_to_keep_size / 2; 
    cv::Point anchor = cv::Point(size, size);
    cv::Mat element = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(bw, mask, MORPH_TOPHAT, element, anchor);
    imwrite("../img/topHat.jpg", mask);

image description

    // Remove very small objects  from the mask
    cv::Mat kernel = Mat::ones(3, 3, CV_8UC1);
    kernel.at<uchar>(1, 1) = 0;
    morphologyEx(mask, mask, MORPH_ERODE, kernel, cv::Point(1, 1));
    morphologyEx(mask, mask, MORPH_DILATE, kernel, cv::Point(1, 1));
    imwrite("../img/removeIsolated.jpg", mask);

image description

    // FloodFill on source image
    FillHoles(bw, bw);
    imwrite("../img/fillholes.jpg", bw);

image description

    // Get the result with logical OR
    bitwise_or(bw, mask, dst);
    imwrite("../img/result-tophat.jpg", dst);
}

image description

2nd solution

Create a mask for large holes (holes in black) and remove them from source.

void FillSmallHolesContours(const cv::Mat &src)
{
    cv::Mat dst, gray, bw, mask;
    cv::cvtColor(src, gray, CV_BGR2GRAY);
    bw = gray > 200;

    // Remove small holes (fill them using morph)
    int hole_to_keep_size = 16;   // just greater than smallest hole to do not fill
    int size = hole_to_keep_size / 2; 
    cv::Point anchor = cv::Point(size, size);
    cv::Mat kernel = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(bw, mask, MORPH_ERODE, kernel, anchor);
    imwrite("../img/erode.jpg", mask);

image description

    //Get contours of the remaining (large) holes
    std::vector<std::vector<cv::Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(mask, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);

    // Fill inner holes with black
    int idx = 0;
    mask = 255;//white background mask 
    Scalar color = 0;
    for (; idx >= 0; idx = hierarchy[idx][0])
    {
        bool hasSon = (hierarchy[idx][2]> 0);
        bool hasFather = (hierarchy[idx][3] > 0);
        if ((hasFather == false) && (hasSon == true))continue;
        drawContours(mask, contours, idx, color, CV_FILLED, 8, hierarchy);
    }

    // restore size for filled holes
    size += 2;
    anchor = cv::Point(size, size);
    kernel = getStructuringElement(MORPH_RECT, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(mask, mask, MORPH_ERODE, kernel, anchor);
    imwrite("../img/innerfill.jpg", mask);

image description

    // get the result using logical AND
    bitwise_and(bw, mask, dst);
    imwrite("../img/result-contour.jpg", dst);
}

image description