2014-10-06 86 views
3

我试图用OpenCV HoughCircles和findContours来检测一个圆,但是圆不够完整或者算法中有太多噪声。或许我们对OpenCV不够熟悉。附加的是我需要找到圈子的图像。你应该能够用眼睛清楚地看到它,但是,没有一个圆检测算法似乎可以工作。我发现应用中值滤波器可以清除大部分噪声,但即使在中值滤波之后,算法也无法检测到圆。OpenCV检测部分有噪声的圆

注意我甚至看了看,在这里尝试了解决方案,因此它不是问题的重复: Detect semi-circle in opencv

任何想法?这是我需要使用的源图片。

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

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

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

+0

你能假设图像中总是有一个圆圈吗? – Micka 2014-10-07 09:44:32

回答

0

MMMMM ....如果您提高图像一点点的对比度,你得到这个

enter image description here

,我认为大多数算法都会遇到困难。由于实际圈是相当明亮相对于其他(大概)不需要的东西,也许在某个地方考虑​​阈值围绕价值200

+0

是的,只有圆圈是想要的。不是所有的噪音。听起来像噪音引起的问题,我认为这是问题。 – 2014-10-06 23:52:20

+0

另外,用什么OpenCV命令来设置阈值? – 2014-10-07 05:14:51

+0

它实际上被称为'theeshold',这里有一个例子http://www.tutorialspoint.com/java_dip/basic_thresholding.htm – 2014-10-07 05:50:59

9

在这里你去:

我用我的第二个答案从Detect semi-circle in opencv和修改它有点。现在这个版本检测到最好的半圆(关于完整性)。

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

但现在我要做的事:用这个作为输入

(你自己的中值滤波图像(我刚刚冒出的话):

enter image description here

首先我“正常化”的形象。我只是拉伸值,最小的val是0,最大的val是255,导致这个结果:(可能一些真实的对比度增强更好)

enter image description here

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

enter image description here

从这个形象,我使用一些简单的RANSAC圆检测(非常类似于我在链接半圆检测问题的答案),给你这个结果作为最好半sircle:

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何时停止它。所以你可能会很早就决定一个找到的圆是正确的,不需要测试其他任何圆;)

+0

+1非常好的方法和执行! – 2014-10-09 07:46:28

+0

基于直方图分析的最终技术会不会更快,并且可能更容易检测到圆圈? – LandonZeKepitelOfGreytBritn 2017-05-09 20:24:32

+0

@trilolil:也许是的,你有什么特别的想法吗? – Micka 2017-05-09 20:27:36