首页 文章

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

提问于
浏览
7

假设我从 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() 填充 .

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

Edit: 关于从边框复制像素的建议 . 问题是我的标记功能也在绘制"background"中的所有像素,即任何对象的一部分:

enter image description here

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

enter image description here

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

enter image description here

这导致一个漂亮的白色填充框 .

2 回答

  • 6

    解决方案编号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

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

相关问题