Improving circle detection

2020-02-29 04:34发布

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 here enter image description here enter image description here enter image description here

1条回答
Melony?
2楼-- · 2020-02-29 05:02

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.

查看更多
登录 后发表回答