改善圈检测 [英] Improving circle detection

查看:69
本文介绍了改善圈检测的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试检测图像中的圆圈.我已经使用EmguCV在C#中编写了以下代码.在大多数情况下,它是可行的,但是在某些情况下,它会检测到稍微偏向一侧的较小或较大的圆圈.

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.

这是我的代码:

        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命令只是为了确保文件名不同并在以后删除. 我还附加了在此过程中此代码保存的图像.最后一张图片显示了检测到的圆圈,该圆圈较大并且向右移动.

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?

谢谢.

推荐答案

与我在

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!

就像在openCV教程中所做的那样,您可以直接在灰度图像上计算HoughCircles,为我提供以下结果:

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

输入:

参数:

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

代码:

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 );
    }

结果:

我使用我的RANSAC方法发布在在opencv中检测半圆(我的第二个答案,但稍作更改以搜索最完整的圆)(输入:精明的边缘)

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)

代码:

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:

对不起,没有C#代码.

Have no C# code, sorry.

这篇关于改善圈检测的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆