OpenCV 检测带有噪声的部分圆

标签 opencv geometry

我曾尝试使用 OpenCV HoughCircles 和 findContours 来检测圆,但是对于这些算法来说,圆不够完整或者图像中的噪声太多。或许我们只是对 OpenCV 不够熟悉。附件是我需要找到圆圈的图像。您应该能够用眼睛清楚地看到它,但是,圆检测算法似乎都不起作用。我发现应用中值滤波器可以清除大部分噪声,但即使在中值滤波器之后,算法也无法检测到圆。

请注意,我什至在这里查看并尝试了解决方案,因此它不是该问题的副本: Detect semicircle in OpenCV

有什么想法吗?这是我需要使用的源图像。

此外,我想要检测圆的原因是我想只使用属于圆一部分的点进行计算。

原始图片: http://www.collegemobile.com/IMG_2021.JPG

中值滤波图像: http://www.collegemobile.com/IMG_2022.JPG

最佳答案

给你:

我正在使用来自 Detect semi-circle in opencv 的第二个答案并稍微修改一下。此版本现在检测找到的最佳半圆(关于完整性)。

但首先我想告诉你为什么接受 link to Detect semi-circle in opencv stack overflow question 的答案在这里不起作用(除了噪音):你只有圆的边缘!如该问题所述,HoughCircle 函数在内部计算梯度,这对于前卫图像效果不佳。

但现在我是怎么做到的:

将其用作输入(您自己的中值滤波图像(我刚刚裁剪了它)):

enter image description here

首先,我“规范化”图像。我只是拉伸(stretch)值,最小的 val 是 0,最大的 val 是 255,导致这个结果:(也许一些真正的对比度增强更好)

enter image description here

之后我用一些固定阈值计算该图像的阈值(您可能需要编辑它并找到一种动态选择阈值的方法!更好的对比度增强可能会有所帮助)

enter image description here

从这张图片中,我使用了一些简单的 RANSAC 圆检测(与我在链接的半圆检测问题中的回答非常相似),给你这个结果作为一个最好的半圆:

enter image description here

代码如下:

int main()
{
    //cv::Mat color = cv::imread("../inputData/semi_circle_contrast.png");
    cv::Mat color = cv::imread("../inputData/semi_circle_median.png");
    cv::Mat gray;

    // convert to grayscale
    cv::cvtColor(color, gray, CV_BGR2GRAY);

    // now map brightest pixel to 255 and smalles pixel val to 0. this is for easier finding of threshold
    double min, max;
    cv::minMaxLoc(gray,&min,&max);
    float sub = min;
    float mult = 255.0f/(float)(max-sub);
    cv::Mat normalized = gray - sub;
    normalized = mult * normalized;
    cv::imshow("normalized" , normalized);
    //--------------------------------


    // now compute threshold
    // TODO: this might ne a tricky task if noise differs...
    cv::Mat mask;
    //cv::threshold(input, mask, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    cv::threshold(normalized, mask, 100, 255, CV_THRESH_BINARY);



    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 = 50;   // 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;
        }

    }

    // 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::waitKey(0);

        return 0;
    }

使用这些辅助函数:

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

编辑:还有一件事:速度性能在很大程度上取决于maxNrOfIterations。如果这很重要,您真的应该阅读有关 RANSAC 的内容以及何时停止它。因此,您可能能够尽早确定找到的圆圈是否正确,而无需测试任何其他圆圈;)

关于OpenCV 检测带有噪声的部分圆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26222525/

相关文章:

geometry - 如何判断两个环(3d 中的圆圈)是否相连

c# - 如何做半圆运动?

javascript - 使用draw2d在JavaScript中围绕主圆绘制圆

opencv - 如何使用Java API在OpenCV中执行DoG

c++ - 如何对 Mat 的单个坐标使用 warpPerspective

python - 如何在 open cv python 中从特定帧播放视频和音频?

opencv - 无法理解Opencv内置的calibrateCamera函数

c++ - 在 OpenGL 中将纹理映射到球体时出现接缝问题

c++ - Objective-C++调整大小实例 vector 不起作用

c - 计算点与多个面之间最小距离的有效方法