检测opencv中的半圆

我正在尝试在图像中检测整圈和半圆。 在这里输入图像描述

我遵循下面提到的过程:过程映像(包括Canny边缘检测)find轮廓并将其绘制在空白图像上,以便消除不需要的组件。 (处理的图像正是我想要的。)使用HoughCircles检测圆圈。 这就是我得到的。

在这里输入图像描述

我试着改变HoughCircles中的参数,但结果并不一致,因为它根据光照和图像中圆圈的位置而变化。 我接受或拒绝一个基于它的大小的圆圈。 所以结果是不能接受的。 另外我有一个很长的“可接受的”圈子列表,所以我需要在HoughCircle params中给予一些补贴。 至于整个圆圈,很容易 – 我可以简单地find轮廓的“圆度”。 问题是半圈!

请在hough变换之前find已编辑的图像 在这里输入图像描述

直接在图像上使用houghCircle ,不要先提取边缘。 然后testing每个检测到的圆,图像中确实存在多less百分比:

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

对于这个input:

在这里输入图像描述

它给出了这个输出:

在这里输入图像描述

红色圆圈是霍夫结果。

圆圈上的绿色采样点是inliers。

蓝点是exception值。

控制台输出:

 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 

如果你想testingRANSAC而不是Hough,请看看这个 。

这里有另一种方式来做到这一点,一个简单的RANSAC版本(要做很多优化,以提高速度),在边缘图像上工作。

该方法循环这些步骤直到它被取消

  1. 随机select3个边缘像素
  2. 从他们估计圆圈(3分足以确定一个圆圈)
  3. validation或伪造它真的是一个圆:计算给定边表示圆的百分比
  4. 如果一个圆被validation,从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:

在这里输入图像描述

输出:

在这里输入图像描述

控制台输出:

  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` 

优化应该包括:

  1. 使用所有inlier来适应更好的圈子

  2. 不要在每个检测到的圆圈之后计算距离变换(这相当昂贵)。 直接从点/边集计算inlier,并从列表中删除inlier边。

  3. 如果图像中有许多小圆圈(和/或大量的噪声),则不可能随机地碰到3个边缘像素或一个圆圈。 首先尝试轮廓检测并检测每个轮廓的圆圈。 之后尝试检测图像中剩下的所有“其他”圆圈。

  4. 很多其他的东西

houghalgorithm检测到的半圆很可能是正确的。 这里的问题可能是,除非严格控制场景的几何形状,即摄像机相对于目标的精确位置,以便图像轴与目标平面垂直,否则将得到省略号而不是投影在图像上的圆圈平面。 更不要说由光学系统引起的扭曲,这进一步退化了几何graphics。 如果你在这里依靠精度,我会build议相机校准 。

你最好尝试不同的内核 高斯模糊,这将帮助你

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

所以改变size(i,i),j,j)

我知道这有点晚,但是我用了不同的方法,这很容易。 从cv2.HoughCircles(...)得到圆的中心和直径(x,y,r)。 所以我简单地通过圆的所有中心点,并检查它们是否远离图像的边缘而不是它们的直径。

这是我的代码:

  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), :]