本文已参与「新人创作礼」活动,一起开启掘金创作之路
一、霍夫圆变换数学原理
霍夫圆变换的数学原理和霍夫直线变换的数学原理是一致的,都是要将要检测的图形从笛卡尔坐标系转换到霍夫空间。在笛卡尔坐标系中某个特定的圆由三个参数(圆心坐标及圆的半径)所唯一确定:
如果要将其上的点转换到霍夫空间的话,它将是一个在以为基坐标的平面的圆锥面:
·
即笛卡尔坐标系中的一个圆变换为了霍夫空间中的一个点,而笛卡尔坐标上某圆上的一点则变为了霍夫空间中的一个圆锥面。在霍夫空间中:每个圆锥面代表一个圆上的点,每个圆锥面上的点代表过该面对应笛卡尔坐标系点的圆。
通过这一方式我们就可以将圆的检测转化为求霍夫空间中圆锥面交点的问题,通过某点的圆锥面越多,该点就越有可能对应一个圆。
二、霍夫梯度法
由于理论上的标准霍夫圆变换法将空间扩展到了三维,增加了很多的计算量,所以实际运用中我们并不采用这一方法,而是使用霍夫梯度法。霍夫梯度法的核心原理是圆上每一点的切线都过圆心,如果许多条这样的切线交于某一点,该点就很有可能是圆心。这样就又将圆的检测问题转化为了求点被多少条切线所经过的累加问题了。 其算法实现过程如下:
- 对输入的图像进行边缘检测;(这一步和霍夫直线检测不同,直线检测不自带边缘检测,要求输入图像为边缘图像)
- 计算边缘图像上每个非零点的梯度值(梯度的计算通过sobel算子计算x,y方向一阶差分求方和根实现),沿着梯度和梯度的反方向(实际在梯度的x方向分量和y方向分量上分别步进)上在minradius到maxradius的范围内对经过的每一个像素,在累加器中进行累加;
- 对累加器中的候选圆心进行非极大抑制及阈值检测,筛选出更有可能的圆心;
- 对于累加器中的每一个候选圆心,先计算它到所有已确定圆心的距离,如果它和任意一个已确定圆心的距离小于mindist,则舍去该圆心;然后再计算出它到边缘图中每一个非零点的距离,挑选出其中最受非零点支持的距离作为待选半径(支持的定义为:具有相同半径的点数/该半径最大,判断半径是否相同并不是直接判断想等,而是判断两个距离的插值是否小于距离分辨率dp),最后如果具有这一相同半径的点数大于阈值RTESH的话,就认为找到了一个圆心及其半径,填入数组; 由上述算法介绍我们可以发现霍夫梯度法检测圆形存在着几个缺陷:首先由于对一个候选圆心我们只会为其选择一个半径,这样使得当原图中存在同心圆的时候,一般只会选出较大的圆而忽略小圆;其次由于对两个圆圆心的距离要求不能小于mindist,容易忽略掉两相邻圆中的一个。其他的缺陷则暂时无法从算法分析中发现,可以参见参考文献中的解释。
三、示例程序
Opencv中给出了霍夫圆检测的函数,其原型如下:
void HoughCircles( InputArray image, OutputArray circles,
int method, double dp, double minDist,
double param1 = 100, double param2 = 100,
int minRadius = 0, int maxRadius = 0 );
第一个参数为输入图像,应该为8位单通道灰度图像,但是不再需要预先进行边缘检测;
第二个参数为输出的圆数组,通常设定为vector<Vec3f>,以保存(x,y,radius)信息;
第三个参数为霍夫圆检测的方法,实际上opencv并没有提供更多的检测方法,只有霍夫梯度法即HOUGH_GRADIENT;
第四个参数为原图和累加器矩阵的长(宽)比值,即累加器矩阵的分辨率,由于累加器矩阵实际上只是为了储存待定圆心的,所以并不要求和原图相同大小,但在程序中由于它牵扯到了最支持半径的判定,所以该值越大,实际检测出来的圆(可能非圆)会越多;
第五个参数为两个圆圆心所能容忍的最小距离,该值越小可以检测出越多的圆,但同时误判的概率也会越高;
第六个参数为程序运行中Canny边缘检测的高阈值,低阈值默认为高阈值的一半;
第七个参数为判断某点是否为圆心时的阈值,该值越小可以检测出越多的圆(可能非圆);
第八、九个参数为检测出的圆半径范围,规定了其最小值和最大值;
示例代码如下:
int main()
{
Mat src, dst, gray;
vector<Vec3f> rounds;
src = imread("E:\\material\\draw.png");
if (src.empty())
{
cout << "not found the picture";
return -1;
}
cvtColor(src, gray, COLOR_BGR2GRAY);//转化边缘检测后的图为灰度图
GaussianBlur(gray,gray , Size(9, 9), 2, 2);
HoughCircles(gray, rounds, HOUGH_GRADIENT, 1.5, 50, 115, 100); //倒数第三个参数为圆心间最小距离,有助于检测相邻圆,倒数第二个为canny边缘检测时的高阈值
//倒数第一个为圆心的累加器阈值,越小检测出的圆越多
for (size_t i = 0;i < rounds.size(); i++)
{
Point center(cvRound(rounds[i][0]), cvRound(rounds[i][1]));
int radius = cvRound(rounds[i][2]);
//半径取3,绘制圆心
circle(src, center, 3, Scalar(134, 125, 163), -1, 8, 0);
//绘制圆轮廓
circle(src, center, radius, Scalar(214, 235, 216), 3, 8, 0);
}
imshow("效果图", src);
waitKey(0);
}
参考文献