【问题标题】:How to fill contours that touch the image border?如何填充触摸图像边框的轮廓?
【发布时间】:2013-12-16 14:49:18
【问题描述】:

假设我从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() 剪掉那个边界:

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

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

【问题讨论】:

    标签: c++ opencv


    【解决方案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));
    

    这两种解决方案都是半途而废的解决方法,但这是我能想到的。

    【讨论】:

    • 我不太确定您是如何获得 iUYue.png 图像的。为什么它在整个图像周围有这个白色边界?
    • 我也不明白,我只是将分水岭函数与我显示的标记图像(我编辑的第一张图像)一起使用。
    • 所以真正的问题不是关闭轮廓,而是移除边缘上所有多余的像素。我想你必须得到所有的轮廓,而不仅仅是外部的,即使用 CV_RETR_LIST。然后您将能够在每个多边形上使用 boundingRect 函数并消除覆盖整个图像的那个。
    • 只是想说声谢谢,也投了赞成票。我在图像的边界上有对象,使用findContours 并没有找到对象中沿边界的所有像素。因此,我用 1 像素边框填充了我的图像,使用了cv::findContours,并确保我的偏移量是cv::Point(-1,-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));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-01
      • 2020-05-23
      • 2011-09-17
      • 1970-01-01
      • 2013-01-01
      • 2013-04-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多