Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improving circle detection

I am trying to detect circles in my images. I have written the following code in C# using EmguCV. Most of the times it works, but there are some cases that it detects smaller or larger circles that are slightly shifted to a side.

Here is my code:

        Thread.Sleep(1000);
        imgCrp.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");

        imgCrpLab = imgCrp.Convert<Lab, Byte>();
        imgIsolatedCathNTipBW = new Image<Gray, Byte>(imgCrp.Size);
        CvInvoke.cvInRangeS(imgCrpLab.Split()[2], new MCvScalar(0), new MCvScalar(100), imgIsolatedCathNTipBW);

        imgCrpNoBgrnd = imgCrp.Copy(imgIsolatedCathNTipBW.Not());
        imgCrpNoBgrndGray = imgCrpNoBgrnd.Convert<Gray, Byte>().PyrUp().PyrDown();
        Thread.Sleep(1000);
        imgCrpNoBgrndGray.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");

        Gray cannyThreshold = new Gray(150);
        Gray cannyThresholdLinking = new Gray(85);
        Gray circleAccumulatorThreshold = new Gray(15);

        imgCrpNoBgrndGrayCanny = imgCrpNoBgrndGray.Canny(cannyThreshold.Intensity, cannyThresholdLinking.Intensity);
        Thread.Sleep(1000);
        imgCrpNoBgrndGrayCanny.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");

        circarrTip = imgCrpNoBgrndGrayCanny.HoughCircles(
            cannyThreshold,
            circleAccumulatorThreshold,
            1, //Resolution of the accumulator used to detect centers of the circles
            500, //min distance 
            15, //min radius
            42 //max radius
            )[0]; //Get the circles from the first channel

        imgCathNoTip = imgIsolatedCathNTipBW.Copy().Not();
        foreach (CircleF circle in circarrTip)
        {
            circLarger2RemTip = circle;
            circLarger2RemTip.Radius = circle.Radius;
            imgCathNoTip.Draw(circLarger2RemTip, new Gray(140), 1); // -1 IS TO FILL THE CIRCLE
        }
        Thread.Sleep(1000);
        imgCathNoTip.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");

Sleep commands are just to make sure that the filenames will be different and will be removed later. I have also attached the images that have been save by this code during the process. The last image shows the detected circle which is larger and also shifted to the right.

Can anyone kindly check my code and let me know how I can improve it to detect circles more accurately?

Thanks in advance.

enter image description hereenter image description hereenter image description hereenter image description here

like image 972
NESHOM Avatar asked Nov 23 '14 23:11

NESHOM


People also ask

Which algorithm is used to detect circle?

Automatic circle detection is an important element of many image processing algorithms. Traditionally the Hough transform has been used to find circular objects in images but more modern approaches that make use of heuristic optimisation techniques have been developed.

How do I identify a circle in a picture?

In order to detect the circles, or any other geometric shape, we first need to detect the edges of the objects present in the image. The edges in an image are the points for which there is a sharp change of color. For instance, the edge of a red ball on a white background is a circle.

How does Hough circle detection work?

The circle Hough Transform (CHT) is a basic feature extraction technique used in digital image processing for detecting circles in imperfect images. The circle candidates are produced by “voting” in the Hough parameter space and then selecting local maxima in an accumulator matrix.

How do I find an image in a circle in Python?

cv2. HoughCircles(image, method, dp, minDist) Where Image is the image file converted to grey scale Method is the algorithm used to detct the circles. Dp is the inverse ratio of the accumulator resolution to the image resolution. minDist is the Minimum distance between the center coordinates of detected circles.


1 Answers

analogue to my answers in Detect semi-circle in opencv I see a problem: Don't extract canny edge detection before hough circle detection, since openCV houghCircle itself computes Gradient AND canny. So what you are trying to do is to extract canny from and edge image and detect circles in that, leading to (in the best case) 2 new edges around each edge => wrong way!

As it is done in the openCV tutorial, you can compute HoughCircles directly on your grayscale image, giving this result for me:

input:

enter image description here

parameters:

cannyHigh = 100
cannyLow = 20
minSize = 0
maxSize = 100

code:

int mainHough()
{
    cv::Mat input = cv::imread("../inputData/CircleDetectGray.jpg");

    // you could load as grayscale if you want, but I used it for (colored) output too
    cv::Mat gray;
    cv::cvtColor(input,gray,CV_BGR2GRAY);

    float canny1 = 100;
    float canny2 = 20;

    // canny here only for visualizing the chosen parameters
    //cv::Mat canny;
    //cv::Canny(gray, canny, canny1,canny2);
    //cv::imshow("canny",canny);

    std::vector<cv::Vec3f> circles;
    /// Apply the Hough Transform to find the circles
    cv::HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 1, gray.cols/8, canny1,canny2,  0, 100 );

    std::cout << "found " << circles.size() << " circles" << std::endl;
    /// Draw the circles detected
    for( size_t i = 0; i < circles.size(); i++ ) 
    {
        cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        cv::circle( input, center, 3, cv::Scalar(0,255,255), -1);
        cv::circle( input, center, radius, cv::Scalar(0,0,255), 1 );
    }

result:

enter image description here

using my RANSAC method I posted in Detect semi-circle in opencv (my 2nd answer but slightly changed to search the most complete circle) (input: canny edges)

code:

float verifyCircle(cv::Mat dt, cv::Point2f center, float radius, std::vector<cv::Point2f> & inlierSet)
{
 unsigned int counter = 0;
 unsigned int inlier = 0;
 float minInlierDist = 2.0f;
 float maxInlierDistMax = 100.0f;
 float maxInlierDist = radius/25.0f;
 if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist;
 if(maxInlierDist>maxInlierDistMax) maxInlierDist = maxInlierDistMax;

 // choose samples along the circle and count inlier percentage
 for(float t =0; t<2*3.14159265359f; t+= 0.05f)
 {
     counter++;
     float cX = radius*cos(t) + center.x;
     float cY = radius*sin(t) + center.y;

     if(cX < dt.cols)
     if(cX >= 0)
     if(cY < dt.rows)
     if(cY >= 0)
     if(dt.at<float>(cY,cX) < maxInlierDist)
     {
        inlier++;
        inlierSet.push_back(cv::Point2f(cX,cY));
     }
 }

 return (float)inlier/float(counter);
}


inline void getCircle(cv::Point2f& p1,cv::Point2f& p2,cv::Point2f& p3, cv::Point2f& center, float& radius)
{
  float x1 = p1.x;
  float x2 = p2.x;
  float x3 = p3.x;

  float y1 = p1.y;
  float y2 = p2.y;
  float y3 = p3.y;

  // PLEASE CHECK FOR TYPOS IN THE FORMULA :)
  center.x = (x1*x1+y1*y1)*(y2-y3) + (x2*x2+y2*y2)*(y3-y1) + (x3*x3+y3*y3)*(y1-y2);
  center.x /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );

  center.y = (x1*x1 + y1*y1)*(x3-x2) + (x2*x2+y2*y2)*(x1-x3) + (x3*x3 + y3*y3)*(x2-x1);
  center.y /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );

  radius = sqrt((center.x-x1)*(center.x-x1) + (center.y-y1)*(center.y-y1));
}



std::vector<cv::Point2f> getPointPositions(cv::Mat binaryImage)
{
 std::vector<cv::Point2f> pointPositions;

 for(unsigned int y=0; y<binaryImage.rows; ++y)
 {
     //unsigned char* rowPtr = binaryImage.ptr<unsigned char>(y);
     for(unsigned int x=0; x<binaryImage.cols; ++x)
     {
         //if(rowPtr[x] > 0) pointPositions.push_back(cv::Point2i(x,y));
         if(binaryImage.at<unsigned char>(y,x) > 0) pointPositions.push_back(cv::Point2f(x,y));
     }
 }

 return pointPositions;
}



int mainRANSAC_circle()
{
    cv::Mat color = cv::imread("../inputData/CircleDetectGray.jpg");
    cv::Mat gray;

    // convert to grayscale
    // you could load as grayscale if you want, but I used it for (colored) output too
    cv::cvtColor(color, gray, CV_BGR2GRAY);


    cv::Mat mask;

    float canny1 = 100;
    float canny2 = 20;

    cv::Mat canny;
    cv::Canny(gray, canny, canny1,canny2);
    cv::imshow("canny",canny);

    mask = canny;



    std::vector<cv::Point2f> edgePositions;
    edgePositions = getPointPositions(mask);

    // create distance transform to efficiently evaluate distance to nearest edge
    cv::Mat dt;
    cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3);

    //TODO: maybe seed random variable for real random numbers.

    unsigned int nIterations = 0;

    cv::Point2f bestCircleCenter;
    float bestCircleRadius;
    float bestCirclePercentage = 0;
    float minRadius = 10;   // TODO: ADJUST THIS PARAMETER TO YOUR NEEDS, otherwise smaller circles wont be detected or "small noise circles" will have a high percentage of completion

    //float minCirclePercentage = 0.2f;
    float minCirclePercentage = 0.05f;  // at least 5% of a circle must be present? maybe more...

    int maxNrOfIterations = edgePositions.size();   // TODO: adjust this parameter or include some real ransac criteria with inlier/outlier percentages to decide when to stop

    for(unsigned int its=0; its< maxNrOfIterations; ++its)
    {
        //RANSAC: randomly choose 3 point and create a circle:
        //TODO: choose randomly but more intelligent, 
        //so that it is more likely to choose three points of a circle. 
        //For example if there are many small circles, it is unlikely to randomly choose 3 points of the same circle.
        unsigned int idx1 = rand()%edgePositions.size();
        unsigned int idx2 = rand()%edgePositions.size();
        unsigned int idx3 = rand()%edgePositions.size();

        // we need 3 different samples:
        if(idx1 == idx2) continue;
        if(idx1 == idx3) continue;
        if(idx3 == idx2) continue;

        // create circle from 3 points:
        cv::Point2f center; float radius;
        getCircle(edgePositions[idx1],edgePositions[idx2],edgePositions[idx3],center,radius);

        // inlier set unused at the moment but could be used to approximate a (more robust) circle from alle inlier
        std::vector<cv::Point2f> inlierSet;

        //verify or falsify the circle by inlier counting:
        float cPerc = verifyCircle(dt,center,radius, inlierSet);

        // update best circle information if necessary
        if(cPerc >= bestCirclePercentage)
            if(radius >= minRadius)
            {
                bestCirclePercentage = cPerc;
                bestCircleRadius = radius;
                bestCircleCenter = center;
            }

    }

    std::cout << "bestCirclePerc: " << bestCirclePercentage << std::endl;
    std::cout << "bestCircleRadius: " << bestCircleRadius << std::endl;

    // draw if good circle was found
    if(bestCirclePercentage >= minCirclePercentage)
        if(bestCircleRadius >= minRadius);
    cv::circle(color, bestCircleCenter,bestCircleRadius, cv::Scalar(255,255,0),1);


    cv::imshow("output",color);
    cv::imshow("mask",mask);
    cv::imwrite("../outputData/1_circle_color.png", color);
    cv::imwrite("../outputData/1_circle_mask.png", mask);
    //cv::imwrite("../outputData/1_circle_normalized.png", normalized);
    cv::waitKey(0);

    return 0;
}

I achieved this result instead:

enter image description here

Have no C# code, sorry.

like image 85
Micka Avatar answered Sep 23 '22 10:09

Micka