Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find overlapping/complex circles with OpenCV

I want to compute the red circles radius (fig 2). I have troubles finding these circles using HoughCircles from OpenCV. As you can see in fig. 2 I can only find the little circles in center which are shown in black using HoughCircles.

original pic1 fig 2. red

Since I know the center of the red circles (which are the same as the red ones), is there a way to compute simply the radius of the red circles ?

Is it also possible to have a generic way of computing radius of circles on a more complex image such as this one :

example 2

Edit : Here the interesting part of my code after obtaining fig 2 :

threshold(maskedImage, maskedImage, thresh, 255, THRESH_BINARY_INV | THRESH_OTSU);
    std::vector<Vec3f> circles;
// Canny(maskedImage, maskedImage, thresh, thresh * 2, 3);

HoughCircles(maskedImage, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows / 4, cannyThreshold, accumulatorThreshold, 0, 0);

Mat display = src_display.clone();
for (size_t i = 0; i < circles.size(); i++)
{
    Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
    int radius = cvRound(circles[i][2]);
    // circle center
    circle(display, center, 3, Scalar(0, 255, 0), -1, 8, 0);
    // circle outline
    circle(display, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}

I have tried to use play with cannyThreshold and accumulator without results. Real images are 5x biggers. Here a link for example 1 after threshold.

Thanks

like image 557
coincoin Avatar asked Sep 04 '14 13:09

coincoin


1 Answers

You already know the smaller circles in the image(which you have drawn in black).

  • Prepare a mask image using these circles so the areas having smaller circles will have non-zero pixels. We'll call it mask:

enter image description here

  • In the original image, fill these circle areas in a dark color(say black). This will result in an image like your fig 2. We'll call it filled
  • Threshold the filled image to obtain the dark areas. We'll call it binary. You can use Otsu thresholding for this. Result will look something like this:

enter image description here

  • Take the distance transform of this binary image. Use an accurate distance estimation method for this. We'll call this dist. It'll look something like this. The colored one is just a heat map for more clarity:

enter image description hereenter image description here

  • Use the mask to obtain the peak regions from dist. The max value of each such region should give you the radius of the larger circle. You can also do some processing on these regions to arrive at a more reasonable value for radius rather than just picking up the max.
  • For selecting the regions, you can either find the contours of the mask and then extract that region from dist image, or, since you already know the smaller circles from applying hough-circle transform, prepare a mask from each of those circles and extract that region from dist image. I'm not sure if you can calculate max or other stats by giving a mask. Max will definitely work because the rest of the pixels are 0. You might be able calculate the stats of the region if you extract those pixels to another array.

Figures below show such mask and the extracted region from dist. For this I get a max around 29 which is consistent with the radius of that circle. Note that the images are not to scale.

mask for a circle, extracted region from dist

enter image description hereenter image description here

Here's the code (I'm not using hough-circles transform):

    Mat im = imread(INPUT_FOLDER_PATH + string("ex1.jpg"));

    Mat gray;
    cvtColor(im, gray, CV_BGR2GRAY);

    Mat bw;
    threshold(gray, bw, 0, 255, CV_THRESH_BINARY|CV_THRESH_OTSU);
    // filtering smaller circles: not using hough-circles transform here. 
    // you can replace this part with you hough-circles code.
    vector<int> circles;
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
    for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
    {
        Rect rect = boundingRect(contours[idx]);
        if (abs(1.0 - ((double)rect.width/rect.height) < .1))
        {
            Mat mask = Mat::zeros(im.rows, im.cols, CV_8U);
            drawContours(mask, contours, idx, Scalar(255, 255, 255), -1);
            double area = sum(mask).val[0]/255;
            double rad = (rect.width + rect.height)/4.0;
            double circArea = CV_PI*rad*rad;
            double dif = abs(1.0 - area/circArea);
            if (dif < .5 && rad < 50 && rad > 30)   // restrict the radius
            {
                circles.push_back(idx); // store smaller circle contours
                drawContours(gray, contours, idx, Scalar(0, 0, 0), -1); // fill circles
            }
        }
    }

    threshold(gray, bw, 0, 255, CV_THRESH_BINARY_INV|CV_THRESH_OTSU);

    Mat dist, distColor, color;
    distanceTransform(bw, dist, CV_DIST_L2, 5);
    double max;
    Point maxLoc;
    minMaxLoc(dist, NULL, &max);
    dist.convertTo(distColor, CV_8U, 255.0/max);
    applyColorMap(distColor, color, COLORMAP_JET);
    imshow("", color);
    waitKey();

    // extract dist region corresponding to each smaller circle and find max
    for(int idx = 0; idx < (int)circles.size(); idx++)
    {
        Mat masked;
        Mat mask = Mat::zeros(im.rows, im.cols, CV_8U);
        drawContours(mask, contours, circles[idx], Scalar(255, 255, 255), -1);
        dist.copyTo(masked, mask);
        minMaxLoc(masked, NULL, &max, NULL, &maxLoc);
        circle(im, maxLoc, 4, Scalar(0, 255, 0), -1);
        circle(im, maxLoc, (int)max, Scalar(0, 0, 255), 2);
        cout << "rad: " << max << endl;
    }
    imshow("", im);
    waitKey();

Results(scaled):

enter image description hereenter image description here

Hope this helps.

like image 116
dhanushka Avatar answered Oct 21 '22 21:10

dhanushka