如何填充触摸图像边框的轮廓?

时间:2013-12-06 07:26:52

标签: c++ opencv

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

enter image description here

现在我想找到并填充轮廓,因此我可以将相应的对象与原始图像中的背景分开(由分水岭函数分割)。

要分割图像并找到轮廓,我使用下面的代码:

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
);

这就是我得到的:

enter image description here

最左边的轮廓由cv::findContours()打开,因此cv::drawContours()没有填充。

现在我知道这是cv::findContours()剪掉图像周围的1像素边框的结果(如documentation中所述),但该怎么办呢?放弃轮廓似乎是一种可怕的浪费,因为它碰巧刷掉了图像的边框。无论如何,我怎么能找到属于这个类别的轮廓? cv::isContourConvex()在这种情况下不是解决方案;一个地区可以concave但是“关闭”,因此没有这个问题。

修改:关于从边框复制像素的建议。问题是我的标记功能也在绘制“背景”中的所有像素,即那些我确定不属于任何对象的区域:

enter image description here

这导致在输出周围绘制边界。如果我以某种方式避免cv::findContours()剪掉那个边界:

enter image description here

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

enter image description here

这会产生一个漂亮的白色盒子。

2 个答案:

答案 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));
}