7

I'm doing a coin detection using JavaCV (OpenCV wrapper) but I have a little problem when the coins are connected. If I try to erode them to separate these coins they loose their circle form and if I try to count pixels inside each coin there can be problems so that some coins can be miscounted as one that bigger. What I want to do is firstly to reshape them and make them like a circle (equal with the radius of that coin) and then count pixels inside them.

Here is my thresholded image:enter image description here

And here is eroded image:enter image description here

Any suggestions? Or is there any better way to break bridges between coins?

Zaur Guliyev
  • 3,994
  • 7
  • 26
  • 42

5 Answers5

11

It looks similar to a problem I recently had to separate bacterial colonies growing on agar plates. I performed a distance transform on the thresholded image (in your case you will need to invert it). Then found the peaks of the distance map (by calculating the difference between a the dilated distance map and the distance map and finding the zero values). Then, I assumed each peak to be the centre of a circle (coin) and the value of the peak in the distance map to be the radius of the circle.

Here is the result of your image after this pipeline: distance transform works

I am new to OpenCV, and c++ so my code is probably very messy, but I did that:

int main( int argc, char** argv ){

    cv::Mat objects, distance,peaks,results;
    std::vector<std::vector<cv::Point> > contours;

    objects=cv::imread("CUfWj.jpg");
    objects.copyTo(results);
    cv::cvtColor(objects, objects, CV_BGR2GRAY);
    //THIS IS THE LINE TO BLUR THE IMAGE CF COMMENTS OF THIS POST
    cv::blur( objects,objects,cv::Size(3,3));
    cv::threshold(objects,objects,125,255,cv::THRESH_BINARY_INV);


    /*Applies a distance transform to "objects".
     * The result is saved in "distance" */
    cv::distanceTransform(objects,distance,CV_DIST_L2,CV_DIST_MASK_5);

    /* In order to find the local maxima, "distance"
     * is subtracted from the result of the dilatation of
     * "distance". All the peaks keep the save value */
    cv::dilate(distance,peaks,cv::Mat(),cv::Point(-1,-1),3);
    cv::dilate(objects,objects,cv::Mat(),cv::Point(-1,-1),3);

    /* Now all the peaks should be exactely 0*/
    peaks=peaks-distance;

    /* And the non-peaks 255*/
    cv::threshold(peaks,peaks,0,255,cv::THRESH_BINARY);
    peaks.convertTo(peaks,CV_8U);

    /* Only the zero values of "peaks" that are non-zero
     * in "objects" are the real peaks*/
    cv::bitwise_xor(peaks,objects,peaks);

    /* The peaks that are distant from less than
     * 2 pixels are merged by dilatation */
    cv::dilate(peaks,peaks,cv::Mat(),cv::Point(-1,-1),1);

    /* In order to map the peaks, findContours() is used.
     * The results are stored in "contours" */
    cv::findContours(peaks, contours, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
    /* The next steps are applied only if, at least,
     * one contour exists */
    cv::imwrite("CUfWj2.jpg",peaks);
    if(contours.size()>0){

        /* Defines vectors to store the moments of the peaks, the center
         * and the theoritical circles of the object of interest*/
        std::vector <cv::Moments> moms(contours.size());
        std::vector <cv::Point> centers(contours.size());
        std::vector<cv::Vec3f> circles(contours.size());
        float rad,x,y;
        /* Caculates the moments of each peak and then the center of the peak
         * which are approximatively the center of each objects of interest*/

        for(unsigned int i=0;i<contours.size();i++) {
            moms[i]= cv::moments(contours[i]);
            centers[i]= cv::Point(moms[i].m10/moms[i].m00,moms[i].m01/moms[i].m00);
            x= (float) (centers[i].x);
            y= (float) (centers[i].y);
            if(x>0 && y>0){
                rad= (float) (distance.at<float>((int)y,(int)x)+1);
                circles[i][0]= x;
                circles[i][3]= y;
                circles[i][2]= rad;
                cv::circle(results,centers[i],rad+1,cv::Scalar( 255, 0,0 ), 2, 4, 0 );
            }
        }
        cv::imwrite("CUfWj2.jpg",results);
    }

    return 1;
}
mevatron
  • 13,381
  • 3
  • 51
  • 68
Quentin Geissmann
  • 2,230
  • 1
  • 18
  • 33
  • 1
    You have got the best result btw.. I will try this on JavaCV tonight! – Zaur Guliyev Apr 26 '12 at 11:02
  • 1
    **However**, [the image I got from your code](http://imageshack.us/photo/my-images/526/58131003.jpg/) is a little bit different from the one you showed us. – karlphillip Apr 26 '12 at 13:49
  • 1
    I tested it on other PCs and it seems to work. Can you [try this](http://desmond.imageshack.us/Himg526/scaled.php?server=526&filename=58131003.jpg&res=landing)? – karlphillip Apr 26 '12 at 15:08
  • That link didn't work as well..that would be great to see what I'll get before converting all these line of code to Java as I'm using JavaCV. But its ok anyway, I believe in you :) – Zaur Guliyev Apr 26 '12 at 15:19
  • @karlphillip I got the same as you after retrying with the original picture. I am sorry about that... I forgot to paste the line to blur the image which makes the pipeline more robust: cv::blur( objects,objects,cv::Size(3,3)); I just edited the code it in my post. Thanks for noticing. – Quentin Geissmann Apr 26 '12 at 17:55
  • Thank you again for testing :) – Quentin Geissmann Apr 26 '12 at 18:00
5

You don't need to erode, just a good set of params for cvHoughCircles():

enter image description here

The code used to generate this image came from my other post: Detecting Circles, with these parameters:

CvSeq* circles = cvHoughCircles(gray, storage, CV_HOUGH_GRADIENT, 1, gray->height/12, 80, 26);
Community
  • 1
  • 1
karlphillip
  • 87,606
  • 33
  • 227
  • 395
  • Ok that's cool but in Hough Circles method in some case 5 cents and 10 cents can be drawn with the same radius which is not what I want, I want just find radiuses for each coin and find how much money is on that image according to radiuses.. – Zaur Guliyev Apr 25 '12 at 13:27
  • The problem is that when you execute erosion, you loose this info, as you have already observed. If you are not willing to spend time investigating how to improve your detection using Hough Circles I'll have to suggest that you take an entirely different approach using either [SURF](http://achuwilson.wordpress.com/2011/08/05/object-detection-using-surf-in-opencv-part-1/) or [haar](http://achuwilson.wordpress.com/2011/02/13/object-detection-using-opencv-using-haartraining/) [training](http://note.sonots.com/SciSoftware/haartraining.html) to detect coins. – karlphillip Apr 25 '12 at 13:44
  • If you only have to deal with coins near each other, that's a degree of difficulty that Hough Circles can handle. But if you have to deal with coins overlapping each other, than you must consider one of the other approaches I just suggested. – karlphillip Apr 25 '12 at 13:46
  • There is nothing stopping you from executing `cvHoughCircles()` to detect a specific size of coin, and then execute it again to detect another size, and so on... It's not efficient in terms of performance, but it may be an easier way to solve the problem. – karlphillip Apr 25 '12 at 13:48
2

OpenCV has a function called HoughCircles() that can be applied to your case, without separating the different circles. Can you call it from JavaCV ? If so, it will do what you want (detecting and counting circles), bypassing your separation problem.

The main point is to detect the circles accurately without separating them first. Other algorithms (such as template matching can be used instead of generalized Hough transform, but you have to take into account the different sizes of the coins.

sansuiso
  • 8,519
  • 1
  • 36
  • 55
  • firstly I applied Gaussian blurring and then cvHoughCircles but in some images it can't find correct radiuses and centers for circle. And also in some images it finds out wrong circles - especially in the case when coins are grouped and there're a lot of them.. – Zaur Guliyev Apr 25 '12 at 12:00
  • It's probably better to use a median filter instead of the Gaussian, since fit will limit the blur effect (hence it will improve accuracy of your algorithm). The main problem however is that it is very hard to separate these intricate shapes by morphology (you have to erode to much to summarize). If Hough circles didn't work, you could try template matching with circles of different scales instead, to detect circles directly. – sansuiso Apr 25 '12 at 12:13
1

The usual approach for erosion-based object recognition is to label continuous regions in the eroded image and then re-grow them until they match the regions in the original image. Hough circles is a better idea in your case, though.

thiton
  • 34,333
  • 3
  • 63
  • 96
0

After detecting the joined coins, I recommend applying morphological operations to classify areas as "definitely coin" and "definitely not coin", apply a distance transformation, then run the watershed to determine the boundaries. This scenario is actually the demonstration example for the watershed algorithm in OpenCV − perhaps it was created in response to this question.

kokociel
  • 392
  • 7
  • 16