Detect semi-circle in opencv

岁酱吖の 提交于 2019-11-25 19:17:44
Micka

Use houghCircle directly on your image, don't extract edges first. Then test for each detected circle, how much percentage is really present in the image:

int main() {     cv::Mat color = cv::imread("../houghCircles.png");     cv::namedWindow("input"); cv::imshow("input", color);      cv::Mat canny;      cv::Mat gray;     /// Convert it to gray     cv::cvtColor( color, gray, CV_BGR2GRAY );      // compute canny (don't blur with that image quality!!)     cv::Canny(gray, canny, 200,20);     cv::namedWindow("canny2"); cv::imshow("canny2", canny>0);      std::vector<cv::Vec3f> circles;      /// Apply the Hough Transform to find the circles     cv::HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 1, 60, 200, 20, 0, 0 );      /// Draw the circles detected     for( size_t i = 0; i < circles.size(); i++ )      {         Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));         int radius = cvRound(circles[i][2]);         cv::circle( color, center, 3, Scalar(0,255,255), -1);         cv::circle( color, center, radius, Scalar(0,0,255), 1 );     }      //compute distance transform:     cv::Mat dt;     cv::distanceTransform(255-(canny>0), dt, CV_DIST_L2 ,3);     cv::namedWindow("distance transform"); cv::imshow("distance transform", dt/255.0f);      // test for semi-circles:     float minInlierDist = 2.0f;     for( size_t i = 0; i < circles.size(); i++ )      {         // test inlier percentage:         // sample the circle and check for distance to the next edge         unsigned int counter = 0;         unsigned int inlier = 0;          cv::Point2f center((circles[i][0]), (circles[i][1]));         float radius = (circles[i][2]);          // maximal distance of inlier might depend on the size of the circle         float maxInlierDist = radius/25.0f;         if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist;          //TODO: maybe paramter incrementation might depend on circle size!         for(float t =0; t<2*3.14159265359f; t+= 0.1f)          {             counter++;             float cX = radius*cos(t) + circles[i][0];             float cY = radius*sin(t) + circles[i][1];              if(dt.at<float>(cY,cX) < maxInlierDist)              {                 inlier++;                 cv::circle(color, cv::Point2i(cX,cY),3, cv::Scalar(0,255,0));             }             else                 cv::circle(color, cv::Point2i(cX,cY),3, cv::Scalar(255,0,0));         }         std::cout << 100.0f*(float)inlier/(float)counter << " % of a circle with radius " << radius << " detected" << std::endl;     }      cv::namedWindow("output"); cv::imshow("output", color);     cv::imwrite("houghLinesComputed.png", color);      cv::waitKey(-1);     return 0; } 

For this input:

It gives this output:

The red circles are Hough results.

The green sampled dots on the circle are inliers.

The blue dots are outliers.

Console output:

100 % of a circle with radius 27.5045 detected 100 % of a circle with radius 25.3476 detected 58.7302 % of a circle with radius 194.639 detected 50.7937 % of a circle with radius 23.1625 detected 79.3651 % of a circle with radius 7.64853 detected 

If you want to test RANSAC instead of Hough, have a look at this.

Here is another way to do it, a simple RANSAC version (much optimization to be done to improve speed), that works on the Edge Image.

the method loops these steps until it is cancelled

  1. choose randomly 3 edge pixel
  2. estimate circle from them (3 points are enough to identify a circle)
  3. verify or falsify that it's really a circle: count how much percentage of the circle is represented by the given edges
  4. if a circle is verified, remove the circle from input/egdes

    int main() { //RANSAC  //load edge image cv::Mat color = cv::imread("../circleDetectionEdges.png");  // convert to grayscale cv::Mat gray; cv::cvtColor(color, gray, CV_RGB2GRAY);  // get binary image cv::Mat mask = gray > 0; //erode the edges to obtain sharp/thin edges (undo the blur?) cv::erode(mask, mask, cv::Mat());  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;  char quitKey = 'q'; std::cout << "press " << quitKey << " to stop" << std::endl; while(cv::waitKey(-1) != quitKey) {     //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);      float minCirclePercentage = 0.4f;      // 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);      if(cPerc >= minCirclePercentage)     {         std::cout << "accepted circle with " << cPerc*100.0f << " % inlier" << std::endl;         // first step would be to approximate the circle iteratively from ALL INLIER to obtain a better circle center         // but that's a TODO          std::cout << "circle: " << "center: " << center << " radius: " << radius << std::endl;         cv::circle(color, center,radius, cv::Scalar(255,255,0),1);          // accept circle => remove it from the edge list         cv::circle(mask,center,radius,cv::Scalar(0),10);          //update edge positions and distance transform         edgePositions = getPointPositions(mask);         cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3);     }      cv::Mat tmp;     mask.copyTo(tmp);      // prevent cases where no fircle could be extracted (because three points collinear or sth.)     // filter NaN values     if((center.x == center.x)&&(center.y == center.y)&&(radius == radius))     {         cv::circle(tmp,center,radius,cv::Scalar(255));     }     else     {         std::cout << "circle illegal" << std::endl;     }      ++nIterations;     cv::namedWindow("RANSAC"); cv::imshow("RANSAC", tmp); }  std::cout << nIterations <<  " iterations performed" << std::endl;   cv::namedWindow("edges"); cv::imshow("edges", mask); cv::namedWindow("color"); cv::imshow("color", color);  cv::imwrite("detectedCircles.png", color); cv::waitKey(-1); 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; } 

input:

output:

console output:

    press q to stop     accepted circle with 50 % inlier     circle: center: [358.511, 211.163] radius: 193.849     accepted circle with 85.7143 % inlier     circle: center: [45.2273, 171.591] radius: 24.6215     accepted circle with 100 % inlier     circle: center: [257.066, 197.066] radius: 27.819     circle illegal     30 iterations performed` 

optimization should include:

  1. use all inlier to fit a better circle

  2. dont compute distance transform after each detected circles (it's quite expensive). compute inlier from point/edge set directly and remove the inlier edges from that list.

  3. if there are many small circles in the image (and/or a lot of noise), it's unlikely to hit randomly 3 edge pixels or a circle. => try contour detection first and detect circles for each contour. after that try to detect all "other" circles left in the image.

  4. a lot of other stuff

The semicircle detected by the hough algorithm is most probably correct. The issue here might be that unless you strictly control the geometry of the scene, i.e. exact position of the camera relative to the target, so that the image axis is normal to the target plane, you will get ellipsis rather than circles projected on the image plane. Not to mention the distortions caused by the optical system, which further degenerate the geometric figure. If you rely on precision here, I would recommend camera calibration.

sandeep

You better try with different kernel for gaussian blur.That will help you

GaussianBlur( src_gray, src_gray, Size(11, 11), 5,5); 

so change size(i,i),j,j)

I know that it's little bit late, but I used different approach which is much easier. From the cv2.HoughCircles(...) you get centre of the circle and the diameter (x,y,r). So I simply go through all centre points of the circles and I check if they are further away from the edge of the image than their diameter.

Here is my code:

        height, width = img.shape[:2]          #test top edge         up = (circles[0, :, 0] - circles[0, :, 2]) >= 0          #test left edge         left = (circles[0, :, 1] - circles[0, :, 2]) >= 0          #test right edge         right = (circles[0, :, 0] + circles[0, :, 2]) <= width          #test bottom edge         down = (circles[0, :, 1] + circles[0, :, 2]) <= height          circles = circles[:, (up & down & right & left), :] 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!