霍夫线变换是一种用来寻找直线的方法.
是用霍夫线变换之前, 首先要对图像进行边缘检测的处理,也即霍夫线变换的直接输入只能是边缘二值图像.
19.1 原理
众所周知, 一条直线在图像二维空间可由两个变量表示. 例如:
- 在 笛卡尔坐标系: 可由参数: (m,b)
斜率和截距表示.
- 在 极坐标系: 可由参数: (r,θ)
极径和极角表示
对于霍夫变换, 我们将用 极坐标系 来表示直线. 因此, 直线的表达式可为:
化简后得:
一般来说对于点 , 我们可以将通过这个点的一族直线统一定义为:
如果对于一个给定点 ,我们在极坐标对极径极角平面绘出所有通过它的直线, 将得到一条正弦曲线. 例如, 对于给定点
我们可以绘出下图 (在平面
):
只绘出满足下列条件的点 。
我们可以对图像中所有的点进行上述操作。如果两个不同点进行上述操作后得到的曲线在平面 θ-r 相交, 这就意味着它们通过同一条直线。
例如,接上面的例子我们继续对点 和点
绘图, 得到下图:
这三条曲线【坐标表示的是参数对 (θ,r )】在θ-r 平面相交于点 (0.925,9.6) ,则表明点 (x0,y0) 、点 (x1,y1) 和点 (x2,y2) 位于平面内的的同一条直线上。
那么以上的材料要说明什么呢? 这意味着一般来说, 一条直线能够通过在平面θ-r 寻找交于一点的曲线数量来 检测。越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成。一般来说我们可以通过设置直线上点的 阈值 来定义多少条曲线交于一点我们才认为 检测 到了一条直线。
- 这就是霍夫线变换要做的。它追踪图像中每个点对应曲线间的交点,如果交于一点的曲线的数量超过了 阈值,那么可以认为这个交点所代表的参数对 (
)在原图像中为一条直线。
OpenCV实现了以下两种霍夫线变换:
- 标准霍夫线变换:原理在上面的部分已经说明了。它能给我们提供一组参数对(θ,rθ )的集合来表示检测到的直线。在OpenCV 中通过函数 HoughLines 来实现。
- 统计概率霍夫线变换:这是执行起来效率更高的霍夫线变换. 它输出检测到的直线的端点
。在OpenCV 中它通过函数 HoughLinesP 来实现
19.2 cv2.HoughLines() 标准霍夫线变换
作用:查找直线
原型:cv2.HoughLinesP(image, rho, theta, threshold)
参数:
- image: 必须是二值图像,在使用hough变换之前,应用阈值或使用canny边缘检测,推荐使用canny边缘检测的结果图像;
- rho: 为距离分辨率,double类型的,推荐用1.0
- theta: 角度范围。以弧度为单位的角度精度,推荐用numpy.pi/180
- threshod: 累加器阈值,累加平面的阈值参数,int类型,指可以被认为是一个线条的最小计数值。由于计数值的多少取决于线上的点数,所以这代表了可以被识别为线的最小长度。 超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试。
返回值:
说明:
假设有一个100×100 的图片,有一条水平线在中间,获取这条线的第一个点,已知它的(x, y) 值,然后将θ = 0,1,2,……,180 放入线条方程中,获取r 值。对于每个(θ,rθ )单元格,累加器中与之相关的(θ,rθ )单元格每次增加的值为1,所以在累加器中,(90, 50) = 1 和一些其他单项相关联。
然后取线条上的第二个点,进行以上相同的动作,增加与之相关联的( )单元格,此时(90, 50) = 2 ,现在的操作都会增加(
)的值。
继续为线条上的每个点进行相同的操作,在每个点,(90, 50) 单元格可以增值或者计数,其他单元格可能不会被计数。通过这种方式,最后(90, 50) 单元格会得到最大的计数。如果在计数器中搜索最大计数,就可以得到(90, 50) 的值,表示在距离原点50,90° 角的地方有一条线,如下图:
。
示例
import cv2
import numpy as np
img = cv2.imread('C:\\Users\\xxx\\Downloads\\contourImg.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
lines = cv2.HoughLines(edges,1,np.pi/180,78)
for i in range(0,len(lines)):
rho,theta = lines[i][0][0],lines[i][0][1]
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(img,(x1,y1),(x2,y2),(0,0,255),2)
cv.imshow('HoughLine',img)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果如下:
19.3 cv2.HoughLinesP() 统计概率霍夫线变换
在霍夫变换中,可以通过2个变量来检测一条线上的点,但花费大量计算。概率霍夫变换是一个优化的霍夫变换,它不会计算所有的点,而是随机的选取一组足以识别直线的点,所以我们需要减少阈值。看下图中关于霍夫变换和概率霍夫变换在霍夫空间的比较:
OpenCV 技术是基于Matas, J. and Galambos, C. and Kittler, J.V.使用概率哈夫变换进行的线条鲁棒检测。它通过cv2.HoughLinesP()来实现,函数有2个新的参数。
- minLineLength - 线段的最小长度. Line segments shorter than this are rejected.
- maxLineGap - 使程序识别线段为一条线的线段之间最大的空隙
最好是这样,函数直接返回了线条的两个终点,在前面的实例中,必须找到所有的点,而只获取了线的参数。在这个实例中,所有的事都变得简单直接。
作用:查找直线段
原型:
cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)
参数:
- image: 必须是二值图像,推荐使用canny边缘检测的结果图像;
- rho: 线段以像素为单位的距离精度,double类型的,推荐用1.0
- theta: 线段以弧度为单位的角度精度,推荐用numpy.pi/180
- threshod: 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试
- lines:这个参数的意义未知,发现不同的lines对结果没影响,但是不要忽略了它的存在
- minLineLength:线段以像素为单位的最小长度,根据应用场景设置
- maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段
示例
import cv2
import numpy as np
img = cv2.imread('C:\\Users\\xxx\\Downloads\\contourImg.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 200
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
#for x1,y1,x2,y2 in lines[0]:
for i in range(0,len(lines)):
for x1,y1,x2,y2 in lines[i]:
cv2.line(img,(x1,y1),(x2,y2),(0,255,255),5)
cv.imshow('HoughLineP',img)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果如下: