目录:一、引入极坐标
二、霍夫线变换实现原理
三、图像中的霍夫线变换
四、概率霍夫变换
五、Python 例子
六、参考
Hough线变换是一种用于检测直线的变换。它最大的优点是,即使是虚线(dashed line),或者某些部分缺失、被遮挡的直线,也能检测到完整的线条。
一、引入极坐标
我们经常在直角坐标系中用两个参数表示直线:
,其中 k 为斜率(slope),b为截距(intercept)。但是当直线趋近于垂直x轴时,k值会变得非常大(
)或非常小(
)以至于无法表示该直线。
所以对于霍夫线变换,常在极坐标中表示一条直线:
直线在极坐标系下的表达式为:
由该图可以看出直线表达式是如何形成的
(x, y)为直角坐标系中的一个点A,这个表达式就表示经过点(x, y)的所有直线,每条直线所对应的
和
都不相同。
这里
表示原点到直线的距离,以像素为单位;
是这条垂直线和水平轴形成的角度。
所以图像空间的每一个像素点都对应一个这样的正弦表达式。
二、霍夫线变换实现原理
2.1、对于某个点
,我们可以将穿过该点的所有直线的集合定义为:
这意味着其中的每对
都表示经过
的一条直线。
2.2、给定一个点
,我们会得到过该点的一系列直线,将这些直线对应的参数
值画在
平面中,我们可以得到一条正弦曲线(只考虑
和
的点。):
上面是经过点(8, 6)的所有直线的极坐标表达式中的
参数所构成的曲线。
3.3、我们可以对其他的点进行相同的操作,按照上面的例子,画出另外两点
和
的曲线图:
如果两个不同点所对应的曲线在平面
中相交,就意味着两个点属于同一条直线,因为它们有相同的
参数。
如上图所示,这三条曲线相交于点(0.925,9.6),这是参数
的值,表示
、
和
都在这两个参数值所决定的直线上。
我们把
平面看做霍夫空间,
就是该空间中的点。而极坐标中的一个点就对应霍夫空间中的一条曲线。
三、图像中的霍夫线变换
知道了霍夫线变换的实现原理,怎么应用于检测图像中的直线呢?上面提到的点在图像中是对应哪些点呢?
进行霍夫线变换之前需要先对图像进行canny边缘检测得到二值图,将这个二值图进行霍夫线变换。
我们知道这个二值图除了图像中的边缘像素,其他像素的灰度值都为零,即表现黑色。而我们就是要根据这些非零灰度值的像素点(边缘像素),来获取在
平面中的曲线。
就是根据每个边缘像素(x,y),我们将
的值从0更改为
,根据公式
,获得对应的
值。从而获取所有边缘像素在
平面的曲线。
得到每个边缘像素的曲线后,通过计算
平面中各交点所在的曲线数量来检测直线。某点相交的曲线越多,意味着该交点所表示的直线具有越多的点,即在图像中直线越长。
我们还可以定义阈值,在某点相交的曲线数量大于该阈值,才认为该点在图像中对应一条直线。
所以要理解霍夫变换最关键的一点就是:
极坐标的点(
-
平面) <=> 直角坐标系中的曲线(
-
平面)
即在图像中一个边缘像素点,经过霍夫变换,变成霍夫空间(
-
平面)中的一条正弦曲线。
四、概率霍夫变换
上面介绍的标准霍夫变换,其本质上就是把图像中的边缘像素映射到它的霍夫空间,比如一共有M个边缘像素,则所有的边缘像素都需要进行映射,其运算量和所需内存都会很大。
如果只处理图像的m(m
下面是概率霍夫变换的简易步骤:随机抽取图像中的一个边缘像素点,如果已经被标定为是某一条直线上的点,则继续在剩下的边缘点中随机抽取一个边缘点,直到所有边缘点都抽取完为止;
对该点进行霍夫变换,并进行累加计算;
选取在霍夫空间内累加值最大的点,如果该点的值大于阈值,则进行步骤4,否则回到步骤1;
对于累加值大于阈值的点,从该点出发,沿着图像中的直线的方向位移,从而找到直线的两个端点;
计算直线的长度,如果大于某个阈值,则被认为是直线并输出。
五、Python 例子
OpenCV 中有霍夫线变换的API,我们直接调用即可。
原图为:白底黑线
我这里用的概率霍夫线变换函数:
HoughLinesP(image, rho, theta, threshold)image:二值图(边缘检测器的输出);
rho:距离分辨率,以像素为单位;
theta:角度分辨率,单位为弧度;
threshold:交点的最小曲线数量大于阈值,才被视为一条直线。
maxLineGap:这是一个可选参数,表示两个线段之间的gap小于该值,则进行连接
import cv2
import numpy as np
img = cv2.imread("/home/zxd/Pictures/lines.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# detecting the edges
edges = cv2.Canny(gray, 50, 200)
# apply probabilistic hough transform for line detection
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30)
# maxLineGap 参数的值为200,则线之间的gap小于200像素就将其进行连接
lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=30, maxLineGap=200)
# 得到所有线段的端点
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(img, (x1, y1), (x2, y2), (0,0,255))
cv2.imshow('img', img)
if cv2.waitKey(0) == ord('q'):
cv2.destroyAllWindows()
下面是输出的结果:
两者分别为不使用 maxLineGap 参数和使用 maxLineGap 参数的结果图。
所以即使遇到虚线或者被遮挡的直线,我们也可以使用霍夫线变换来进行直线检测。
六、参考
如果觉得有用,点个赞吧(ง •̀_•́)ง。