1.问题简介
霍夫变换常用来在图像中提取直线和圆等几何形状,如下图所示:
在这里我使用霍夫变换主要是想来提取图像中的直线.
2.基本原理
一条直线可由两个点A=(X1,Y1)和B=(X2,Y2)确定(笛卡尔坐标)
另一方面 y=kx+q也可以写成关于(k,q)的函数表达式(霍夫空间):
对应的变换可以通过图形直观表示:
变换后的空间成为霍夫空间。即:笛卡尔坐标系中一条直线,对应霍夫空间的一个点。
注意
当以下情况
k=∞是不方便表示的,而且q怎么取值呢,这样不是办法。因此考虑将笛卡尔坐标系换为:极坐标表示。
上面的公式有一处错误,应该是
x
cos
θ
+
y
sin
θ
=
ρ
x \cos \theta+y \sin \theta=\rho
xcosθ+ysinθ=ρ
在极坐标系下,其实是一样的:极坐标的点→霍夫空间的直线,只不过霍夫空间不再是[k,q]的参数,而是的参数,给出对比图:
以上就是霍夫变换的基本原理.
3.应用原理
在实现的图像处理领域,图像的像素坐标P(x, y)是已知的,而r, theta则是我们要寻找的变量。如果我们能绘制每个(r, theta)值根据像素点坐标P(x, y)值的话,那么就从图像笛卡尔坐标系统转换到极坐标霍夫空间系统,这种从点到曲线的变换称为直线的霍夫变换。变换通过量化霍夫参数空间为有限个值间隔等分或者累加格子
首先创建一个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,θ) 累加数,累加数超过一定值后就认为在同一直线上(有一个阈值)。
4.python代码实现
# coding:utf8
import cv2
import numpy as np
# 读入图像
img = cv2.imread("/home/cheng/Pictures/cangchu.png")
# 转为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Canny边缘检测
edges = cv2.Canny(gray, 50, 100)
"""
canny边缘检测:
有五个步骤:
1 高斯滤波器降噪
2 计算梯度
3 边缘上使用非最大抑制 nms
4 边缘上使用双阈值去除假阳性
5 分析所有边缘连接 消除不明显的边缘
"""
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength, maxLineGap)
"""
cv2.HoughLinesP
作用:标准霍夫线变换, 找到图像中的所有直线
参数:
1 二值图
2 半径精度
3 角度精度
4 最短检测长度
5 允许的最大缺口
返回:
一个列表,每一项是一个四元组,分别是直线两个端点的坐标
"""
for line in lines:
for x1, y1, x2, y2 in line:
# 在图片上画直线
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow("edges", edges)
cv2.imshow("lines", img)
cv2.waitKey()
cv2.destroyAllWindows()