34

I have a problem with filling white holes inside a black coin so that I can have only 0-255 binary images with filled black coins. I have used a Median filter to accomplish it but in that case connection bridge between coins grows and it goes impossible to recognize them after several times of erosion... So I need a simple floodFill like method in opencv

Here is my image with holes:

enter image description here

EDIT: floodfill like function must fill holes in big components without prompting X, Y coordinates as a seed...

EDIT: I tried to use the cvDrawContours function but it doesn't fill contours inside bigger ones.

Here is my code:

        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);

Result: (you can see black holes inside each coin)

enter image description here

Innat
  • 16,113
  • 6
  • 53
  • 101
Zaur Guliyev
  • 4,254
  • 7
  • 28
  • 44

7 Answers7

59

There are two methods to do this:

1) Contour Filling:

First, invert the image, find contours in the image, fill it with black and invert back.

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)

Resulting image:

enter image description here

2) Image Opening:

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

The resulting image is as follows:

enter image description here

You can see, there is not much difference in both cases.

NB: gray - grayscale image, All codes are in OpenCV-Python


Reference. OpenCV Morphological Transformations

Innat
  • 16,113
  • 6
  • 53
  • 101
Abid Rahman K
  • 51,886
  • 31
  • 146
  • 157
  • Ok, I had a parameter problem i should use: cvDrawContours(gray, ptr, color, color, 0, CV_FILLED, 8); with 0 as max_level parameter. Hm now its filled with success :) – Zaur Guliyev Apr 25 '12 at 16:11
  • Try setting a larger StructuringElement, for example, 15,15. This might improve your 'open' procedure – Gilad Oct 31 '12 at 15:10
  • Cool solution - Thanks! For me, the image opening method gave ~6ms execution time and the contour filling method gave ~3.5ms execution time. Working with video taken using cv2.VideoCapture(0) & my macbook pro. Any way to speed this up using numpy, or is that already being used by opencv? – user391339 Feb 06 '14 at 06:40
  • @user391339: I don't think there is much to do anything on this to improve its speed, because it uses readymade opencv function, and they are optimized to their best. oh.. wait please !!! – Abid Rahman K Feb 06 '14 at 08:28
  • 1
    @user391339: on second thought, I think using `cv2.RETR_ExTERNAL` instead of `cv2.RETR_CCOMP` may have a little improvement. I am not sure, you have to check it. But still, it will be of not much use. Instead, you can check whatelse take more time in your code and try to optimize it. By the way, it is only just 3.5ms. Why you need to improve its speed anyway? – Abid Rahman K Feb 06 '14 at 08:32
  • I'll try it. I want to do more expensive processing later so it's worth to save time on the low level stuff. – user391339 Feb 06 '14 at 19:10
  • Note that the first method will close any holes regardless of the size, and thus is the "true" method of hole filling in OpenCV, compared with the second method which requires a manually set parameter depending on the sizes of the holes. – Soltius Jul 05 '22 at 15:13
6

A simple dilate and erode would close the gaps fairly well, I imagine. I think maybe this is what you're looking for.

A more robust solution would be to do an edge detect on the whole image, and then a hough transform for circles. A quick google shows there are code samples available in various languages for size invariant detection of circles using a hough transform, so hopefully that will give you something to go on.

The benefit of using the hough transform is that the algorithm will actually give you an estimate of the size and location of every circle, so you can rebuild an ideal image based on that model. It should also be very robust to overlap, especially considering the quality of the input image here (i.e. less worry about false positives, so can lower the threshold for results).

Elias Vasylenko
  • 1,524
  • 11
  • 21
5

You might be looking for the Fillhole transformation, an application of morphological image reconstruction.

This transformation will fill the holes in your coins, even though at the cost of also filling all holes between groups of adjacent coins. The Hough space or opening-based solutions suggested by the other posters will probably give you better high-level recognition results.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
thiton
  • 35,651
  • 4
  • 70
  • 100
2

In case someone is looking for the cpp implementation -

            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

infoclogged
  • 3,641
  • 5
  • 32
  • 53
1

Try using cvFindContours() function. You can use it to find connected components. With the right parameters this function returns a list with the contours of each connected components.

Find the contours which represent a hole. Then use cvDrawContours() to fill up the selected contour by the foreground color thereby closing the holes.

bubble
  • 3,408
  • 5
  • 29
  • 51
1

I think if the objects are touched or crowded, there will be some problems using the contours and the math morophology opening. Instead, the following simple solution is found and tested. It is working very well, and not only for this images, but also for any other images.

here is the steps (optimized) as seen in http://blogs.mathworks.com/steve/2008/08/05/filling-small-holes/

let I: the input image

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.

In short, the algorithm is just to find all holes, remove the big ones then write the small ones only on the original image.

pradyunsg
  • 18,287
  • 11
  • 43
  • 96
Faroq AL-Tam
  • 495
  • 5
  • 10
1

I've been looking around the internet to find a proper imfill function (as the one in Matlab) but working in C with OpenCV. After some reaserches, I finally came up with a solution :

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;
}

For this: Original Binary Image

Result is: Final Binary Image

The trick is in the parameters setting of the cvDrawContours function: cvDrawContours( dst, contour, white, white, 0, CV_FILLED);

  • dst = destination image
  • contour = pointer to the first contour
  • white = color used to fill the contour
  • 0 = Maximal level for drawn contours. If 0, only contour is drawn
  • CV_FILLED = Thickness of lines the contours are drawn with. If it is negative (For example, =CV_FILLED), the contour interiors are drawn.

More info in the openCV documentation.

There is probably a way to get "dst" directly as a binary image but I couldn't find how to use the cvDrawContours function with binary values.

Jeremy.S
  • 189
  • 2
  • 10