Reshaping noisy coin into a circle form

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?


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

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

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.


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.