我曾尝试使用OpenCV HoughCircles和findContours来检测圆圈但是圆圈不够完整或者算法中的噪声太多而无法使用这些算法。或许我们对OpenCV不够熟悉。附上我的图像,我需要找到圆圈。你应该能够用眼睛清楚地看到它,但是没有一个圆检测算法似乎有效。我发现应用中值滤波器可以清除大部分噪声,但即使在中值滤波后,算法也无法检测到圆圈。
注意我甚至在这里查看并尝试了解决方案,因此它不是该问题的重复: Detect semi-circle in opencv
有什么想法吗?这是我需要使用的源图像。
此外,我想要检测圆圈的原因是我想仅使用作为圆圈一部分的点进行计算。
原始图片: http://www.collegemobile.com/IMG_2021.JPG
Median Filtered Image: http://www.collegemobile.com/IMG_2022.JPG
答案 0 :(得分:12)
你走了:
我使用Detect semi-circle in opencv的第二个答案并对其进行了一些修改。此版本现在可以检测到找到的最佳半圆(关于完整性)。
但首先我要告诉你为什么link to Detect semi-circle in opencv stack overflow question的接受答案在这里不起作用(除了噪音):你只有圆圈的边缘!如该问题所述,HoughCircle函数在内部计算渐变,这对于前卫图像效果不佳。
但现在我是怎么做的:
使用此作为输入(您自己的中间过滤图像(我刚刚裁剪它):
首先我"正常化"图片。我只是拉伸值,最小值为0,最大值为255,导致这个结果:(可能有一些真正的对比度增强更好)
之后我用一些固定的阈值来计算该图像的阈值(您可能需要对其进行编辑并找到一种动态选择阈值的方法!更好的对比度增强可能对此有所帮助)
从这张图片中,我使用了一些简单的RANSAC圆检测(非常类似于我在链接半圆检测问题中的答案),给你这个结果作为最好的半圈:
以及代码:
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什么时候停止它。因此,您可以尽早确定找到的圆圈是正确的,不需要测试任何其他圆圈;)
答案 1 :(得分:0)
嗯....如果你稍微提高你的图像的对比度,你得到这个
我认为大多数算法都会遇到困难。由于实际圆相对于其他(可能是)不需要的东西非常明亮,可以考虑在值200附近的阈值处进行阈值处理。
答案 2 :(得分:0)
我实现了最小二乘算法,以使圆适合2d点。我将算法应用于来自Micka的阈值图像,但之前使用形态学开口去除了异常值。
Mat img;
img = imread("iokqh.png");
if (img.empty())
{
cout << "Could not open image..." << endl;
return -1;
}
cvtColor(img, img, COLOR_BGR2GRAY);
int dilation_type = 0;
int dilation_elem = 0;
if (dilation_elem == 0) { dilation_type = MORPH_RECT; }
else if (dilation_elem == 1) { dilation_type = MORPH_CROSS; }
else if (dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
int size = 1;
Mat element = getStructuringElement(dilation_type, Size(2 * size + 1, 2 * size + 1), Point(size, size));
morphologyEx(img, img, MORPH_OPEN, element);
vector<Point2f> points;
for (int x = 0; x < img.cols; x++)
{
for (int y = 0; y < img.rows; y++)
{
if (img.at<uchar>(y, x) > 0)
{
points.push_back(cv::Point2f(x, y));
}
}
}
//// Least Square Algorithm
float xn = 0, xsum = 0;
float yn = 0, ysum = 0;
float n = points.size();
for (int i = 0; i < n; i++)
{
xsum = xsum + points[i].x;
ysum = ysum + points[i].y;
}
xn = xsum / n;
yn = ysum / n;
float ui = 0;
float vi = 0;
float suu = 0, suuu = 0;
float svv = 0, svvv = 0;
float suv = 0;
float suvv = 0, svuu = 0;
for (int i = 0; i < n; i++)
{
ui = points[i].x - xn;
vi = points[i].y - yn;
suu = suu + (ui * ui);
suuu = suuu + (ui * ui * ui);
svv = svv + (vi * vi);
svvv = svvv + (vi * vi * vi);
suv = suv + (ui * vi);
suvv = suvv + (ui * vi * vi);
svuu = svuu + (vi * ui * ui);
}
cv::Mat A = (cv::Mat_<float>(2, 2) <<
suu, suv,
suv, svv);
cv::Mat B = (cv::Mat_<float>(2, 1) <<
0.5*(suuu + suvv),
0.5*(svvv + svuu));
cv::Mat abc;
cv::solve(A, B, abc);
float u = abc.at<float>(0);
float v = abc.at<float>(1);
float x = u + xn;
float y = v + yn;
float alpha = u * u + v * v + ((suu + svv) / n);
float r = sqrt(alpha);
////
cvtColor(img, img, COLOR_GRAY2BGR);
// Draw circle
circle(img, Point(x, y), r, Scalar(255, 0, 0), 1, 8, 0);
imshow("window", img);
waitKey(0);