霍夫变换常用来在图像中提取直线和圆等几何形状。如下图:
我们下面来看看如何使用霍夫变换来检测直线。一条直线可以用数学表达式 y = mx + 或者 ρ = xcosθ + y sinθ表示(极坐标)
简单说明一下:
ρ 是从原点到直线的垂直距离,θ是直线的垂线与横轴顺时针方向的夹角,如下图所示:
首先创建一个2D数组(累加器),初始化累加器,所有的值都为0。行表示 ρ,列表示 θ。这个数组的大小决定了最后结果的准确性。如果你希望角度精确到1°,你就需要180列。对于 ρ,最大值为图片对角线距离。
想象一下我们有一个大小为100x100的直线位于图像中央。取直线上的第一个点,我们知道此处的(x,y)值,把x和y带入公式:ρ = xcosθ + y sinθ,然后遍历 θ 的取值0, 1, 2, 3,...,180.分别求出对应的 ρ 值,这样我们就得到了一系列(ρ,θ)的数值对,如果这个数值对在累加器中也存在相应的位置,就在这个位置上加 1。由于同一条直线上的点必然会有同样的(ρ,θ)。所以现在累加器中的(50,90)=1。现在取直线上的第二个点。重复上边的过程。更新累加器中的值。现在累加器中(50,90)的值为 2。你每次做的就是更新累加器中的值。对直线上的每个点都执行上边的操作,每次操作完成之后,累加器中的值就加 1,但其他地方有时会加 1, 有时不会。按照这种方式下去,到最后累加器中(50,90)的值肯定是最大的。如果你搜索累加器中的最大值,并找到其位置(50,90),这就说明图像中有一条直线,这条直线到原点的距离为 50,它的垂线与横轴的夹角为 90 度。
OpenCV中首先计算(r,θ) 累加数,累加数超过一定值后就认为在同一直线上(有一个阈值)。
霍夫直线变换
import cv2 import numpy as np # 1.加载图片,转为二值图 img = cv2.imread('shapes.jpg') drawing = np.zeros(img.shape[:], dtype=np.uint8) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 50, 150) # 2.霍夫直线变换 lines = cv2.HoughLines(edges, 0.8, np.pi / 180, 90)
函数中:
- 参数1:要检测的二值图(一般是阈值分割或边缘检测后的图)
- 参数2:距离 ρ 的精度,值越大,考虑越多的线
- 参数3:角度 θ 的精度,值越小,考虑越多的线
- 参数4:累加数阈值,值越小,考虑越多的线
# 3.将检测的线画出来(注意是极坐标噢) for line in lines: rho, theta = line[0] a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) cv2.line(drawing, (x1, y1), (x2, y2), (0, 0, 255))
统计概率霍夫直线变换
前面的方法又称为标准霍夫变换,它会计算图像中的每一个点,计算量比较大,另外它得到的是整一条线,并不知道原图中直线的端点。所以提出了概率霍夫直线变换(Probabilistic Hough Transform),是一种改进的霍夫变换:
drawing = np.zeros(img.shape[:], dtype=np.uint8) # 统计概率霍夫线变换 lines = cv2.HoughLinesP(edges, 0.8, np.pi / 180, 90, minLineLength=50, maxLineGap=10)
# 将检测的线画出来 for line in lines: x1, y1, x2, y2 = line[0] cv2.line(drawing, (x1, y1), (x2, y2), (0, 255, 0), 1, lineType = cv2.LINE_AA) cv2.imwrite('houghlines4.jpg', drawing)
参数:
minLineLength
:最短长度阈值,比这个长度短的线会被排除- maxLineGap:同一直线两点之间的最大距离
cv2.LINE_AA:抗锯齿线型
霍夫圆变换
霍夫圆变换跟直线变换类似,只不过线是用(r,θ),圆是用(x_center,y_center,r)来表示,从二维变成了三维,数据量变大了很多;所以一般使用霍夫梯度法去减少计算量
drawing = np.zeros(img.shape[:], dtype=np.uint8) # 霍夫圆变换 circles = cv2.HoughCircles(edges, cv2.HOUGH_GRADIENT, 1, 20, param2=30) circles = np.int0(np.around(circles)) # 将检测的圆画出来 for i in circles[0, :]: cv2.circle(drawing, (i[0], i[1]), i[2], (0, 255, 0), 2) # 画出外圆 cv2.circle(drawing, (i[0], i[1]), 2, (0, 0, 255), 3) # 画出圆心 cv2.imwrite('HoughCircles.jpg', drawing)
- 参数2:变换方法,一般使用霍夫梯度法,详情:HoughModes
- 参数3 dp=1:表示霍夫梯度法中累加器图像的分辨率与原图一致
- 参数4:两个不同圆圆心的最短距离
- 参数5:param2跟霍夫直线变换中的累加数阈值一样