第 18 章

从直线检测到无人驾驶车道检测

霍夫变换的基础原理解说 · HoughLines() 函数 · HoughLinesP() 函数 · 霍夫圆环变换检测
霍夫变换可以将影像中的几何形状转换为参数空间中的点,再借由累加与投票找出最可能存在的直线或圆形。本章从直线的笛卡儿表示法说起,逐步进入 OpenCV 的直线与圆形检测函数,最后以车道线检测作为应用范例。
章首页与小节目录
第 18 章 从直线检测到无人驾驶车道检测 章首页
原书第 18-1 页裁切:本章标题与 18-1、18-2、18-3、18-4 小节目录。
18-1

霍夫变换的基础原理解说

霍夫变换(Hough Transform)最初是 1962 年由霍夫提出专利申请,专利名称是「辨识复杂图案的方法」(Method and Means for Recognizing Complex Patterns)。此方法的主要观念是任何一条直线可以用斜率(slope)和截距(intercept)表示,同时使用斜率和截距将一条直线参数化。

现在广泛使用的霍夫变换,是 1972 年由 Richard Duda 和 Peter Hart 发明。经典的霍夫变换是侦测影像中的直线,之后的霍夫变换不仅能辨识直线,也可以辨识其他简单形状,例如圆形和椭圆形。1981 年 Dana H. Ballard 发表了 Generalizing the Hough transform to detect arbitrary shapes,自此电脑视觉领域开始流行应用霍夫变换;目前最流行的自动驾驶,也使用霍夫变换对行车的车道进行检测。

18-1-1 认识笛卡儿座标与霍夫座标

笛卡儿座标系统就是我们熟知的直角座标系统。在直角座标系统中,一条直线可以用下列方程式表示:

y = mx + b

在上述方程式中,m 代表斜率,b 代表截距。相同的概念若映射到霍夫空间,斜率 m 和截距 b 也可以当作座标,因此笛卡儿座标中的一条直线,会映射到霍夫空间中的一个点。

18-1-2 映射观念

笛卡儿空间中存在 1 个点,可以映射到霍夫空间成一条线;笛卡儿空间中存在 2 个点,可以映射到霍夫空间成两条交叉的直线。若笛卡儿空间两个点 (x0, y0)(x1, y1) 可以连成一条直线,假设直线为 y = m1x + b1,则这条直线映射到霍夫空间的点是 (m1, b1)

b = -x0m + y0 b = -x1m + y1

18-1-3 认识极座标的基本定义

极座标(Polar coordinate system)是一个二维的座标系统,每一个点的位置使用夹角和相对原点的距离表示。点可写成 (r cosθ, r sinθ)

18-1-4 霍夫变换与极座标

在笛卡儿座标系统中,直线方程式 y = m0x + b0 的最大问题是无法表示一条垂直线,因为斜率值会是无限大。因此 Richard Duda 和 Peter Hart 发明使用极座标方式表示直线的参数,下方左图是笛卡儿空间,右图是霍夫空间;相当于极座标上的线映射到霍夫空间也是一个点 (ρ, θ)

ρ = x cosθ + y sinθ
霍夫变换原理图
霍夫变换基础原理和笛卡儿座标图
原书第 18-2 页裁切:笛卡儿座标中的点与直线方程式示意图。
笛卡儿座标直线与霍夫空间映射解说
原书第 18-3 页裁切:笛卡儿直线映射到霍夫空间中的点。
两点映射和霍夫空间交点解说
原书第 18-4 页裁切:单点映射成直线、两点映射成霍夫空间交点。
霍夫空间交点和极座标基本定义
原书第 18-5 页裁切:极座标 (r cosθ, r sinθ) 与半径分量。
霍夫变换与极座标 rho theta
原书第 18-6 页裁切:垂直线、极座标直线与霍夫空间点的对应关系。
18-2

HoughLines() 函数

OpenCV 提供了两种用于检测直线的霍夫变换函数,本节先说明基本版的 HoughLines()。这个函数需要输入二值影像,实务上常先将影像转为灰阶,再使用 Canny 做边缘侦测。

lines = cv2.HoughLines(image, rho, theta, threshold)
参数 / 回传值说明
lines函数回传值,直线参数,格式是 (ρ, θ),元素类型是 Numpy 阵列。
image要辨识的影像,这是二值影像;建议先使用 Canny 边缘侦测。
rho以像素为单位的距离 ρ,常将此值设为 1,表示检测所有可能的半径长度。
theta检测角度 θ,常将此值设为 π/180,表示检测所有可能的角度。
threshold阈值;如果此值越小,所检测的直线就会越多。

程式实例 ch18_1.py:使用 calendar.jpg 当作影像,然后 HoughLines() 函数检测此影像内的直线。

# ch18_1.py import cv2 import numpy as np src = cv2.imread('calendar.jpg', cv2.IMREAD_COLOR) cv2.imshow("src", src) src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) # 转成灰阶 edges = cv2.Canny(src_gray, 100, 200) # 使用 Canny 边缘检测 cv2.imshow("Canny", edges) # 显示 Canny 边缘线条 lines = cv2.HoughLines(edges, 1, np.pi/180, 200) # 检测直线 # 绘制直线 for line in lines: rho, theta = line[0] # lines 回传 a = np.cos(theta) # cos(theta) b = np.sin(theta) # sin(theta) x0 = rho * a y0 = rho * b x1 = int(x0 + 1000*(-b)) # 建立 x1 y1 = int(y0 + 1000*(a)) # 建立 y1 x2 = int(x0 - 1000*(-b)) # 建立 x2 y2 = int(y0 - 1000*(a)) # 建立 y2 cv2.line(src, (x1, y1), (x2, y2), (0, 255, 0), 2) # 绘制绿色线条 cv2.imshow("dst", src) cv2.waitKey(0) cv2.destroyAllWindows()
HoughLines() 执行结果
极座标空间多条曲线交会示意
原书第 18-7 页裁切:极座标空间的多条曲线交会示意。
ch18_1.py HoughLines calendar 执行结果
原书第 18-8 页裁切:原始影像、Canny 边缘影像与 HoughLines() 检测结果。
18-3

HoughLinesP() 函数

HoughLinesP() 是进阶版的直线检测函数,基本上是 18-2 节 HoughLines() 的改良,主要增加 minLineLengthmaxLineGap 参数。此函数所采用的方法称为机率霍夫变换法。

lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)
参数 / 回传值说明
lines函数回传值,直线参数为 (x1, y1)(x2, y2),只要将两点连起来就构成直线。
image要辨识的二值影像,建议先使用 Canny 边缘侦测。
rho以像素为单位的距离,常设为 1
theta检测角度,常设为 π/180
threshold阈值;如果此值越小,检测的直线会越多。
minLineLength检测直线的最小长度,短于该长度的直线会被舍去。
maxLineGap线段之间最大的允许间隙,短于该长度会被视为一条直线。

程式实例 ch18_2.py:使用 lane.jpg 影像,设计仓库道路识别。

# ch18_2.py import cv2 import numpy as np src = cv2.imread('lane.jpg', cv2.IMREAD_COLOR) cv2.imshow("src", src) src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) # 转成灰阶 edges = cv2.Canny(src_gray, 50, 200) # 使用 Canny 边缘检测 cv2.imshow("Canny", edges) lines = cv2.HoughLines(edges, 1, np.pi/180, 200) 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(src, (x1, y1), (x2, y2), (255, 0, 0), 3) cv2.imshow("dst", src) cv2.waitKey(0) cv2.destroyAllWindows()

上述范例可用于简单道路识别。对于复杂的图,可能需要不断调整 HoughLines() 函数的 threshold 参数。实际应用不同影像时,必须适度调整 HoughLinesP() 的参数。

程式实例 ch18_3.py:无人驾驶汽车的道路检测。

# ch18_3.py import cv2 import numpy as np src = cv2.imread('roadtest.jpg', cv2.IMREAD_COLOR) cv2.imshow("src", src) src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) # 转成灰阶 edges = cv2.Canny(src_gray, 50, 200) # 使用 Canny 边缘检测 cv2.imshow("Canny", edges) # 显示 Canny 边缘线条 lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength=10, maxLineGap=100) # 绘制检测到的直线 for line in lines: x1, y1, x2, y2 = line[0] cv2.line(src, (x1, y1), (x2, y2), (255, 0, 0), 3) cv2.imshow("dst", src) cv2.waitKey(0) cv2.destroyAllWindows()

检测所有车道时,当无人车在驾驶时,更重要的是检测目前车子所在车道。此时可以使用阈值处理,将目前所在车道或要专注的车道处理成 ROI 区域,这个 ROI 区块用遮罩处理。

实际应用不同影像时,必须适度调整 HoughLinesP() 的参数。
HoughLinesP() 执行结果
ch18_2.py lane HoughLines 道路识别执行结果
原书第 18-9 页裁切:仓库道路原图与直线检测结果。
ch18_3.py roadtest 无人驾驶车道检测执行结果
原书第 18-10 页裁切:道路、Canny 边缘与 HoughLinesP() 车道线检测结果。
18-4

霍夫圆环变换检测

霍夫变换除了可以检测直线,也可以用于检测其他形状的物体。本节讲解检测圆形物体的函数 HoughCircles()。在霍夫圆环检测中,其实就是要检测圆形的中心点 (x, y) 座标,和圆环的半径 r

circles = cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius)
参数 / 回传值说明
circles函数回传值,由圆中心点和半径所组成的 numpy.ndarray 阵列,格式是 [(x1, y1, r1), (x2, y2, r2), ...]
image要辨识的二值影像,需先二值化处理。
method目前可以使用 HOUGH_GRADIENT,此方法先对影像执行 Canny 边缘检测,再使用 Sobel 演算法计算区域梯度并累加圆中心。
dp累加器分辨率。例如 dp=1 时,累加器与输入影像有相同解析度;dp=2 时,累加器宽高为输入影像的一半。
minDist两个不同圆之间最小的距离。太小会将多个邻接圆检测为一个圆;太大则部分圆无法检测出来。
param1method 相关的高阈值,预设值是 100;一般低阈值是高阈值的一半。
param2method 相关的低阈值,预设值是 100。
minRadius圆半径的最小值,小于此半径的圆将被舍去,预设值是 0。
maxRadius圆半径的最大值,大于此半径的圆将被舍去,预设值是 0。

使用这个函数时,为了降低影像噪音,建议可以先用 medianBlur() 去除影像杂质。

程式实例 ch18_4.py:有一个影像内含多个圆圈,这个程式会将半径大于 70 的圆圈起来。半径小于 70 或其他外形的物件则不理会。

# ch18_4.py import cv2 import numpy as np src = cv2.imread('shapes.jpg') cv2.imshow("src", src) image = cv2.medianBlur(src, 5) # 过滤杂讯 src_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转成灰阶 circles = cv2.HoughCircles(src_gray, cv2.HOUGH_GRADIENT, 1, 100, param1=50, param2=30, minRadius=70, maxRadius=200) circles = np.uint(np.around(circles)) # 转成整数 # 绘制检测到的圆 for c in circles[0]: x, y, r = c cv2.circle(src, (x, y), r, (0, 255, 0), 3) # 绿色绘圆外圈 cv2.circle(src, (x, y), 2, (0, 0, 255), 2) # 红色绘圆中心 cv2.imshow("dst", src) cv2.waitKey(0) cv2.destroyAllWindows()
HoughCircles() 执行结果
HoughCircles 函数语法和参数说明
原书第 18-11 页裁切:HoughCircles() 语法与核心参数说明表。
ch18_4.py HoughCircles shapes 执行结果
原书第 18-12 页裁切:检测半径大于 70 的圆圈,并以绿色外圈与红色中心点标示。
习题

习题 1:使用 lane2.jpg,扩充 ch18_2.py,建立仓库道路影像。

习题 2:请重新设计 ch18_4.py,将所有圆圈起来。

第 18 章习题 lane2 仓库道路影像
原书第 18-13 页裁切:习题 1 的 lane2.jpg 仓库道路检测参考输出。
第 18 章习题全部圆圈检测参考输出
原书第 18-13 页下方裁切:习题 2 的全部圆圈检测参考输出。