33

我有一个从OpenCV中的边缘检测模块中提取的边缘图(canny edge detection)。我想要做的是填补边缘图中的洞。

我正在使用C++OpenCV库。在 OpenCV 中有一个cvFloodFill()函数,它将用种子填充孔洞(其中一个位置开始泛滥)。但是,我试图在不知道种子的情况下填充所有内部孔。(类似于MATLAB中的imfill() )

Q1:如何找到所有的种子,以便我可以应用'cvFloodFill()'?
Q2:如何实现'imfill()'等价物?

OpenCV中的新手,任何提示都表示赞赏。

4

9 回答 9

35

根据imfillMA​​TLAB 中的文档:

BW2 = imfill(BW,'holes');

填充二进制图像中的孔BW。孔洞是一组背景像素,无法通过从图像边缘填充背景来达到。

cvFloodFill因此,要获取“孔”像素,请使用图像的左角像素作为种子进行调用。您可以通过补充上一步中获得的图像来获得孔。

MATLAB 示例:

BW = im2bw( imread('coins.png') );
subplot(121), imshow(BW)

% used here as if it was cvFloodFill
holes = imfill(BW, [1 1]);    % [1 1] is the starting location point

BW(~holes) = 1;               % fill holes
subplot(122), imshow(BW)

截图1 截图2

于 2009-11-11T18:02:16.837 回答
12

cvDrawContours 函数有一个选项来填充您绘制的轮廓。

这是一个简短的示例 cvDrawContours(IplImage, contours, color, color, -1, CV_FILLED, 8);

这是文档

http://opencv.willowgarage.com/documentation/drawing_functions.html?highlight=cvdrawcontours#cvDrawContours

我猜你很久以前就发布了这个,但我希望它对某人有所帮助。

这是源代码(在 C# 中):

        Image<Gray, byte> image = new Image<Gray, byte>(@"D:\final.bmp");
        CvInvoke.cvShowImage("image 1", image);

        var contours = image.FindContours();
        while (contours != null)
        {
            CvInvoke.cvDrawContours(image, contours, new Gray(255).MCvScalar, new Gray (255).MCvScalar, 0, -1, Emgu.CV.CvEnum.LINE_TYPE.CV_AA, new DPoint(0, 0));
            contours = contours.HNext;
        }
        CvInvoke.cvShowImage("image 2", image);

结果

于 2010-07-30T18:21:30.427 回答
8

我一直在互联网上寻找合适的imfill函数(如 Matlab 中的函数),但在 C++ 中使用 OpenCV。经过一番研究,我终于想出了一个解决方案:

IplImage* imfill(IplImage* src)
{
    CvScalar white = CV_RGB( 255, 255, 255 );

    IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3);
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contour = 0;

    cvFindContours(src, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
    cvZero( dst );

    for( ; contour != 0; contour = contour->h_next )
    {
        cvDrawContours( dst, contour, white, white, 0, CV_FILLED);
    }

    IplImage* bin_imgFilled = cvCreateImage(cvGetSize(src), 8, 1);
    cvInRangeS(dst, white, white, bin_imgFilled);

    return bin_imgFilled;
}

为此:原始二进制图像

结果是:最终二值图像

诀窍在于 cvDrawContours 函数的参数设置: cvDrawContours(dst, contour, white, white, 0, CV_FILLED);

  • dst = 目标图像
  • 轮廓 = 指向第一个轮廓的指针
  • 白色 = 用于填充轮廓的颜色
  • 0 = 绘制轮廓的最大级别。如果为 0,则仅绘制轮廓
  • CV_FILLED = 绘制轮廓线的粗细。如果它是负数(例如,=CV_FILLED),则绘制轮廓内部。

openCV 文档中的更多信息。

可能有一种方法可以直接将“dst”作为二进制图像获取,但我找不到如何将 cvDrawContours 函数与二进制值一起使用。

于 2013-04-15T12:54:04.013 回答
7

我做了一个简单的函数,相当于matlab的imfill('holes')。我没有在很多情况下测试过它,但到目前为止它已经奏效了。我在边缘图像上使用它,但它接受任何类型的二进制图像,例如来自阈值操作。

一个洞只不过是填充背景时无法“到达”的一组像素,因此,

void fillEdgeImage(cv::Mat edgesIn, cv::Mat& filledEdgesOut) const
{
    cv::Mat edgesNeg = edgesIn.clone();

    cv::floodFill(edgesNeg, cv::Point(0,0), CV_RGB(255,255,255));
    bitwise_not(edgesNeg, edgesNeg);
    filledEdgesOut = (edgesNeg | edgesIn);

    return;
}

这是一个示例结果

在此处输入图像描述

于 2014-03-05T16:34:20.113 回答
4

这是一种快速而肮脏的方法:

  1. 对输入图像执行精明处理,使新的二值图像边缘为 1,否则为 0
  2. 沿着边缘图像的一侧找到第一个 0,然后使用边缘图像作为蒙版在空白图像上的该点处使用 1 启动填充。(我们希望在这里我们没有走运,并将第一次填充播种在屏幕一半的形状内部)
  3. 这个新的填充图像是“背景”。这里任何有 1 的像素都是背景,任何有 0 的像素都是前景。
  4. 循环遍历图像并找到任何前景像素。在你找到的任何东西上播种填充物。
  5. 或者使用您在步骤 1 中的 Canny 图像使用这个新的填充图像,您就完成了。
于 2010-01-26T18:41:30.427 回答
3

只是Amro 答案的附录。

void cvFillHoles(cv::Mat &input)
{
    //assume input is uint8 B & W (0 or 1)
    //this function imitates imfill(image,'hole')
    cv::Mat holes=input.clone();
    cv::floodFill(holes,cv::Point2i(0,0),cv::Scalar(1));
    for(int i=0;i<input.rows*input.cols;i++)
    {
        if(holes.data[i]==0)
            input.data[i]=1;
    }
}
于 2012-10-04T14:43:38.920 回答
2

您是否尝试过通过 Cannyied 图像进行 ContourFinding?

cvFindContours 创建了一种树,其中外部计数是内部轮廓(“孔”)的父级。请参阅 contours.py 示例。从轮廓中你可以提取种子

于 2009-11-22T16:07:52.943 回答
2

最近我也在寻找解决这个问题的方法。这里我实现了Amro的想法如下:

#include <iostream>
using namespace std;
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
using namespace cv;

int main()
{
    IplImage *im = cvLoadImage("coin.png",CV_LOAD_IMAGE_ANYDEPTH);
    IplImage *hole = cvCreateImage(cvSize(im->width,im->height),8,1);
    cvShowImage("Original",im);

    cvCopyImage(im,hole);
    cvFloodFill(hole,cvPoint(0,0),cvScalar(255));
    cvShowImage("Hole",hole);
    cvSaveImage("hole.png",hole);

    cvNot(hole,hole);
    cvAdd(im,hole,im);
    cvShowImage("FillHole",im);
    cvSaveImage("fillHole.png",im);

    cvWaitKey(0);
    system("pause");
    return 0;
} 

希望这会有所帮助。

于 2014-04-30T09:54:23.353 回答
0

如果你有来自边缘的点,你可以使用fillConvexPoly() 或 fillPoly()(如果 poly 不是凸的)。

从边缘获取点的一种方法是执行findContours() -> approxPolyDP()

于 2012-10-04T09:17:14.897 回答