本章将学习如下内容:
- 学习霍夫直线变换的概念
- 如何使用霍夫直线变换来检测图像中的线条
- 介绍两个关键函数:
cv2.HoughLines()
,cv2.HourLines()
理论基础
霍夫变换(Hough Transform)是一种经典的形状检测算法,只要能用数学形式表示目标形状即可实现检测。 即使目标形状存在断裂或轻微变形,该算法仍能有效识别。本文将以直线检测为例说明其工作原理。
直线可以用斜截式方程 $y=mx+c$ 表示,也可以用极坐标参数方程 $\rho=x\cos\theta+y\sin\theta$ 表示, 其中 $\rho$ 表示原点到直线的垂直距离,$\theta$ 为该垂线与水平轴的夹角(以逆时针方向测量,具体方向取决于坐标系定义方式。OpenCV中即采用此种表示方法)。
如下图所示:
因此,如果直线从原点下方通过,ρ为正,角度小于180度。如果原点上方,则取小于180的角度,而不是大于180的角度。 任何垂直线都有0度,水平线有90度。
霍夫直线变换的实现原理
任何直线均可由参数 $(\rho, \theta)$ 表示,其具体实现流程如下:
- 初始化二维累加器数组
- 数组维度:行表示 $\rho$ ,列表示 $\theta$
- 初始值:所有元素置零
- 数组尺寸由精度要求决定:
- 角度精度为1°时,需180列(对应0°-179°)
- $\rho$的最大值为图像对角线长度,若精度为1像素,则行数等于对角线像素值
霍夫变换的累加过程示例
以100×100像素的图像为例(假设其中包含一条位于图像中央的水平线):
单点参数计算:
- 取直线上第一个已知坐标点(x,y)
- 代入直线方程,遍历 $\theta = 0,1,2,....,180$ 计算对应 $\rho$
累加器投票机制:
- 对每个 $(\rho, \theta)$ 参数对,在累加器对应单元格的值+1
- 示例结果:单元格(50,90)的值变为1(其他若干单元格也会同步更新)
霍夫变换的投票机制完整流程
多点累积投票:
- 对直线上第二个点重复上述计算
- 代入所有 $\theta$ 角度值(0°-180°)计算 $\rho$
- 在累加器中对应的 $(\rho, \theta)$ 单元格值+1
- 此时单元格(50,90)的值累积为2
全线段处理:
- 对直线上所有点执行相同操作
- 每次处理时:
- 单元格(50,90)必然获得投票(值持续增加)
- 其他单元格可能获得零星投票
结果判定:
- 最终累加器中(50,90)将获得最大投票数
- 检索最大值对应参数,即可确定直线:
- $\rho$=50(距原点50像素)
- $\theta$=90°(水平线)
可视化说明:下图动画清晰展示此过程(图像来源:Amos Storkey ))
霍夫直线变换的工作原理便是如此。该算法简洁明了,甚至可以尝试用NumPy自行实现。 下图展示了累加器的可视化效果, 图中的亮斑位置即对应图像中可能存在的直线参数(图像来源:维基百科)。
OpenCV中的霍夫变换
OpenCV 将上述原理完整封装在 cv2.HoughLines()
函数中。该函数返回一个由 $(\rho, \theta)$
值组成的数组,其中:$\rho$ 单位为像素(pixels),$\theta$单位为弧度(radians)。
函数参数详解:输入图像(Input image),
必须为二值图像(需预先进行阈值处理或Canny边缘检测);
精度参数,第二参数:$\rho$ 的检测精度(像素级步长);
第三参数:$\theta$ 的角步长(弧度制);
投票阈值(threshold),第四参数:直线判定的最小投票数阈值。
投票数对应直线上的有效点数,因此该参数实质上决定了可检测直线的最小长度。
import cv2
import numpy as np
img = cv2.imread('/data/cvdata/dave.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
lines = cv2.HoughLines(edges,1,np.pi/180,200)
for rho,theta in lines[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(img,(x1,y1),(x2,y2),(0,0,255),2)
cv2.imwrite('xx_houghlines3.jpg',img)
True
结果如下所示:
概率霍夫变换
在标准霍夫变换中,即使只是检测带有两个参数的直线,也需要进行大量计算。 概率霍夫变换(Probabilistic Hough Transform)正是对传统霍夫变换的一种优化改进。 随机抽样检测:不再考虑所有点,而是仅随机选取部分点集参与计算。 阈值调整:相应需要降低检测阈值; 计算效率:这种改进足以完成直线检测,同时显著减少计算量。
下图直观展示了霍夫空间(hough space)中标准霍夫变换与概率霍夫变换的区别(图片来源:Franck Bettinger's home page的个人主页)
OpenCV中的概率霍夫变换实现基于Matas、Galambos和Kittler提出的《使用渐进式概率霍夫变换的鲁棒直线检测》论文。
该算法通过cv2.HoughLinesP()
函数提供,相比标准霍夫变换新增了两个重要参数:
关键参数说明:
minLineLength
(最小线段长度),低于此长度的线段将被舍弃maxLineGap
(最大线段间距),允许将间距小于此值的线段视为同一条直线
该实现最显著的优势是直接返回线段的两个端点坐标。 与标准霍夫变换(仅返回直线参数 $(\rho, \theta)$,需额外计算所有点)相比, 这一改进使得结果获取更直接、算法使用更简便、计算效率更高。
import cv2
import numpy as np
img = cv2.imread('/data/cvdata/dave.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for x1,y1,x2,y2 in lines[0]:
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv2.imwrite('xx_houghlines5.jpg',img)
True