参考博文:https://blog.csdn.net/qq_15642411/article/details/79585961
霍夫变换原理网上很多,我就不在过多的赘述。在此,我只简要的描述一下霍夫变换的关键要点。
霍夫变换就是通过Canny等边缘检测算法找到关键点,然后以关键点为中心,做无数条直线,如果某一条直线上的点超过设定的阈值,则这条直线被检测出来。该方法在笛卡尔坐标系下是很难被实现的,霍夫变换就是将笛卡尔坐标系下的点映射到霍夫空间上,就像傅里叶变换一样。霍夫空间就是一个极坐标系描述的点空间。
霍夫变换方程
(
极坐标方程
)
:
ρ
=
x
cos
(
θ
)
+
y
sin
(
θ
)
霍夫变换方程(极坐标方程):\rho =x\,\cos\left(\theta \right)+y\,\sin\left(\theta \right)
霍夫变换方程(极坐标方程):ρ=xcos(θ)+ysin(θ)
所以,以点为中心旋转直线的过程在霍夫空间中就是改变θ求ρ的过程,例如:
关键点 | -90° | -45° | 0° | 45° | 90° |
---|---|---|---|---|---|
(5,25) | -25 | -14 | 5 | 21 | 25 |
在OpenCV库中使用霍夫变换还有一个旋转精度的参数,就是每旋转多少度做一条直线,转换到霍夫空间,就是每隔多少度打一个点,即采样的精度,上面的例子是每隔45°打一个点。最后一个点在笛卡尔坐标系中的旋转对应着霍夫空间中的一条正弦曲线,证明如下:
ρ
=
x
cos
(
θ
)
+
y
sin
(
θ
)
=
x
cos
(
θ
)
x
2
+
y
2
+
y
sin
(
θ
)
x
2
+
y
2
x
2
+
y
2
=
cos
(
Γ
)
sin
(
θ
)
+
sin
(
Γ
)
cos
(
θ
)
x
2
+
y
2
=
sin
(
Γ
+
θ
)
x
2
+
y
2
其中:
sin
(
Γ
)
=
x
x
2
+
y
2
\rho =x\,\cos\left(\theta \right)+y\,\sin\left(\theta \right)\\ =\frac{\frac{x\,\cos\left(\theta \right)}{\sqrt{x^2+y^2}}+\frac{y\,\sin\left(\theta \right)}{\sqrt{x^2+y^2}}}{\sqrt{x^2+y^2}}\\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \qquad=\frac{\cos\left(\Gamma \right)\,\sin\left(\theta \right)+\sin\left(\Gamma \right)\,\cos\left(\theta \right)}{\sqrt{x^2+y^2}}\\=\frac{\sin\left(\Gamma +\theta \right)}{\sqrt{x^2+y^2}} \ \ \ \ \ \ \ \ \ \ \ \ \\其中:\sin\left(\Gamma \right)=\frac{x}{\sqrt{x^2+y^2}}
ρ=xcos(θ)+ysin(θ)=x2+y2x2+y2xcos(θ)+x2+y2ysin(θ) =x2+y2cos(Γ)sin(θ)+sin(Γ)cos(θ)=x2+y2sin(Γ+θ) 其中:sin(Γ)=x2+y2x
最后给出笛卡尔坐标系下的直线转化到霍夫空间的python代码:
import cv2
import numpy as np
def deg2rad(theta):
return theta / 180 * np.pi
# 绘制初始的图像(即初始化一个500*500的矩阵,并画上一条线)
img = np.zeros([500, 500]).astype(np.uint8) # 创建一个矩阵
cv2.line(img, (50, 50), (100, 100), 255) # 画直线
cv2.imshow('', img)
# 将关键点,也就是线上的点放入key_points列表中
key_points = []
for y_cnt in range(0, 500): # 将直线上的点取出(白点即像素值为255的点)
for x_cnt in range(0, 500):
if img[y_cnt][x_cnt] == 255:
key_points.append((x_cnt, y_cnt))
# 将key_points中的点转换到霍夫空间中,间隔的θ为1度,在笛卡尔坐标系中可以描述成在-90°到90°间以点为中心每隔1°画一条直线
conver_points = []
for key_point in key_points: # 将点转换到霍夫空间
conver_points_tmp = []
for theta in range(-90, 90): # 从-90°到90°打点,间隔1°
x, y = key_point
rho = x * np.cos(deg2rad(theta)) + y * np.sin(deg2rad(theta)) # 注意将角度转换成弧度
conver_points_tmp.append((int(theta), int(rho)))
conver_points.append(conver_points_tmp)
# 绘制换换到霍夫空间的曲线
hough_img = np.zeros([700, 180]).astype(np.uint8) # 转换成uint8的图像,否则imshow无法显示
for conver_point in conver_points: # 绘制霍夫空间的曲线
for point in conver_point:
theta, rho = point
hough_img[rho - 350][theta - 90] = 255 # 350 90 是为了将(0,0)坐标至于图像中间
cv2.imshow('hough', hough_img)
# 获取投票结果
point_vote = {}
for conver_point in conver_points: # 绘制霍夫空间的曲线
for point in conver_point:
theta, rho = point
key = f"{theta} {rho}"
if key not in point_vote:
point_vote[key] = 0
point_vote[key] += 1
result = sorted(point_vote.items(), key=lambda d: d[1], reverse=True)[0][0] # 获取最高得票的坐标
result = tuple(map(int, result.split()))
# 绘制结果
result_img = np.zeros([500, 500]).astype(np.uint8)
theta, rho = result
getY = lambda r, t, x: int((rho - x * np.cos(deg2rad(theta)))/np.sin(deg2rad(theta)))
cv2.line(result_img, (0, getY(rho, theta, 0)), (500, getY(rho, theta, 500)), 255) # 画直线
cv2.imshow('result', result_img)
cv2.waitKey()
笛卡尔坐标系下的直线
霍夫直线变换
曲线的交点即在笛卡尔坐标系中,在一条直线上的点,所以在霍夫空间中相交最多的那点的ρ和θ即可以表示笛卡尔坐标系中的直线。
绘制结果。