简介
霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。
霍夫变换在OpenCV中分为霍夫线变换和霍夫圆变换两种,我们下面将分别进行介绍。
霍夫线变换
OpenCV支持三种不同的霍夫线变换,它们分别是:标准霍夫变换(Standard Hough Transform,SHT)和多尺度霍夫变换(Multi-Scale Hough Transform,MSHT)累计概率霍夫变换(Progressive Probabilistic Hough Transform ,PPHT)。
其中,多尺度霍夫变换(MSHT)为标准霍夫变换(SHT)在多尺度下的一个变种。累计概率霍夫变换(PPHT)算法是标准霍夫变换(SHT)算法的一个改进,它在一定的范围内进行霍夫变换,计算单独线段的方向以及范围,从而减少计算量,缩短计算时间。
其中
- 标准霍夫变换(Standard Hough Transform,SHT),由HoughLines函数调用
- 多尺度霍夫变换(Multi-Scale Hough Transform,MSHT),由HoughLines函数调用
- 累计概率霍夫变换(ProgressiveProbabilistic Hough Transform,PPHT),由HoughLinesP函数调用
原理
对于霍夫变换, 我们将采用极坐标系方式来表示直线. 因此, 直线的表达式可为:
1 2
| y = (- \frac{\cos \theta}{\sin \theta})x + \frac{r}{\sin \theta}
|
化简为
1
| r = x\cos \theta + y\sin \theta
|
HoughLines
函数原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
void HoughLines( InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn = 0, double stn = 0, double min_theta = 0, double max_theta = CV_PI );
|
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Mat mat_base = imread("26.jpg"); Mat mat_mid, mat_dst; Canny(mat_base, mat_mid, 50, 200, 3); cvtColor(mat_mid, mat_dst, COLOR_GRAY2BGR); vector<Vec2f>lines; HoughLines(mat_mid, lines, 1, CV_PI / 180, 150); for (auto i = 0; i < lines.size(); ++i) { float rho = lines[i][0], theta = lines[i][1]; Point pt1, pt2; double a = cos(theta), b = sin(theta); double x0 = a * rho, y0 = b * rho; pt1.x = cvRound(x0 + 1000 * (-b)); pt1.y = cvRound(y0 + 1000 * (a)); pt2.x = cvRound(x0 - 1000 * (-b)); pt2.y = cvRound(y0 - 1000 * (a)); line(mat_dst, pt1, pt2, Scalar(55, 100, 195), LINE_AA); }
|
![]()
HoughLinesP
此函数在HoughLines的基础上末尾加了一个代表Probabilistic(概率)的P,表明它可以采用累计概率霍夫变换(PPHT)来找出二值图像中的直线。
函数原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
void HoughLinesP( InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0 );
|
实例
1 2 3 4 5 6 7 8 9 10 11
| Mat mat_base = imread("lou.png"); Mat mat_mid, mat_dst; Canny(mat_base, mat_mid, 50, 200, 3); cvtColor(mat_mid, mat_dst, COLOR_GRAY2BGR); vector<Vec4i>lines; HoughLinesP(mat_mid, lines, 1, CV_PI / 80, 50, 10); for (auto i = 0; i < lines.size(); ++i) { Vec4i l = lines[i]; line(mat_dst, Point(l[0], l[1]), Point(l[1], l[2]), Scalar(55, 100, 195), LINE_8); }
|
![]()
霍夫圆变换
霍夫圆变换的基本原理和上面讲的霍夫线变化大体上是很类似的,只是点对应的二维极径极角空间被三维的圆心点x, y还有半径r空间取代。
在OpenCV中,我们一般通过一个叫做“霍夫梯度法”的方法来解决圆变换的问题。
霍夫梯度法的原理
- 首先对图像应用边缘检测,比如用canny边缘检测。
- 然后,对边缘图像中的每一个非零点,考虑其局部梯度,即用 Sobel 函数计算x和y方向的 Sobel 一阶导数得到梯度。
- 利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。
- 同时,标记边缘图像中每一个非 0 像素的位置。
- 然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。
- 接下来对每一个中心,考虑所有的非 0 像素。
- 这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。
- 如果一个中心收到边缘图像非 0 像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。
霍夫梯度法的缺点
- 在霍夫梯度法中,我们使用 Sobel 导数来计算局部梯度,那么随之而来的假设是,其可以视作等同于一条局部切线,并这个不是一个数值稳定的做法。在大多数情况下,这样做会得到正确的结果,但或许会在输出中产生一些噪声。
- 在边缘图像中的整个非 0 像素集被看做每个中心的候选部分。因此,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间。第三,因为每一个中心只选择一个圆,如果有同心圆,就只能选择其中的一个。
- 因为中心是按照其关联的累加器值的升序排列的,并且如果新的中心过于接近之前已经接受的中心的话,就不会被保留下来。且当有许多同心圆或者是近似的同心圆时,霍夫梯度法的倾向是保留最大的一个圆。可以说这是一种比较极端的做法,因为在这里默认 Sobel 导数会产生噪声,若是对于无穷分辨率的平滑图像而言的话,这才是必须的。
HoughCircles
函数原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
void HoughCircles( InputArray image, OutputArray circles, int method, double dp, double minDist, double param1 = 100, double param2 = 100, int minRadius = 0, int maxRadius = 0 );
|
需要注意的是,使用此函数可以很容易地检测出圆的圆心,但是它可能找不到合适的圆半径。我们可以通过第八个参数 minRadius 和第九个参数 maxRadius 指定最小和最大的圆半径,来辅助圆检测的效果。或者,我们可以直接忽略返回半径,因为它们都有着默认值 0,单单用 HoughCircles 函数检测出来圆心,然后用额外的一些步骤来进一步确定半径。
实例
1 2 3 4 5 6 7 8 9 10 11 12 13
| Mat mat_base = imread("ball.png"); Mat mat_mid, mat_dst = mat_base.clone(); cvtColor(mat_base, mat_mid, COLOR_BGR2GRAY); GaussianBlur(mat_mid, mat_mid, Size(9, 9), 2, 2); vector<Vec3f>circles; HoughCircles(mat_mid, circles, HOUGH_GRADIENT, 1.5, 10, 200); for (auto i = 0; i < circles.size(); ++i) { Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); circle(mat_dst, center, 3, Scalar(0, 255, 0), -1, 8, 0); circle(mat_dst, center, radius, Scalar(0, 0, 0), 3, 8, 0); }
|
![]()