计算机视觉 - python实现求解homography矩阵
要求
给定 P i ≡ [ x i , y i , 1 ] P_i \equiv [x_i,y_i,1] Pi≡[xi,yi,1] 和 P i ′ ≡ [ x i ′ , y i ′ , 1 ] P_i^{'} \equiv [x_i^{'},y_i^{'},1] Pi′≡[xi′,yi′,1],需要计算一个齐次变换矩阵 H ∈ R 3 H \in R^3 H∈R3满足 P i ′ ≡ H P i P_i^{'} \equiv HP_i Pi′≡HPi。
而大部分的数据集不能准确地计算出 H H H,所以可以计算出最优的 H H H
计算方法如下: a r g m i n ∣ ∣ H ∣ ∣ 2 2 = 1 ∣ ∣ A h ∣ ∣ , h ∈ R 9 , A ∈ R 2 N × 9 \mathop{arg min}\limits_{||H||_2^2=1}||Ah||,h \in R^9,A\in R^{2N \times 9} ∣∣H∣∣22=1argmin∣∣Ah∣∣,h∈R9,A∈R2N×9
使 A h Ah Ah的模最小的 h h h的参数则为所求的 H H H的九个参数
分析与求解
首先,我们来看看这里的
A
A
A是什么,
A
=
[
−
x
1
−
y
1
−
1
0
0
0
x
1
x
1
′
y
1
x
1
′
x
1
′
0
0
0
−
x
1
−
y
1
−
1
x
1
y
1
′
y
1
y
1
′
y
1
′
−
x
2
−
y
2
−
1
0
0
0
x
2
x
2
′
y
2
x
2
′
x
2
′
0
0
0
−
x
2
−
y
2
−
1
x
2
y
2
′
y
2
y
2
′
y
2
′
−
x
3
−
y
3
−
1
0
0
0
x
3
x
3
′
y
3
x
3
′
x
3
′
0
0
0
−
x
3
−
y
3
−
1
x
3
y
3
′
y
3
y
3
′
y
3
′
−
x
4
−
y
4
−
1
0
0
0
x
4
x
4
′
y
4
x
4
′
x
4
′
0
0
0
−
x
4
−
y
4
−
1
x
4
y
4
′
y
4
y
4
′
y
4
′
.
.
.
]
A = \begin{bmatrix} -x_1 & -y_1 & -1 & 0 & 0 & 0 & x_1x_1^{'} & y_1x_1^{'} & x_1^{'} \\ 0 & 0 & 0 & -x_1 & -y_1 & -1 & x_1y_1^{'} & y_1y_1^{'} & y_1^{'} \\ -x_2 & -y_2 & -1 & 0 & 0 & 0 & x_2x_2^{'} & y_2x_2^{'} & x_2^{'} \\ 0 & 0 & 0 & -x_2 & -y_2 & -1 & x_2y_2^{'} & y_2y_2^{'} & y_2^{'} \\ -x_3 & -y_3 & -1 & 0 & 0 & 0 & x_3x_3^{'} & y_3x_3^{'} & x_3^{'} \\ 0 & 0 & 0 & -x_3 & -y_3 & -1 & x_3y_3^{'} & y_3y_3^{'} & y_3^{'} \\ -x_4 & -y_4 & -1 & 0 & 0 & 0 & x_4x_4^{'} & y_4x_4^{'} & x_4^{'} \\ 0 & 0 & 0 & -x_4 & -y_4 & -1 & x_4y_4^{'} & y_4y_4^{'} & y_4^{'} \\ ... \end{bmatrix}
A=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡−x10−x20−x30−x40...−y10−y20−y30−y40−10−10−10−100−x10−x20−x30−x40−y10−y20−y30−y40−10−10−10−1x1x1′x1y1′x2x2′x2y2′x3x3′x3y3′x4x4′x4y4′y1x1′y1y1′y2x2′y2y2′y3x3′y3y3′y4x4′y4y4′x1′y1′x2′y2′x3′y3′x4′y4′⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
矩阵
A
A
A就是将数据点按照上述规则进行构造的矩阵
我们再来看看h
h
=
[
H
00
H
01
H
02
H
10
H
11
H
12
H
20
H
21
H
22
]
T
h = \begin{bmatrix} H_{00}& H_{01}&H_{02}& H_{10}& H_{11}&H_{12}& H_{20}&H_{21}& H_{22}& \end{bmatrix}^T
h=[H00H01H02H10H11H12H20H21H22]T
这样,问题就转化为了齐次方程线性最小二乘的求解问题,我们期望找到
A
h
=
0
Ah=0
Ah=0中
h
h
h不等于零的解。由于该方程的特殊形式我们发现:对于
h
h
h不等于零的解,我们乘上任意的尺度因子
k
k
k,使解变为
k
h
kh
kh,都不会改变最终结果,因此我们可以将问题转化为求解
h
h
h,使得
∣
∣
A
h
∣
∣
||Ah||
∣∣Ah∣∣值最小并且
∣
∣
h
∣
∣
=
1
||h||=1
∣∣h∣∣=1。
我们可以采取奇异值分解的方法,对A进行SVD分解有:
∣
∣
A
h
∣
∣
=
∣
∣
U
D
V
T
h
∣
∣
=
∣
∣
D
V
T
h
∣
∣
||Ah|| = ||UDV^Th|| = ||DV^Th||
∣∣Ah∣∣=∣∣UDVTh∣∣=∣∣DVTh∣∣
令
∣
∣
h
∣
∣
=
∣
∣
V
T
h
∣
∣
||h|| = ||V^Th||
∣∣h∣∣=∣∣VTh∣∣,可将问题进一步转化为,最小化
∣
∣
D
V
T
h
∣
∣
||DV^Th||
∣∣DVTh∣∣,且
∣
∣
V
T
h
∣
∣
=
1
||V^Th||=1
∣∣VTh∣∣=1。
又令 y = V T h y=V^Th y=VTh,有: ∣ ∣ D y ∣ ∣ ||Dy|| ∣∣Dy∣∣ 且 ∣ ∣ y ∣ ∣ = 1 ||y||=1 ∣∣y∣∣=1
对于当前问题,A列满秩,则D是对角阵,V是方阵
在奇异值分解中D的对角线元素是递减排列的,那么只需取 = ( 0 , 0 , 0 , . . . , 0 , 1 ) =(0,0,0,...,0,1) =(0,0,0,...,0,1),则 ∣ ∣ A h ∣ ∣ = ∣ ∣ D y ∣ ∣ = σ n ||Ah||=||Dy||= \sigma_n ∣∣Ah∣∣=∣∣Dy∣∣=σn, σ n \sigma_n σn是A最小的奇异值
此时: h = V y h=Vy h=Vy,
即 h h h的值为矩阵 V V V的最后一列,即为矩阵 V T V^T VT的最后一行。
这样,我们就可以解决问题了:先构造出A,然后调用scipy库中的svd函数进行奇异值分解,然后利用h构造出H即可,代码如下:
注:
- 最好不要用numpy库里的svd函数,当数据量较大时,会显示init_dgesdd failed init报错
- scipy库里的svd函数的第三个返回值为 V T V^T VT,而不是 V V V,因此是取它的最后一行,而不是最后一列
- 可视化时,注意是H左乘齐次坐标 [ x i , y i , 1 ] [x_i,y_i,1] [xi,yi,1]
具体代码如下:
import numpy as np
import matplotlib.pyplot as plt
import scipy.linalg as linalg
def fit_homography(XY):
'''
Given a set of N correspondences XY of the form [x,y,x',y'],
fit a homography from [x,y,1] to [x',y',1].
Input - XY: an array with size(N,4), each row contains two
points in the form [x_i, y_i, x'_i, y'_i] (1,4)
Output -H: a (3,3) homography matrix that (if the correspondences can be
described by a homography) satisfies [x',y',1]^T === H [x,y,1]^T
'''
A = np.zeros(shape=(XY.shape[0] * 2,9),dtype=np.float32)
for i in range(XY.shape[0]):
A[2 * i,0] = -1.0 * XY[i,0]
A[2 * i,1] = -1.0 * XY[i,1]
A[2 * i,2] = -1
A[2 * i,6] = XY[i,0] * XY[i,2]
A[2 * i,7] = XY[i,1] * XY[i,2]
A[2 * i,8] = XY[i,2]
A[2 * i + 1,3] = -1.0 * XY[i,0]
A[2 * i + 1,4] = -1.0 * XY[i,1]
A[2 * i + 1,5] = -1
A[2 * i + 1,6] = XY[i,0] * XY[i,3]
A[2 * i + 1,7] = XY[i,1] * XY[i,3]
A[2 * i + 1,8] = XY[i,3]
_, _, vt = linalg.svd(A)
H = vt[-1].reshape(3,3) # 注意是-1行,不是-1列
H = H / H[2,2] # 第三行第三列为1
return H
if __name__ == "__main__":
file = 'file.npy'
data = np.load(file) # 数据每一行为[x,y,x',y']
plt.clf()
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
xy = data[:,0:2]
xy_pie = data[:,2:]
# 添加一列1,转化为齐次坐标
A = np.c_[xy,np.ones(len(xy))]
H = fit_homography(data)
print(H)
xy_pred = np.dot(H, A.T).T
# 转化为二维坐标
xy_pred[:,0] = xy_pred[:,0] / xy_pred[:,2]
xy_pred[:,1] = xy_pred[:,1] / xy_pred[:,2]
plt.scatter(xy[:,0], xy[:,1], 1, color='orange', label='x,y')
plt.scatter(xy_pie[:,0], xy_pie[:,1], 1, color='blue', label='x\',y\'')
plt.scatter(xy_pred[:,0], xy_pred[:,1], 1, color='red', label='x_pred,y_pred')
plt.legend()
plt.show()