How to fill the contours that touch the border of the image?

Let's say I have the following binary image created in the output of cv::watershed() :

enter image description here

Now I want to find and fill out the contours, so I can separate the corresponding objects from the background in the original image (which was segmented by the watershed function).

To segment the image and find outlines, I use the following code:

 cv::Mat bgr = cv::imread("test.png"); // Some function that provides the rough outline for the segmented regions. cv::Mat markers = find_markers(bgr); cv::watershed(bgr, markers); cv::Mat_<bool> boundaries(bgr.size()); for (int i = 0; i < bgr.rows; i++) { for (int j = 0; j < bgr.cols; j++) { boundaries.at<bool>(i, j) = (markers.at<int>(i, j) == -1); } } std::vector<std::vector<cv::Point> > contours; std::vector<cv::Vec4i> hierarchy; cv::findContours( boundaries, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE ); 

So far so good. However, if I go through the paths above to cv::drawContours() , as shown below:

 cv::Mat regions(bgr.size(), CV_32S); cv::drawContours( regions, contours, -1, cv::Scalar::all(255), CV_FILLED, 8, hierarchy, INT_MAX ); 

This is what I get:

enter image description here

The leftmost contour remained open on cv::findContours() , and as a result, it was not filled by cv::drawContours() .

Now I know that this is a consequence of cv::findContours() clipping a 1-pixel border around the image (as indicated in the documentation ), but what then? It seems like a terrible waste to drop the outline just because it happened to clear the border of the image. And anyway, how can I find which circuit falls into this category? cv::isContourConvex() not a solution in this case; the region may be concave , but "closed" and therefore does not have this problem.

Edit: On the assumption of duplicating pixels from borders. The problem is that my marking function also paints all the pixels in the "background", i.e. Those regions that, I am sure, are not part of any object:

enter image description here

This causes the border to be drawn around the output. If I somehow avoid cv::findContours() to wring this border:

enter image description here

The border for the background is combined with this leftmost object:

enter image description here

The result is a beautiful field filled with white.

+7
c ++ opencv
source share
2 answers

Following the recommendations of Burdinov, I came up with the code below that correctly fills all the selected areas, ignoring the all-limiting border:

 cv::Mat fill_regions(const cv::Mat &bgr, const cv::Mat &prospective) { static cv::Scalar WHITE = cv::Scalar::all(255); int rows = bgr.rows; int cols = bgr.cols; // For the given prospective markers, finds // object boundaries on the given BGR image. cv::Mat markers = prospective.clone(); cv::watershed(bgr, markers); // Copies the boundaries of the objetcs segmented by cv::watershed(). // Ensures there is a minimum distance of 1 pixel between boundary // pixels and the image border. cv::Mat borders(rows + 2, cols + 2, CV_8U); for (int i = 0; i < rows; i++) { uchar *u = borders.ptr<uchar>(i + 1) + 1; int *v = markers.ptr<int>(i); for (int j = 0; j < cols; j++, u++, v++) { *u = (*v == -1); } } // Calculates contour vectors for the boundaries extracted above. std::vector<std::vector<cv::Point> > contours; std::vector<cv::Vec4i> hierarchy; cv::findContours( borders, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE ); int area = bgr.size().area(); cv::Mat regions(borders.size(), CV_32S); for (int i = 0, n = contours.size(); i < n; i++) { // Ignores contours for which the bounding rectangle's // area equals the area of the original image. std::vector<cv::Point> &contour = contours[i]; if (cv::boundingRect(contour).area() == area) { continue; } // Draws the selected contour. cv::drawContours( regions, contours, i, WHITE, CV_FILLED, 8, hierarchy, INT_MAX ); } // Removes the 1 pixel-thick border added when the boundaries // were first copied from the output of cv::watershed(). return regions(cv::Rect(1, 1, cols, rows)); } 
+1
source share

Solution # 1: Use an image enlarged by one pixel in each direction:

 Mat extended(bgr.size()+Size(2,2), bgr.type()); Mat markers = extended(Rect(1, 1, bgr.cols, bgr.rows)); // all your calculation part std::vector<std::vector<Point> > contours; findContours(boundaries, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); Mat regions(bgr.size(), CV_8U); drawContours(regions, contours, -1, Scalar(255), CV_FILLED, 8, Mat(), INT_MAX, Point(-1,-1)); 

Note that the outlines were extracted from the expanded image, i.e. their x and y values ​​are 1 more from what they should be. This is why I use drawContours with (-1, -1) pixel offset.

Solution # 2: add white pixels from the image border to the adjacent row / column:

 bitwise_or(boundaries.row(0), boundaries.row(1), boundaries.row(1)); bitwise_or(boundaries.col(0), boundaries.col(1), boundaries.col(1)); bitwise_or(boundaries.row(bgr.rows()-1), boundaries.row(bgr.rows()-2), boundaries.row(bgr.rows()-2)); bitwise_or(boundaries.col(bgr.cols()-1), boundaries.col(bgr.cols()-2), boundaries.col(bgr.cols()-2)); 

Both solutions are dirty workarounds, but that's all I could think of.

+6
source share

All Articles