2014-10-06 294 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