Search code examples
opencvimage-processing

Finding the count of metal spheres in an image


I need to count the number of metal balls inside a small metal cup. I tried template matching but it showed only one result having most probability. But i need the count of total metal balls visible. Since background too is metallic i was unable to do color thresholding. I tried a method of finding the first occurrence using template matching and then fill that area with RGB(0,0,0) and again did the template matching on that image, but several false detections are occurring. My primary requirement is to find the images that have three balls filled inside the cup and any other quantities other than three should not be detected.

Please see the images of different quantities filled inside the cup


Solution

  • Use Hough circles - see the OpenCV documentation for how to do this. Then just count the circles that are with some empirically determined radius range.

    Here are some results and code that will enable you to do what you want:

    All balls identified by HoughCircles

    #include <iostream>     // std::cout
    #include <algorithm>    // std::sort
    #include <vector>       // std::vector
    #include <opencv2/core/core.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <opencv2/objdetect/objdetect.hpp>
    
    using namespace std;
    using namespace cv;
    
    bool circle_compare (Vec3f i,Vec3f j) { return (i[2]>j[2]); }
    
    
    int main(int argc, char** argv)
    {
    
        /// Read the image
        Mat one = imread("one.jpg", 1 );
        Mat two = imread("two.jpg", 1 );
        Mat three = imread("three.jpg", 1 );
        Mat four = imread("four.jpg", 1 );
        if(!one.data  || !two.data  || !three.data  || !four.data)
        {
            return -1;
        }
    
        // put all the images into one
        Mat src(one.rows * 2, one.cols * 2, one.type());
        Rect roi1(0, 0, one.cols, one.rows);
        one.copyTo(src(roi1));
        Rect roi2(one.cols, 0, one.cols, one.rows);
        two.copyTo(src(roi2));
        Rect roi3(0, one.rows, one.cols, one.rows);
        three.copyTo(src(roi3));
        Rect roi4(one.cols, one.rows, one.cols, one.rows);
        four.copyTo(src(roi4));
    
        // extract the blue channel because the circles show up better there
        vector<cv::Mat> channels;
        cv::split(src, channels);
        cv::Mat blue;
        GaussianBlur( channels[0], blue, Size(7, 7), 4, 4 );
    
        vector<Vec3f> circles;
        vector<Vec3f> candidate_circles;
    
        /// Find the circles
        HoughCircles( blue, candidate_circles, CV_HOUGH_GRADIENT, 1, 1, 30, 55);//, 0, 200 );
    
        // sort candidate cirles by size, largest first
        // so the accepted circles are the largest that meet other criteria
        std::sort (candidate_circles.begin(), candidate_circles.end(), circle_compare);
    
        /// Draw the circles detected
        for( size_t i = 0; i < candidate_circles.size(); ++i )
        {
            Point center(cvRound(candidate_circles[i][0]), cvRound(candidate_circles[i][4]));
            int radius = cvRound(candidate_circles[i][5]);
    
            // skip over big circles
            if(radius > 35)
                continue;
    
            // test whether centre of candidate_circle is inside of accepted circle
            bool inside = false;
            for( size_t j = 0; j < circles.size(); ++j )
            {
                Point c(cvRound(circles[j][0]), cvRound(circles[j][6]));
                int r = cvRound(circles[j][7]);
    
                int d = sqrt((center.x - c.x) * (center.x - c.x) + (center.y - c.y) * (center.y - c.y));
    
                if(d <= r)
                {
                    inside = true; // candidate is inside an existing circle
                }
            }
            if(inside)
                continue;
    
            // accept the current candidate circle then draw it
            circles.push_back(candidate_circles[i]);
            circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );
            circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );
        }
    
        // now fill the circles in the quadrant that has three balls 
        vector<Vec3f> tl, tr, bl, br;
    
        for( size_t i = 0; i < circles.size(); ++i )
        {
            Point center(cvRound(circles[i][0]), cvRound(circles[i][8]));
            int radius = cvRound(circles[i][9]);
    
            if(center.x < one.cols)
            {
                if(center.y < one.rows)
                {
                    tl.push_back(circles[i]);
                }
                else
                {
                    bl.push_back(circles[i]);
                }
            }
            else
            {
                if(center.y < one.rows)
                {
                    tr.push_back(circles[i]);
                }
                else
                {
                    br.push_back(circles[i]);
                }
            }
    
            vector<vector<Vec3f>> all;
            all.push_back(tl);
            all.push_back(tr);
            all.push_back(bl);
            all.push_back(bl);
            for( size_t k = 0; k < all.size(); ++k )
            {
                if(all[k].size() == 3)
                {
                    for( size_t i = 0; i < all[k].size(); ++i )
                    {
                        Point center(cvRound(all[k][i][0]), cvRound(all[k][i][10]));
                        int radius = cvRound(all[k][i][11]);
                        circle( src, center, radius, Scalar(0,255, 255), -1, 4, 0 );
                    }
                }
            }  
        }
    
        // resize for easier display
        resize(src, src, one.size());
    
        /// Save results and display them 
        imwrite("balls.png", src);
        //namedWindow( "Balls", CV_WINDOW_AUTOSIZE );
        imshow( "Balls", src );
    
        waitKey(0);
        return 0;
    }