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
convex hull in red (single or double holes in blue)
result
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 ...
(more)
@PedroBatista you can recover your old user account see
Cool, thanks :)
How about watershed filling as explained in this tutorial? It would respect the outer boundaries and only fill up the inner circles.
The problem of watershed is that I cannot guarantee that I can get an individual seed for each individual object with distanceTransform+threshold (this example is really simple). which wouldn't be such a big issue if the results on a failed instance wouldn't be so unpredictable.
Other problem is that I am running on low CPU resources (Using cortex A9 boards to process a stream of visual data) and all the steps necessary to use watershed are just too heavy for my system capabilities.