Improving circle detection

I am trying to detect circles in my images. I wrote the following code in C # using EmguCV. Most of the time it works, but there are times when it detects smaller or larger circles that are slightly off to the 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 the filenames are different and will be removed later. I also added the images that were saved by this code during the process. The last image shows the detected circle, which is larger and also shifted to the right.

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

Thanks in advance.

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

+3


source to share


2 answers


analogous to my answers in Semicircle detection in opencv I see a problem: do not extract the canny edge detection before the circle detection, as openCV houghCircle calculates the Gradient and the cannes by itself. So what you are trying to do is extract the edges of the image from it and detect circles in it, which results in (at best) two new edges around each edge => wrong path!

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

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 that I posted at Detecting a semicircle in opencv (my second answer, but slightly modified to find the fullest circle) (input: canny edge)

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 got this result instead:

enter image description here

You don't have any C # code, sorry.

+7


source


Hi trying I am trying to detect circles in C # Can you share your C # code๏ผŸ thanks๏ผ



0


source







All Articles