假设我从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
);
到目前为止一切顺利。但是,如果我将上面获得的轮廓传递给cv::drawContours()
,如下所示:
cv::Mat regions(bgr.size(), CV_32S);
cv::drawContours(
regions, contours, -1, cv::Scalar::all(255),
CV_FILLED, 8, hierarchy, INT_MAX
);
这就是我得到的:
最左边的轮廓由cv::findContours()
打开,因此cv::drawContours()
没有填充。
现在我知道这是cv::findContours()
剪掉图像周围的1像素边框的结果(如documentation中所述),但该怎么办呢?放弃轮廓似乎是一种可怕的浪费,因为它碰巧刷掉了图像的边框。无论如何,我怎么能找到属于这个类别的轮廓? cv::isContourConvex()
在这种情况下不是解决方案;一个地区可以concave但是“关闭”,因此没有这个问题。
修改:关于从边框复制像素的建议。问题是我的标记功能也在绘制“背景”中的所有像素,即那些我确定不属于任何对象的区域:
这导致在输出周围绘制边界。如果我以某种方式避免cv::findContours()
剪掉那个边界:
背景的边界与最左边的对象合并:
这会产生一个漂亮的白色盒子。
答案 0 :(得分:7)
解决方案编号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));
请注意,轮廓是从扩展图像中提取的,即它们的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));
这两个解决方案都是半肮脏的解决方法,但这是我能想到的全部。
答案 1 :(得分:2)
按照Burdinov的建议,我想出了下面的代码,它正确地填充了所有提取的区域,同时忽略了全部封闭边界:
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));
}