在二进制对象中填充空洞

35

我有一个问题,需要填充黑色硬币内部的白色空洞,以便我可以得到只有0-255二进制图像的填充黑色硬币。我已经使用了中值滤波器来完成它,但在这种情况下,硬币之间的连接桥梁会增长,并且在多次腐蚀后很难识别它们...因此,我需要一种类似于opencv中的简单floodFill方法。

这是我的带孔图像:

enter image description here

编辑:类似于floodfill的函数必须填充大组件内的孔洞,而不需要提示X、Y坐标作为种子...

编辑:我尝试使用cvDrawContours函数,但它不能填充更大的轮廓内部。

这是我的代码:

        CvMemStorage mem = cvCreateMemStorage(0);
        CvSeq contours = new CvSeq();
        CvSeq ptr = new CvSeq();
        int sizeofCvContour = Loader.sizeof(CvContour.class);
        
        cvThreshold(gray, gray, 150, 255, CV_THRESH_BINARY_INV);
        
        int numOfContours = cvFindContours(gray, mem, contours, sizeofCvContour, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
        System.out.println("The num of contours: "+numOfContours); //prints 87, ok
        
        Random rand = new Random();
        for (ptr = contours; ptr != null; ptr = ptr.h_next()) {
            Color randomColor = new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat());
            CvScalar color = CV_RGB( randomColor.getRed(), randomColor.getGreen(), randomColor.getBlue());
            cvDrawContours(gray, ptr, color, color, -1, CV_FILLED, 8);
        }
        CanvasFrame canvas6  = new CanvasFrame("drawContours");
        canvas6.showImage(gray);

结果:(您可以看到每个硬币内部都有黑洞)

输入图像说明

7个回答

60

有两种方法可以实现这个:

1)轮廓填充:

首先反转图像,在图像中找到轮廓,将其填充为黑色然后再次反转回去。

des = cv2.bitwise_not(gray)
contour,hier = cv2.findContours(des,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contour:
    cv2.drawContours(des,[cnt],0,255,-1)

gray = cv2.bitwise_not(des)

结果图像:

输入图像描述

2) 图像打开:

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
res = cv2.morphologyEx(gray,cv2.MORPH_OPEN,kernel)

生成的图像如下所示:

输入图像描述

可以看出,两种情况差别不大。

NB: gray - 灰度图像,所有代码均为OpenCV-Python。


参考文献。 OpenCV形态学变换


尝试设置更大的结构元素,例如15,15。这可能会改善您的“开操作”。 - Gilad
@user391339:我认为在这方面提高速度没有太多可做的,因为它使用预制的opencv函数,而且它们已经优化到了最佳状态。哦..等一下! - Abid Rahman K
1
@user391339:仔细想想,我认为使用cv2.RETR_ExTERNAL而不是cv2.RETR_CCOMP可能会有一点改善。我不确定,你需要检查一下。但是,它仍然没有太大用处。相反,您可以检查代码中还需要更多时间的部分,并尝试进行优化。顺便说一句,这只需要3.5毫秒。为什么您需要提高其速度呢? - Abid Rahman K
我会尝试。我想以后做更昂贵的处理,所以在低级别的东西上节省时间是值得的。 - user391339
请注意,第一种方法将关闭任何尺寸的孔洞,因此是OpenCV中填补孔洞的“真正”方法,与第二种方法相比,第二种方法需要手动设置参数以根据孔洞的大小进行调整。 - Soltius
显示剩余2条评论

7
一个简单的膨胀和腐蚀能够相当好地填补间隙,我想这可能是你要找的东西。
更强大的解决方案是对整个图像进行边缘检测,然后进行霍夫变换以检测圆形。通过快速搜索可知,有各种语言的代码样例可用于使用霍夫变换进行大小不变的圆形检测,因此希望这将给您提供一些参考。
使用霍夫变换的好处是算法实际上会为您估计每个圆的大小和位置,因此您可以基于该模型重新建立理想图像。特别是考虑到这里输入图像的质量(即较少关注误报,因此可以降低结果阈值),它也应该非常强大并且具有重叠性。

5
你可能正在寻找填洞变换,它是形态学图像重建的一个应用程序。
该变换将填补硬币中的洞,尽管代价是填补相邻硬币组之间的所有洞。其他帖子建议使用霍夫空间或基于开运算的解决方案,这些方案可能会给出更好的高级识别结果。

2
但是针对OpenCV而不是Matlab。 - Zaur Guliyev
@lbstr: 这个页面主要讲解理论。我无法凭空提供OpenCV代码给您。 - thiton
链接已失效。 - Reunanen
@Reunanen 在本文撰写期间,链接仍然可用。该链接是指 MATLAB 图像处理工具箱中官方的 imfill 函数。只要 MATLAB 存在,它就应该是可靠的。 - rayryeng
@rayryeng-ReinstateMonica 这个问题已经可以运行了,因为在我发表评论后,Cris Luengo对其进行了修复:https://stackoverflow.com/posts/10318004/revisions - Reunanen

2

如果有人正在寻找cpp实现 -

            std::vector<std::vector<cv::Point> > contours_vector;

            cv::findContours(input_image, contours_vector, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);

            cv::Mat contourImage(input_image.size(), CV_8UC1, cv::Scalar(0));
            for ( ushort contour_index = 0; contour_index < contours_vector.size(); contour_index++) {
                cv::drawContours(contourImage, contours_vector, contour_index, cv::Scalar(255), -1);
            }

            cv::imshow("con", contourImage);
            cv::waitKey(0);

enter image description here

enter image description here


2

尝试使用cvFindContours()函数。您可以使用它来查找连接的组件。通过正确的参数,此函数返回每个连接组件的轮廓列表。

找到代表孔的轮廓。然后使用cvDrawContours()将所选轮廓用前景颜色填充,从而关闭孔。


1

我认为,如果对象被触碰或过于拥挤,则使用轮廓和数学形态学开运算可能会出现一些问题。相反,我们找到并测试了以下简单的解决方法。它运作良好,不仅适用于这些图像,还适用于任何其他图像。

以下是步骤(优化)如http://blogs.mathworks.com/steve/2008/08/05/filling-small-holes/所示:

I:输入图像

1. filled_I = floodfill(I). // fill every hole in the image.
2. inverted_I = invert(I)`.   
3. holes_I = filled_I AND inverted_I. // finds all holes 
4. cc_list = connectedcomponent(holes_I) // list of all connected component in holes_I.
5. holes_I = remove(cc_list,holes_I, smallholes_threshold_size) // remove all holes from holes_I having size > smallholes_threshold_size.
6. out_I = I OR holes_I. // fill only the small holes.

简而言之,该算法的作用是找到所有的孔洞,去除大的孔洞,然后只在原始图像上写入小的孔洞。

1

我一直在网上寻找一个适合C语言和OpenCV的imfill函数,就像Matlab中的那个。经过一些研究,我最终找到了解决方案:

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 = 目标图像
  • contour = 第一个轮廓的指针
  • white = 用于填充轮廓的颜色
  • 0 = 绘制轮廓的最大级别。如果为0,则只绘制轮廓
  • CV_FILLED = 轮廓绘制线条的厚度。如果它是负数(例如= CV_FILLED),则绘制轮廓内部。

有关更多信息,请参阅openCV文档。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接