如何填充接触图像边框的轮廓? [英] How to fill contours that touch the image border?

查看:52
本文介绍了如何填充接触图像边框的轮廓?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Say I have the following binary image created from the output of cv::watershed():

现在我想找到并填充轮廓,这样我就可以从原始图像中的背景中分离出相应的对象(由分水岭函数分割).

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

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

To segment the image and find the contours I use the code below:

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() 如下:

So far so good. However, if I pass the contours acquired above to cv::drawContours() as below:

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() 填充.

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

现在我知道这是 cv::findContours() 剪掉图像周围 1 像素边框的结果(如 documentation),但是接下来该怎么办?仅仅因为它碰巧擦掉了图像的边界而丢弃轮廓似乎是一种可怕的浪费.无论如何,我怎样才能找到属于这一类的轮廓?cv::isContourConvex() 在这种情况下不是解决方案;一个区域可以但封闭",因此没有这个问题.

Now I know this is a consequence of cv::findContours() clipping off the 1-pixel border around the image (as mentioned in the documentation), but what to do then? It seems an awful waste to discard a contour just because it happened to brush off the image's border. And anyway how can I even find which contour(s) fall in this category? cv::isContourConvex() is not a solution in this case; a region can be concave but "closed" and thus not have this problem.

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

About the suggestion to duplicate the pixels from the borders. The problem is that my marking function is also painting all pixels in the "background", i.e. those regions I'm sure aren't part of any object:

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

This results in a boundary being drawn around the output. If I somehow avoid cv::findContours() to clip off that boundary:

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

The boundary for the background gets merged with that leftmost object:

这会产生一个漂亮的白色填充框.

Which results in a nice white-filled box.

推荐答案

按照 Burdinov 的建议,我想出了下面的代码,它正确地填充了所有提取的区域,同时忽略了所有封闭的边界:

Following Burdinov's suggestions I came up with the code below, which correctly fills all extracted regions while ignoring the all-enclosing boundary:

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

这篇关于如何填充接触图像边框的轮廓?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆