一、数学理论
我们先从最简单的数学问题出发,给定一些二维坐标点,我们怎么才能求出通过最多点的直线呢?有刷Leetcode的同学可以参考149. 直线上最多的点数这道题去思考。
大家都知道直线方程可以表示为
y
=
k
x
+
b
y = kx + b
y=kx+b的形式,所以我们可以枚举所有点之间的组合,计算出来所有组合得到的k和b。(同时还要考虑斜率不存在的情况)
简单分析一下上面做法的时间复杂度。不难理解枚举所有点之间组合的这个步骤需要
C
n
2
=
n
∗
(
n
−
1
)
1
∗
2
C_n^2 = \frac{n * (n -1)}{1*2}
Cn2=1∗2n∗(n−1)次计算过程。那么时间复杂度就是
O
(
n
2
)
O(n^2)
O(n2)的。这个复杂度应用到实际的图像中显然是不合理的。
我们可以从这篇论文中知道,使用霍夫变换我们可以用 O ( n ) O(n) O(n)的时间复杂度去检测出图像中的直线。
其实霍夫变换就是做了一下参数空间映射。对于直线方程我们能不能找到一种可以用有限的参数去表示所有的直线呢?当然可以。
r
=
x
c
o
s
θ
+
y
s
i
n
θ
r = x cos\theta + y sin\theta
r=xcosθ+ysinθ
简单手写一下推到公式
显然
θ
\theta
θ的取值范围是
[
0
o
,
18
0
o
]
[0^o, 180^o]
[0o,180o],那么
r
r
r的取值范围呢?
r
=
x
0
c
o
s
θ
+
y
0
s
i
n
θ
r = x_0cos\theta + y_0sin\theta
r=x0cosθ+y0sinθ
r
=
x
0
2
+
y
0
2
(
x
0
x
0
2
+
y
0
2
c
o
s
θ
+
y
0
x
0
2
+
y
0
2
s
i
n
θ
)
r = \sqrt{x_0^2 + y_0^2} (\frac{x_0}{\sqrt{x_0^2 + y_0^2}}cos\theta + \frac{y_0}{\sqrt{x_0^2 + y_0^2}}sin\theta)
r=x02+y02(x02+y02x0cosθ+x02+y02y0sinθ)
令:
c
o
s
ϕ
=
x
0
x
0
2
+
y
0
2
cos\phi = \frac{x_0}{\sqrt{x_0^2 + y_0^2}}
cosϕ=x02+y02x0,
s
i
n
ϕ
=
y
0
x
0
2
+
y
0
2
sin\phi = \frac{y_0}{\sqrt{x_0^2 + y_0^2}}
sinϕ=x02+y02y0
那么:
r
=
x
0
2
+
y
0
2
c
o
s
(
θ
−
ϕ
)
r =\sqrt{x_0^2 + y_0^2}cos(\theta-\phi)
r=x02+y02cos(θ−ϕ)
(其实就是高一三角函数的课后习题)
由此可知 r r r的取值范围也是确定的。这样我们就可以构造一个叫做累加器(accumulator)的二维矩阵。ok,数学理论部分到这里结束啦,接下来进入代码部分。(talk is cheap, show me the code)
二、代码实践
假设我们有一下几个点,在代码中表示啦。
import matplotlib.pyplot as plt
import math
def points2draw(points):
x = [pt[0] for pt in points]
y = [pt[1] for pt in points]
plt.scatter(x, y)
plt.show()
points = [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
points2draw(points)
霍夫变换
import matplotlib.pyplot as plt
import math
points = [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
X = []
Y = []
for pt in points:
for i in range(0, 181):
theta = math.pi / 180 * i
r = math.cos(theta) * pt[0] + math.sin(theta) *pt[1]
X.append(i)
Y.append(r)
plt.scatter(X,Y, s = 5 , alpha= 0.8)
plt.show()