OpenCV检测带有噪声的部分圆 [英] OpenCV detect partial circle with noise

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

问题描述

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

I have tried to use OpenCV HoughCircles and findContours to detect a circle however the circle isn't complete enough or there is too much noise in the algorithm for these algorithms. Or perhaps we are just not familiar enough with the OpenCV. Attached is my image that I need to find the circle on. You should be able to see it clearly with your eyes however, none of the circle detection algorithms seem to work. I have found that applying a median filter cleans up most of the noise but even after median filtering the algorithms can't detect the circle.

注意,我什至在这里查看并尝试了解决方案,因此它不是该问题的重复:检测opencv中的半圆

Note I even looked at and tried the solution here so it is not a duplicate of that question: Detect semi-circle in opencv

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

Any ideas? This is my source image that I need to use.

另外,我想检测圆的原因是我想只使用圆一部分的点进行计算.

Also, the reason I want to detect the circle is I want to do a calculation using only the points that are a part of the circle.

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

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

推荐答案

给你:

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

I'm using my 2nd answer from Detect semi-circle in opencv and modify it a little. This version now detects the best found semi-circle (regarding completeness).

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

But first I want to tell you why the accepted answer of link to Detect semi-circle in opencv stack overflow question does not work here (beside noise): You have only edges of the circle! as stated in that question, HoughCircle function computes the gradient internally, which does not work well for edgy images.

但现在我该怎么做:

使用这个作为输入(你自己的中值过滤图像(我刚刚裁剪它):

using this as input (your own median filtered image (I've just cropped it):

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

First I "normalize" the image. I just stretch values, that smallest val is 0 and biggest val is 255, leading to this result: (maybe some real contrast enhancement is better)

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

after that I compute the threshold of that image with some fixed threshold (you might need to edit that and find a way to choose the threshold dynamically! a better contrast enhancement might help there)

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

from this image, I use some simple RANSAC circle detection(very similar to my answer in the linked semi-circle detection question), giving you this result as a best semi-sircle:

这是代码:

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

edit:还有一件事:速度性能高度依赖于maxNrOfIterations.如果这很重要,您真的应该阅读有关 RANSAC 以及何时停止它的信息.因此,您可能能够及早确定找到的圆圈是正确的,并且不需要测试任何其他圆圈;)

edit : one more thing: speed performance highly depends on maxNrOfIterations. If that matters you really should read about RANSAC an when to stop it. So you might be able to decide early that a found circle is the right one and dont need to test any other ones ;)

这篇关于OpenCV检测带有噪声的部分圆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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