31

我在填充黑色硬币内的白洞时遇到问题,因此我只能使用填充黑色硬币的 0-255 二进制图像。我使用中值滤波器来完成它,但在这种情况下,硬币之间的连接桥会增长,并且不可能在经过几次侵蚀后才能识别它们......所以我需要一个简单的 opencv 中的类似 floodFill 的方法

这是我的带有孔的图像:

在此处输入图像描述

编辑:类似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);

结果:(你可以看到每个硬币里面的黑洞)

在此处输入图像描述

4

7 回答 7

54

有两种方法可以做到这一点:

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 中

于 2012-04-25T14:33:10.400 回答
5

我想,一个简单的扩张和侵蚀会很好地缩小差距。我想也许这就是你要找的。

更稳健的解决方案是对整个图像进行边缘检测,然后对圆形进行霍夫变换。一个快速的谷歌显示有各种语言的代码示例,用于使用霍夫变换对圆圈进行大小不变检测,所以希望这会给你一些东西。

使用霍夫变换的好处是该算法实际上会为您提供每个圆的大小和位置的估计,因此您可以根据该模型重建理想的图像。重叠也应该非常健壮,尤其是考虑到这里输入图像的质量(即较少担心误报,因此可以降低结果的阈值)。

于 2012-04-25T14:31:03.693 回答
4

您可能正在寻找Fillhole 变换,一种形态图像重建的应用。

这种转换将填补你硬币上的洞,尽管代价是也填满了相邻硬币组之间的所有洞。其他发帖者建议的霍夫空间或基于开放的解决方案可能会给您更好的高级识别结果。

于 2012-04-25T14:39:31.213 回答
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);

在此处输入图像描述

在此处输入图像描述

于 2018-09-27T16:28:21.533 回答
1

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

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

于 2012-04-25T14:01:28.720 回答
1

我认为如果物体被触摸或拥挤,使用轮廓和数学形态学开口会出现一些问题。相反,找到并测试了以下简单的解决方案。它工作得很好,不仅适用于这些图像,而且适用于任何其他图像。

这是http://blogs.mathworks.com/steve/2008/08/05/filling-small-holes/中看到的步骤(优化)

let 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.

简而言之,算法就是找到所有的洞,去掉大的,然后把小的只写在原始图像上。

于 2012-06-04T20:10:42.593 回答
1

我一直在互联网上寻找合适的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-15T13:03:28.883 回答