假设我从输出中创建了以下二进制图像cv::watershed():

现在我想找到并填充轮廓,因此我可以将相应的对象与原始图像中的背景分开(由分水岭函数分割).
要分割图像并找到轮廓,我使用下面的代码:
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
);
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.但是,如果我将上面获得的轮廓传递给cv::drawContours()如下:
cv::Mat regions(bgr.size(), CV_32S);
cv::drawContours(
regions, contours, -1, cv::Scalar::all(255),
CV_FILLED, 8, hierarchy, INT_MAX
);
Run Code Online (Sandbox Code Playgroud)
这就是我得到的:

最左边的轮廓被打开cv::findContours(),因此它没有被填充cv::drawContours().
现在我知道这是cv::findContours()剪掉图像周围的1像素边框的结果(如文档中所述),但该怎么办呢?放弃轮廓似乎是一种可怕的浪费,因为它碰巧刷掉了图像的边框.无论如何,我怎么能找到属于这个类别的轮廓?cv::isContourConvex()在这种情况下不是解决方案; 一个区域可以是凹的但是"封闭",因此没有这个问题.
编辑:关于从边框复制像素的建议.问题是我的标记功能也在绘制"背景"中的所有像素,即那些我确定不属于任何对象的区域:

这导致在输出周围绘制边界.如果我以某种方式避免cv::findContours()削减该边界:

背景的边界与最左边的对象合并:

这导致一个漂亮的白色填充框.
解决方案编号1:使用在每个方向上延伸一个像素的图像:
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));
Run Code Online (Sandbox Code Playgroud)
请注意,轮廓是从扩展图像中提取的,即它们的x和y值比它们应该大1.这就是我使用带有(-1,-1)像素偏移的drawContours的原因.
解决方案编号2:从图像边界添加白色像素到相邻行/列:
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));
Run Code Online (Sandbox Code Playgroud)
这两种解决方案都是半肮脏的解决方法,但这是我能想到的.
根据布尔迪诺夫的建议,我想出了下面的代码,它正确填充了所有提取的区域,同时忽略了所有封闭的边界:
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));
}
Run Code Online (Sandbox Code Playgroud)