前期预备知识
在开始阅读下面文章之前,首先需要掌握SVD分解数学知识、相机成像模型、向量的内积和外积相关知识,然后在进行后续阅读才会事半功倍。下面我们先来详细讲解一下相关知识:
- SVD分解数学知识
在讲解SVD分解之前,我们先看一下特征值分解(EVD),这里我们假设一个实对称 B m ∗ m B_{m*m} Bm∗m矩阵( B m ∗ m T = B m ∗ m B_{m*m}^{T} = B_{m*m} Bm∗mT=Bm∗m),可将其分为:
B
m
∗
m
=
Q
Σ
Q
T
=
Q
(
λ
1
⋯
⋯
⋯
⋯
λ
2
⋯
⋯
⋮
⋮
⋱
⋮
⋯
⋯
⋯
λ
m
)
Q
T
B_{m*m} = Q \Sigma Q^{T} = Q \begin{pmatrix} {\lambda}_1 & \cdots & \cdots & \cdots \\ \cdots & {\lambda}_2 & \cdots & \cdots\\ \vdots & \vdots & \ddots & \vdots \\ \cdots & \cdots & \cdots & {\lambda}_m \\ \end{pmatrix} Q^{T}
Bm∗m=QΣQT=Q⎝⎜⎜⎜⎛λ1⋯⋮⋯⋯λ2⋮⋯⋯⋯⋱⋯⋯⋯⋮λm⎠⎟⎟⎟⎞QT
这里的矩阵
Q
m
∗
m
Q_{m*m}
Qm∗m为正交矩阵(
Q
m
∗
m
Q
m
∗
m
T
=
I
Q_{m*m} Q_{m*m}^{T} = I
Qm∗mQm∗mT=I,
I
I
I为单位阵),数值
λ
i
{\lambda}_{i}
λi对应特征值,并且每个特征值对应一个特征向量
q
i
q_{i}
qi。特征值和特征向量具有的性质为:
B
q
i
=
λ
i
q
i
Bq_{i} = {\lambda}_{i}q_{i}
Bqi=λiqi。
上面的矩阵能通过特征分解,是因为矩阵为是实对称矩阵。但是我们实际应用过程中,很多矩阵并不一定满足此性质,并且很有可能不是方阵(
B
m
∗
n
,
m
≠
n
B_{m*n}, m \neq n
Bm∗n,m=n,矩阵中行列数不相等)。在这种情况下,要进行分解就需要引入一个概念奇异值分解(Singular Value Decomposition,SVD)。下面来看一下SVD的定义:
对于矩阵
B
m
∗
n
B_{m*n}
Bm∗n,可以将其分解为:
B
m
∗
n
=
U
Σ
V
T
=
U
(
σ
1
0
⋯
0
0
σ
2
⋯
0
⋮
⋮
⋱
⋮
0
0
⋯
0
)
V
T
B_{m*n} = U \Sigma V^{T} =U \begin{pmatrix} {\sigma}_1 & 0 & \cdots & 0 \\ 0 & {\sigma}_2 & \cdots & 0\\ \vdots &\vdots & \ddots & \vdots\\ 0 &0& \cdots & 0 \\ \end{pmatrix} V^{T}
Bm∗n=UΣVT=U⎝⎜⎜⎜⎛σ10⋮00σ2⋮0⋯⋯⋱⋯00⋮0⎠⎟⎟⎟⎞VT
这里的
U
m
∗
m
U_{m*m}
Um∗m和
V
n
∗
n
V_{n*n}
Vn∗n均为正交矩阵(
U
U
T
=
I
,
V
V
T
=
I
UU^T=I, VV^T=I
UUT=I,VVT=I),一般称
U
U
U为左奇异矩阵,
V
V
V为右奇异矩阵。
Σ
\Sigma
Σ仅在主对角线上有值,其它位置处元素均为0。
知道了什么是SVD,下面聊一聊在实际应用中该如何求解呢?
B
B
T
=
U
Σ
1
V
T
V
Σ
1
T
U
T
=
U
Σ
1
Σ
1
T
U
T
B
T
B
=
V
Σ
2
T
U
T
U
Σ
2
V
T
=
V
Σ
2
T
Σ
2
V
T
BB^T = U {\Sigma}_{1} V^{T} V{\Sigma}^T_{1} U^T = U {\Sigma}_{1} {\Sigma}^T_{1} U^T \\ B^T B = V{\Sigma}^T_{2} U^T U \Sigma_{2} V^{T} = V {\Sigma}^T_{2} {\Sigma}_{2} V^T
BBT=UΣ1VTVΣ1TUT=UΣ1Σ1TUTBTB=VΣ2TUTUΣ2VT=VΣ2TΣ2VT
上面求解公式之所以能够进一步化简,是因为
U
m
∗
m
U_{m*m}
Um∗m和
V
n
∗
n
V_{n*n}
Vn∗n均为正交矩阵。还需要说明的是
Σ
1
{\Sigma}_{1}
Σ1和
Σ
2
{\Sigma}_{2}
Σ2的维度是不相同的,
Σ
1
{\Sigma}_{1}
Σ1为
m
∗
m
m*m
m∗m方阵,
Σ
2
{\Sigma}_{2}
Σ2为
n
∗
n
n*n
n∗n方阵。
Σ
1
=
(
σ
1
2
0
⋯
0
0
σ
2
2
⋯
0
⋮
⋮
⋱
⋮
0
0
⋯
0
)
m
∗
m
Σ
2
=
(
σ
1
2
0
⋯
0
0
σ
2
2
⋯
0
⋮
⋮
⋱
⋮
0
0
⋯
0
)
n
∗
n
{\Sigma}_{1}= \begin{pmatrix} {\sigma}_1^2 & 0 & \cdots & 0 \\ 0 & {\sigma}_2^2 & \cdots & 0\\ \vdots &\vdots & \ddots & \vdots\\ 0 &0& \cdots & 0 \\ \end{pmatrix}_{m*m} \\ {\Sigma}_{2}= \begin{pmatrix} {\sigma}_1^2 & 0 & \cdots & 0 \\ 0 & {\sigma}_2^2 & \cdots & 0\\ \vdots &\vdots & \ddots & \vdots\\ 0 &0& \cdots & 0 \\ \end{pmatrix}_{n*n} \\
Σ1=⎝⎜⎜⎜⎛σ120⋮00σ22⋮0⋯⋯⋱⋯00⋮0⎠⎟⎟⎟⎞m∗mΣ2=⎝⎜⎜⎜⎛σ120⋮00σ22⋮0⋯⋯⋱⋯00⋮0⎠⎟⎟⎟⎞n∗n
虽然方阵 Σ 1 {\Sigma}_{1} Σ1和 Σ 2 {\Sigma}_{2} Σ2的维度不同,但是它们主对角线上的奇异值是相同的。通过上面的式子,我们也很轻易的得到 U U U和 V V V的对应值。
- SVD分解的应用
我们知道了SVD分解的相关理论知识,现在简单聊一聊它的一些具体应用。估计很多玩图像的人,肯定会知道一个就是使用SVD来对数据进行降维(关于这块的知识大家查看SVD(奇异值分解)小结,这里实例说明了SVD的数据降维);对于搞深度学习算法的,应该知道SVD可以对网络模型进行压缩(SVD压缩深度模型的全连接层);经常玩SLAM的人,肯定知道就是使用SVD求解超定方程,来得到相机的运动姿态等等。本篇文章主要是讲极线几何来求解相机之间的位姿,所以这里将详讲一下SVD如何求解超定方程,来得到未知变量的值。 - SVD解超定方程
这里说最简单的求解齐次方程 B X = 0 BX = 0 BX=0,因为在求解极线几何的时候就需要求解齐次方程。这里我们很明显的会发现一个特解就是 X = 0 X = 0 X=0的情况,所以这里需要对 X X X的解进行约束(因为 X = 0 X = 0 X=0的情况并不是我们想要的解)。我们添加一个约束 ∣ ∣ X ∣ ∣ 2 = 1 ||X||_{2} = 1 ∣∣X∣∣2=1,即假设 X X X的范数为1。当然可以把它约束成任意的正整数,因为齐次方程乘以任意常数,还是满足方程的,毕竟等式右边为0。可能大家会很长时间没有接触范数啦,这里简单说明一下本篇用到的二范数定义: ∣ ∣ X ∣ ∣ 2 = ( ∣ x 1 2 ∣ + ∣ x 2 2 ∣ + ⋯ + ∣ x n 2 ∣ ) 1 2 ||X||_{2} = (|x_{1}^2| + |x_{2}^2|+\cdots + |x_{n}^2|)^{\frac{1}{2}} ∣∣X∣∣2=(∣x12∣+∣x22∣+⋯+∣xn2∣)21。好啦,这里就可以将解齐次方程组的问题转化为:
m i n ( ∣ ∣ B X ∣ ∣ 2 ) , ∣ ∣ X ∣ ∣ 2 = 1 min(||BX||_{2}),||X||_{2} = 1 min(∣∣BX∣∣2),∣∣X∣∣2=1
在求解之前,我们还需要知道正交矩阵 M M M的一个性质:
∣ ∣ M X ∣ ∣ = ∣ ∣ X ∣ ∣ ||MX|| = ||X|| ∣∣MX∣∣=∣∣X∣∣
考虑到要使用这个性质,我们这里将优化问题的函数变为
∣ ∣ B X ∣ ∣ 2 = ∣ ∣ U Σ 1 V T X ∣ ∣ 2 = ∣ ∣ Σ 1 V T X ∣ ∣ 2 ||BX||_{2} = ||U {\Sigma}_{1} V^{T}X||_{2} = || {\Sigma}_{1} V^{T}X||_{2} ∣∣BX∣∣2=∣∣UΣ1VTX∣∣2=∣∣Σ1VTX∣∣2
这里我们假设 Y = V T X Y=V^{T}X Y=VTX,可以将问题转化为 m i n ( ∣ ∣ Σ 1 Y ∣ ∣ 2 ) , ∣ ∣ Y ∣ ∣ 2 = 1 min(||{\Sigma}_{1} Y||_{2}),||Y||_{2} = 1 min(∣∣Σ1Y∣∣2),∣∣Y∣∣2=1(这里需要说明一下, ∣ ∣ Y ∣ ∣ 2 = 1 ||Y||_{2} = 1 ∣∣Y∣∣2=1是根据正交矩阵的性质推导过来的)。当 Σ 1 \Sigma_{1} Σ1 的对角阵按照元素递减的顺序排列的时候,我们知道最优解的位置为: Y = ( 0 , 0 , ⋯ , 1 ) Y=(0,0,\cdots ,1) Y=(0,0,⋯,1)。因为 V V V为正交矩阵,所以 X = V Y X=VY X=VY。即最优解在矩阵 V V V最小奇异值的列向量。(关于解非齐次线性方程,请看:SVD解线性方程组——秘密大起底)
极线几何
在讲解之前我们还是要先了解一下,这个有什么作用?毕竟如果不是很重要,我们就不要学啦。其实,极线几何在SLAM中应用性是非常大。它可以根据相邻图像对应的特征点,来求解出相机之间的位姿变换。好啦,知道了有什么作用,我们来看一下极线几何中的一些定义。这里主要包括:极点、极线、基线、极平面。
我们以上面的图为例,来说明一下上述几个定义表示的什么。上图中
Q
1
Q_{1}
Q1表示图像
I
1
I_{1}
I1处的光心,
Q
2
Q_{2}
Q2表示图像
I
2
I_{2}
I2处的光心。
P
P
P为三维空间中的一点,从每张图像光心与空间点连线,会与图像平面相交于一点
p
1
p_{1}
p1和
p
2
p_{2}
p2。其中,相交点
p
1
p_{1}
p1和
p
2
p_{2}
p2则就定义为极点。两张图像光心
Q
1
Q_{1}
Q1和
Q
2
Q_{2}
Q2的连线,构成基线。基线会与两张图像平面相交,得到交点
e
1
e_{1}
e1和
e
2
e_{2}
e2。在一个图像平面上,极点与交点的连线,构成极线
l
1
l_{1}
l1和
l
1
l_{1}
l1。两个光心与空间点构成一个平面
Q
1
P
Q
2
Q_{1}PQ_{2}
Q1PQ2,此平面称为极平面。
好啦,知道了相关定义,下面我们就来看看,如何将其转化数学形式并进行求解。其实,这里的公式推导都是按照《SLAM十四讲》里面来的,只不过这里加入了一些自己的理解。这里我们假设图像
I
1
I_{1}
I1相机位置为世界起始坐标点,对应空间中一点
P
=
[
X
,
Y
,
Z
]
T
P=[X,Y,Z]^T
P=[X,Y,Z]T,相机的内参矩阵为
K
K
K。相机
Q
1
Q_{1}
Q1到相机
Q
2
Q_{2}
Q2可由旋转矩阵
R
R
R和平移矩阵
T
T
T表示。根据相机成像模型(这块可参考:相机成像模型),可得变换矩阵:
s
1
p
1
=
K
P
s
2
p
2
=
K
(
R
P
+
t
)
s_{1}p_1= KP\\ s_{2}p_2= K(RP+t)\\
s1p1=KPs2p2=K(RP+t)
这里
s
1
s_{1}
s1和
s
2
s_{2}
s2表示尺度因子,我们可以使用齐次坐标来表示,消去尺度因子得到:
p
1
=
K
P
p
2
=
K
(
R
P
+
t
)
p_1= KP\\ p_2= K(RP+t)\\
p1=KPp2=K(RP+t)
在这里需要插一句,就是说明一下什么是齐次坐标,以及它有什么作用(我这里只是简单说,详细请看:为什么要引入齐次坐标,齐次坐标的意义)。齐次坐标最直观的定义就是:用N+1维来代表N维坐标。对应表示为:
(
x
,
y
,
w
)
=
(
x
w
,
y
w
)
(x,y,w)=(\frac{x}{w},\frac{y}{w})
(x,y,w)=(wx,wy)
使用齐次坐标,可以表示平行线在透视空间的无穷远处交于一点。在欧氏空间这将变得没有意义,所以欧式坐标不能表示。齐次坐标可以表示无穷远处的点, 齐次坐标为
(
1
,
3
,
0
)
(1,3,0)
(1,3,0),而笛卡尔坐标表示为
(
1
0
,
3
0
)
=
(
∞
,
∞
)
(\frac{1}{0},\frac{3}{0})=(\infty,\infty)
(01,03)=(∞,∞)
接着上面继续讲,这里假设
x
1
x_{1}
x1和
x
2
x_{2}
x2为像素点归一化平面上的坐标(其实就是,三维空间点的坐标都除以
Z
Z
Z:什么是归一化的平面坐标)。这里补充一下,就是相机坐标系下,三维点坐标同时除以
Z
Z
Z值:
x
1
=
K
−
1
p
1
x
2
=
K
−
1
p
2
x_{1}=K^{-1}p_1 \\ x_{2}=K^{-1}p_2
x1=K−1p1x2=K−1p2
将
x
1
x_{1}
x1和
x
2
x_{2}
x2表示的方程带入到
p
1
p_{1}
p1和
p
2
p_{2}
p2表示的方程,可以得到:
x
2
=
R
x
1
+
t
x_{2} =R x_{1}+t
x2=Rx1+t
将上面方程同时乘以矩阵
t
t
t的外积,这里使用
t
∧
t^{\wedge}
t∧来表示(这里是《SLAM十四讲》前几章讲解的内容)。我们都知道向量叉乘的数值表示为:
a
×
b
=
∣
a
∣
∣
b
∣
s
i
n
(
θ
)
\bm{a}\times\bm{b}=|\bm{a}||\bm{b}|sin(\theta)
a×b=∣a∣∣b∣sin(θ),其中
θ
\theta
θ表示向量
a
\bm{a}
a和向量
b
\bm{b}
b的夹角。向量叉乘的方向符合右手定则(具体看:向量积)。知道了向量外积的定义,上述等式同时乘以
t
∧
t^{\wedge}
t∧可以得到:
t
∧
x
2
=
t
∧
R
x
1
t^{\wedge}x_{2} =t^{\wedge}R x_{1}
t∧x2=t∧Rx1
然后,上面等式同时乘以
x
2
T
x_{2}^{T}
x2T得到:
x
2
T
t
∧
x
2
=
x
2
T
t
∧
R
x
1
x_{2}^{T}t^{\wedge}x_{2} =x_{2}^{T}t^{\wedge}R x_{1}
x2Tt∧x2=x2Tt∧Rx1
因为矩阵满足结合律,所以我们可以先计算
t
∧
x
2
t^{\wedge}x_{2}
t∧x2,我们知道得到的向量方向会与向量x_{2} 垂直,所以
x
2
T
t
∧
x
2
=
0
x_{2}^{T}t^{\wedge}x_{2} =0
x2Tt∧x2=0。因此,可以得到等式:
x
2
T
t
∧
R
x
1
=
0
x_{2}^{T}t^{\wedge}R x_{1}=0
x2Tt∧Rx1=0
将
x
1
x_{1}
x1和
x
2
x_{2}
x2表示的等式带入到上述方程得到:
p
2
T
K
−
T
t
∧
R
K
−
1
p
1
=
0
p_{2}^{T}K^{-T}t^{\wedge}R K^{-1}p_1=0
p2TK−Tt∧RK−1p1=0
我们在上面这个矩阵就可以得到两个概念一个为基础矩阵
F
=
K
−
T
t
∧
R
K
−
1
F=K^{-T}t^{\wedge}R K^{-1}
F=K−Tt∧RK−1,另外一个为本质矩阵
E
=
t
∧
R
E=t^{\wedge}R
E=t∧R。它们之间最本质的区别在于,一个加入了内参矩阵
K
K
K,另外一个没有加入。因此可以得到最终矩阵为:
p
2
T
F
p
1
=
p
2
T
K
−
T
E
K
−
1
p
1
=
0
p_{2}^{T}Fp_1=p_{2}^{T}K^{-T}E K^{-1}p_1=0
p2TFp1=p2TK−TEK−1p1=0
本质矩阵的求解
本质矩阵和基础矩阵就相差了一个内参矩阵。所以这里选择本质矩阵来讲解求解的过程。在求解之前我们需要了解一下,本质矩阵 E E E具有的三个性质:
- 不同尺度下等价: 因为本质矩阵 E E E无论乘以什么值,由于等式右边为零,所以总是满足极线约束;
- E E E的奇异值必定为 [ δ , δ , 0 ] [\delta,\delta,0] [δ,δ,0]:这个要证明的话,需要看论文,可以参考(《SLAM十四讲》论文目录里面的第三篇)
- 自由度:我们知道本质矩阵是由旋转和平移组成,旋转在空间中有3个自由度(绕
x
,
y
,
z
x,y,z
x,y,z轴旋转),平移在空间中也有3个自由度(沿
x
,
y
,
z
x,y,z
x,y,z轴平移)。但由于尺度等价性,自由度实际上有5个自由度。这里怎么理解5个自由度呢!其实很简单,我们本质矩阵乘以任意数值都满足极线约束,所以我们可乘以沿
z
z
z轴平移数值的倒数。这样平移就变成了2个自由度,当然也可以将其它自由度化为1,主要是理解这个操作。
好啦,知道了性质,我们就可以求解本质矩阵啦。这里假设两张图像匹配点,对应的归一化坐标为 x 1 = [ u 1 , v 1 , 1 ] T x_1=[u_1,v_1,1]^T x1=[u1,v1,1]T和 x 2 = [ u 2 , v 2 , 1 ] T x_2=[u_2,v_2,1]^T x2=[u2,v2,1]T,可以得到极线约束方程为:
( u 1 v 1 1 ) ( e 1 e 2 e 3 e 4 e 5 e 6 e 7 e 8 e 9 ) ( u 2 v 2 1 ) = 0 \begin{pmatrix} u_1 & v_1 &1 \end{pmatrix} \begin{pmatrix} e_1 & e_2 &e_3 \\ e_4 & e_5 &e_6 \\ e_7 & e_8 &e_9 \end{pmatrix} \begin{pmatrix} u_2 \\ v_2 \\1 \end{pmatrix}=0 (u1v11)⎝⎛e1e4e7e2e5e8e3e6e9⎠⎞⎝⎛u2v21⎠⎞=0
这里将本质矩阵 E E E转化为向量的形式,目的是为了后面构造超定方程:
e = [ e 1 , e 2 , e 3 , e 4 , e 5 , e 6 , e 7 , e 8 , e 9 ] T e=[e_{1},e_{2},e_{3},e_{4},e_{5},e_{6},e_{7},e_{8},e_{9}]^T e=[e1,e2,e3,e4,e5,e6,e7,e8,e9]T
因此可以写成线性形式:
[ u 1 u 2 , u 1 v 2 , u 1 , v 1 u 2 , v 1 v 2 , v 1 , u 2 , v 2 , 1 ] ⋅ e = 0 [u_{1}u_{2},u_{1}v_{2},u_{1},v_{1}u_{2},v_{1}v_{2},v_{1},u_{2},v_{2},1] \cdot e=0 [u1u2,u1v2,u1,v1u2,v1v2,v1,u2,v2,1]⋅e=0
上面等式的转化就是最简单矩阵程序,把前面的系数提取出来即可。其它的对应点也同样表示出来得到,因为后面会说到八点法求解,所以这里写出八组对应点得到的超定方程:
( u 1 1 u 2 1 u 1 1 v 2 1 u 1 1 v 1 1 u 2 1 v 1 1 v 2 1 v 1 1 u 2 1 v 2 1 1 u 1 2 u 2 2 u 1 2 v 2 2 u 1 2 v 1 2 u 2 2 v 1 2 v 2 2 v 1 2 u 2 2 v 2 2 1 u 1 1 u 2 1 u 1 1 v 2 1 u 1 1 v 1 1 u 2 1 v 1 1 v 2 1 v 1 1 u 2 1 v 2 1 1 u 1 3 u 2 3 u 1 3 v 2 3 u 1 3 v 1 3 u 2 3 v 1 3 v 2 3 v 1 3 u 2 3 v 2 3 1 u 1 4 u 2 4 u 1 4 v 2 4 u 1 4 v 1 4 u 2 4 v 1 4 v 2 4 v 1 4 u 2 4 v 2 4 1 u 1 5 u 2 5 u 1 5 v 2 5 u 1 5 v 1 5 u 2 5 v 1 5 v 2 5 v 1 5 u 2 5 v 2 5 1 u 1 6 u 2 6 u 1 6 v 2 6 u 1 6 v 1 6 u 2 6 v 1 6 v 2 6 v 1 6 u 2 6 v 2 6 1 u 1 7 u 2 7 u 1 7 v 2 7 u 1 7 v 1 7 u 2 7 v 1 7 v 2 7 v 1 7 u 2 7 v 2 7 1 u 1 8 u 2 8 u 1 8 v 2 8 u 1 8 v 1 8 u 2 8 v 1 8 v 2 8 v 1 8 u 2 8 v 2 8 1 ) ( e 1 e 2 e 3 e 4 e 5 e 6 e 7 e 8 e 9 ) = 0 \begin{pmatrix} u_{1}^1u_{2}^1 & u_{1}^1v_{2}^1 & u_{1}^1 & v_{1}^1u_{2}^1 & v_{1}^1v_{2}^1 & v_{1}^1 & u_{2}^1 & v_{2}^1 & 1 \\ u_{1}^2u_{2}^2 & u_{1}^2v_{2}^2 & u_{1}^2 & v_{1}^2u_{2}^2 & v_{1}^2v_{2}^2 & v_{1}^2 & u_{2}^2 & v_{2}^2 & 1 \\ u_{1}^1u_{2}^1 & u_{1}^1v_{2}^1 & u_{1}^1 & v_{1}^1u_{2}^1 & v_{1}^1v_{2}^1 & v_{1}^1 & u_{2}^1 & v_{2}^1 & 1 \\ u_{1}^3u_{2}^3 & u_{1}^3v_{2}^3 & u_{1}^3 & v_{1}^3u_{2}^3 & v_{1}^3v_{2}^3 & v_{1}^3 & u_{2}^3 & v_{2}^3 & 1 \\ u_{1}^4u_{2}^4 & u_{1}^4v_{2}^4 & u_{1}^4 & v_{1}^4u_{2}^4 & v_{1}^4v_{2}^4 & v_{1}^4 & u_{2}^4 & v_{2}^4 & 1 \\ u_{1}^5u_{2}^5 & u_{1}^5v_{2}^5 & u_{1}^5 & v_{1}^5u_{2}^5 & v_{1}^5v_{2}^5 & v_{1}^5 & u_{2}^5 & v_{2}^5& 1 \\ u_{1}^6u_{2}^6 & u_{1}^6v_{2}^6 & u_{1}^6 & v_{1}^6u_{2}^6 & v_{1}^6v_{2}^6 & v_{1}^6 & u_{2}^6 & v_{2}^6 & 1 \\ u_{1}^7u_{2}^7 & u_{1}^7v_{2}^7 & u_{1}^7 & v_{1}^7u_{2}^7 & v_{1}^7v_{2}^7 & v_{1}^7 & u_{2}^7 & v_{2}^7 & 1 \\ u_{1}^8u_{2}^8 & u_{1}^8v_{2}^8 & u_{1}^8 & v_{1}^8u_{2}^8 & v_{1}^8v_{2}^8 & v_{1}^8 & u_{2}^8 & v_{2}^8 & 1 \\ \end{pmatrix} \begin{pmatrix} e_1 \\ e_2 \\e_3 \\ e_4 \\ e_5 \\e_6 \\ e_7 \\ e_8 \\e_9 \end{pmatrix}=0 ⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛u11u21u12u22u11u21u13u23u14u24u15u25u16u26u17u27u18u28u11v21u12v22u11v21u13v23u14v24u15v25u16v26u17v27u18v28u11u12u11u13u14u15u16u17u18v11u21v12u22v11u21v13u23v14u24v15u25v16u26v17u27v18u28v11v21v12v22v11v21v13v23v14v24v15v25v16v26v17v27v18v28v11v12v11v13v14v15v16v17v18u21u22u21u23u24u25u26u27u28v21v22v21v23v24v25v26v27v28111111111⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛e1e2e3e4e5e6e7e8e9⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞=0
上面系数矩阵构成了 8 × 9 8 \times 9 8×9的矩阵,当系数矩阵矩阵为满秩矩阵时(秩为8),则零空间维度(定义可参考:零空间概念,Ax=0)为1。也就是 e \bm{e} e构成了一条直线,满足尺度等价性。这样我们可以使用SVD分解系数矩阵(定义已经在上面讲解),可以得到下面解:
t 1 ∧ = U R Z ( π 2 ) Σ U T , R 1 = U R Z ( π 2 ) T V T t 2 ∧ = U R Z ( − π 2 ) Σ U T , R 2 = U R Z ( − π 2 ) T V T t^{\wedge}_1=UR_{Z(\frac{\pi}{2})} \Sigma U^{T},R_1=UR_{Z(\frac{\pi}{2})}^T V^{T} \\t^{\wedge}_2=UR_{Z(-\frac{\pi}{2})} \Sigma U^{T},R_2=UR_{Z(-\frac{\pi}{2})}^T V^{T} t1∧=URZ(2π)ΣUT,R1=URZ(2π)TVTt2∧=URZ(−2π)ΣUT,R2=URZ(−2π)TVT
R Z ( π 2 ) R_{Z(\frac{\pi}{2})} RZ(2π)表示沿 z z z轴旋转90度。因为 − E -E −E和 E E E是等价矩阵( − B = Q E P -B=QEP −B=QEP, Q Q Q和 P P P为可逆矩阵),所以任意 t t t取负号是相同。所以我们可以得到四个解:
( t 1 ∧ , R 1 ) , ( t 2 ∧ , R 1 ) , ( t 1 ∧ , R 2 ) , ( t 2 ∧ , R 2 ) (t^{\wedge}_1,R_1),(t^{\wedge}_2,R_1),(t^{\wedge}_1,R_2),(t^{\wedge}_2,R_2) (t1∧,R1),(t2∧,R1),(t1∧,R2),(t2∧,R2)
四个解对应分别对应的空间点位置以及投影点位置。我们知道深度值肯定为正值所以
(
2
)
(2)
(2)和
(
3
)
(3)
(3)就不考虑啦,
(
4
)
(4)
(4)不满足相机的成像模式,所以我们会根据空间位置和投影位置从四个解里面选择出一个正解。
在这里还需要指出一点,我们在前面已经说过啦。本质矩阵的奇异值为
[
δ
,
δ
,
0
]
[\delta,\delta,0]
[δ,δ,0],但是由于求解过程中的误差,我们往往求解出来的奇异值为
[
δ
1
,
δ
2
,
δ
3
]
[\delta_1,\delta_2,\delta_3]
[δ1,δ2,δ3]。这种情况下,该如何解决呢。其实,我们可以转化为求最优解问题:
m
i
n
∣
∣
E
−
E
′
∣
∣
2
=
m
i
n
∣
∣
U
(
Σ
−
Σ
′
)
V
T
∣
∣
2
=
m
i
n
∣
∣
Σ
−
Σ
′
∣
∣
2
min||E-E^{'}||_2 = min||U (\Sigma -\Sigma^{'})V^{T} ||_2 =min||\Sigma -\Sigma^{'} ||_2
min∣∣E−E′∣∣2=min∣∣U(Σ−Σ′)VT∣∣2=min∣∣Σ−Σ′∣∣2
我们假定
Σ
=
(
δ
0
0
0
δ
0
0
0
0
)
=
0
\Sigma = \begin{pmatrix} \delta & 0& 0\\ 0 &\delta& 0\\ 0 & 0& 0\\ \end{pmatrix}=0
Σ=⎝⎛δ000δ0000⎠⎞=0,
Σ
′
=
(
δ
1
0
0
0
δ
2
0
0
0
δ
3
)
=
0
\Sigma^{'} = \begin{pmatrix} \delta_1 & 0& 0\\ 0 &\delta_2& 0\\ 0 & 0& \delta_3\\ \end{pmatrix}=0
Σ′=⎝⎛δ1000δ2000δ3⎠⎞=0。所以可以将最小化问题转化为:
m
i
n
(
(
δ
1
−
δ
)
2
+
(
δ
2
−
δ
)
2
+
(
δ
3
−
0
)
2
)
1
2
min((\delta_1-\delta)^2+ (\delta_2-\delta)^2+(\delta_3-0)^2)^{\frac{1}{2}}
min((δ1−δ)2+(δ2−δ)2+(δ3−0)2)21
求解上述方程可以得到
δ
=
(
δ
1
+
δ
2
)
2
\delta = \frac{(\delta_1+\delta_2)}{2}
δ=2(δ1+δ2),所以在实际应用中,我们可以得到奇异值
[
(
δ
1
+
δ
2
)
2
,
(
δ
1
+
δ
2
)
2
,
0
]
[\frac{(\delta_1+\delta_2)}{2},\frac{(\delta_1+\delta_2)}{2},0]
[2(δ1+δ2),2(δ1+δ2),0]
单应性矩阵
知道了极线几何,再看单应性矩阵,那就太简单啦。我们还是先讨论一下,什么时候需要使用单应性矩阵。对于场景中特征特征点几乎全都分布在同一平面上(飞机俯视拍摄的场景等),这时候使用单应性矩阵来估计相机的运行最为合适。
知道了上面应用的背景,下面我们来看看它对应的数学推导,这个主要就涉及一个法向量表示平面方程,其它的就没有啥啦:
n
T
P
+
d
=
0
n^TP+d=0
nTP+d=0
上面为构建的特征点所落在平面对应的平面方程。后面就是化简带入:
−
n
T
P
d
=
1
-\frac{n^TP}{d}=1
−dnTP=1
p
2
=
K
(
R
P
+
t
)
p
2
=
K
(
R
P
+
t
⋅
(
−
n
T
P
d
)
)
=
K
(
R
−
t
n
T
d
)
K
−
1
p
1
p_2= K(RP+t)\\ p_2=K(RP+t \cdot (-\frac{n^TP}{d}))=K(R-\frac{tn^T}{d})K^{-1}p_{1}
p2=K(RP+t)p2=K(RP+t⋅(−dnTP))=K(R−dtnT)K−1p1
我们使用
H
H
H表示
K
(
R
−
t
n
T
d
)
K
−
1
K(R-\frac{tn^T}{d})K^{-1}
K(R−dtnT)K−1,因此
p
2
=
H
p
1
p_2=Hp_1
p2=Hp1。下面在求解的时候,就是我们在SLAM中经常使用到的DLT(Direct Linear Transform)求解。我们可以把等式转化为(和上面本质矩阵求解相似):
(
u
2
v
2
1
)
=
(
h
1
h
2
h
3
h
4
h
5
h
6
h
7
h
8
h
9
)
(
u
1
v
1
1
)
\begin{pmatrix} u_2 & v_2 &1 \end{pmatrix}= \begin{pmatrix} h_1 & h_2 &h_3 \\ h_4 & h_5 &h_6 \\ h_7 & h_8 &h_9 \end{pmatrix} \begin{pmatrix} u_1 \\ v_1 \\1 \end{pmatrix}
(u2v21)=⎝⎛h1h4h7h2h5h8h3h6h9⎠⎞⎝⎛u1v11⎠⎞
在实际应用中,往往会乘以一个非零因子使
h
9
=
1
h_9=1
h9=1,然后在去除这个非零因子,在这里我们写详细一点:
m
(
u
2
v
2
1
)
=
(
h
1
h
2
h
3
h
4
h
5
h
6
h
7
h
8
1
)
(
u
1
v
1
1
)
m\begin{pmatrix} u_2 & v_2 &1 \end{pmatrix}= \begin{pmatrix} h_1 & h_2 &h_3 \\ h_4 & h_5 &h_6 \\ h_7 & h_8 &1 \end{pmatrix} \begin{pmatrix} u_1 \\ v_1 \\1 \end{pmatrix}
m(u2v21)=⎝⎛h1h4h7h2h5h8h3h61⎠⎞⎝⎛u1v11⎠⎞
我们可以得到对应解为:
m
u
2
=
h
1
u
1
+
h
2
v
1
+
h
3
m
v
2
=
h
4
u
1
+
h
5
v
1
+
h
6
m
=
h
7
u
1
+
h
8
v
1
+
1
mu_2 =h_1u_1+ h_2 v_1+h_3\\ mv_2 =h_4u_1+ h_5 v_1+h_6\\ m=h_7u_1+ h_8 v_1+1
mu2=h1u1+h2v1+h3mv2=h4u1+h5v1+h6m=h7u1+h8v1+1
在进行化简,分别除以
m
m
m得到:
u
2
=
h
1
u
1
+
h
2
v
1
+
h
3
h
7
u
1
+
h
8
v
1
+
1
v
2
=
h
4
u
1
+
h
5
v
1
+
h
6
h
7
u
1
+
h
8
v
1
+
1
u_2 =\frac{h_1u_1+ h_2 v_1+h_3}{h_7u_1+ h_8 v_1+1}\\ v_2 =\frac{h_4u_1+ h_5 v_1+h_6}{h_7u_1+ h_8 v_1+1}
u2=h7u1+h8v1+1h1u1+h2v1+h3v2=h7u1+h8v1+1h4u1+h5v1+h6
化简上述方程可以得到:
u
2
=
h
1
u
1
+
h
2
v
1
+
h
3
−
h
7
u
1
u
2
−
h
8
v
1
u
2
v
2
=
h
4
u
1
+
h
5
v
1
+
h
6
−
h
7
u
1
v
2
−
h
8
v
1
v
2
u_2 =h_1u_1+ h_2 v_1+h_3-h_7u_1u_2- h_8 v_1u_2\\ v_2 =h_4u_1+ h_5 v_1+h_6-h_7u_1v_2- h_8 v_1v_2
u2=h1u1+h2v1+h3−h7u1u2−h8v1u2v2=h4u1+h5v1+h6−h7u1v2−h8v1v2
也就是一组对应点可以得到两个方程组,我们需要四组点,来求解8个未知参数,因为我们将
h
9
=
1
h_9=1
h9=1。所以方程为:
(
u
1
1
v
1
1
1
0
0
0
−
u
1
1
u
2
1
−
v
1
1
v
2
1
0
0
0
u
1
1
v
1
1
1
−
u
1
1
u
2
1
−
v
1
1
v
2
1
u
1
2
v
1
2
1
0
0
0
−
u
1
2
u
2
2
−
v
1
2
v
2
2
0
0
0
u
1
2
v
1
2
1
−
u
1
2
u
2
2
−
v
1
2
v
2
2
u
1
3
v
1
3
1
0
0
0
−
u
1
3
u
2
3
−
v
1
3
v
2
3
0
0
0
u
1
3
v
1
3
1
−
u
1
3
u
2
3
−
v
1
3
v
2
3
u
1
4
v
1
4
1
0
0
0
−
u
1
4
u
2
4
−
v
1
4
v
2
4
0
0
0
u
1
4
v
1
4
1
−
u
1
4
u
2
4
−
v
1
4
v
2
4
)
(
h
1
h
2
h
3
h
4
h
5
h
6
h
7
h
8
)
=
0
\begin{pmatrix} u_{1}^1 & v_{1}^1 & 1&0 & 0 & 0 & -u_{1}^1u_{2}^1 &-v_{1}^1v_{2}^1 \\ 0& 0 & 0& u_{1}^1 & v_{1}^1 & 1&-u_{1}^1u_{2}^1 &-v_{1}^1v_{2}^1\\ u_{1}^2 & v_{1}^2 & 1&0 & 0 & 0 & -u_{1}^2u_{2}^2 &-v_{1}^2v_{2}^2 \\ 0& 0 & 0& u_{1}^2 & v_{1}^2 & 1&-u_{1}^2u_{2}^2 &-v_{1}^2v_{2}^2\\ u_{1}^3 & v_{1}^3 & 1&0 & 0 & 0 & -u_{1}^3u_{2}^3 &-v_{1}^3v_{2}^3 \\ 0& 0 & 0& u_{1}^3 & v_{1}^3 & 1&-u_{1}^3u_{2}^3 &-v_{1}^3v_{2}^3\\ u_{1}^4 & v_{1}^4 & 1&0 & 0 & 0 & -u_{1}^4u_{2}^4 &-v_{1}^4v_{2}^4 \\ 0& 0 & 0& u_{1}^4 & v_{1}^4 & 1&-u_{1}^4u_{2}^4 &-v_{1}^4v_{2}^4\\ \end{pmatrix} \begin{pmatrix} h_1 \\ h_2 \\h_3 \\ h_4 \\ h_5 \\h_6 \\ h_7 \\ h_8 \end{pmatrix}=0
⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛u110u120u130u140v110v120v130v140101010100u110u120u130u140v110v120v130v1401010101−u11u21−u11u21−u12u22−u12u22−u13u23−u13u23−u14u24−u14u24−v11v21−v11v21−v12v22−v12v22−v13v23−v13v23−v14v24−v14v24⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛h1h2h3h4h5h6h7h8⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞=0
代码实现
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
// #include "extra.h" // use this if in OpenCV2
using namespace std;
using namespace cv;
/****************************************************
* 本程序演示了如何使用2D-2D的特征匹配估计相机运动
* **************************************************/
void find_feature_matches (
const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches );
void pose_estimation_2d2d (
std::vector<KeyPoint> keypoints_1,
std::vector<KeyPoint> keypoints_2,
std::vector< DMatch > matches,
Mat& R, Mat& t );
// 像素坐标转相机归一化坐标
Point2d pixel2cam ( const Point2d& p, const Mat& K );
int main ( int argc, char** argv )
{
if ( argc != 3 )
{
cout<<"usage: pose_estimation_2d2d img1 img2"<<endl;
return 1;
}
//-- 读取图像
Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );
Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );
vector<KeyPoint> keypoints_1, keypoints_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );
cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;
//-- 估计两张图像间运动
Mat R,t;
pose_estimation_2d2d ( keypoints_1, keypoints_2, matches, R, t );
//-- 验证E=t^R*scale
Mat t_x = ( Mat_<double> ( 3,3 ) <<
0, -t.at<double> ( 2,0 ), t.at<double> ( 1,0 ),
t.at<double> ( 2,0 ), 0, -t.at<double> ( 0,0 ),
-t.at<double> ( 1,0 ), t.at<double> ( 0,0 ), 0 );
cout<<"t^R="<<endl<<t_x*R<<endl;
//-- 验证对极约束
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
for ( DMatch m: matches )
{
Point2d pt1 = pixel2cam ( keypoints_1[ m.queryIdx ].pt, K );
Mat y1 = ( Mat_<double> ( 3,1 ) << pt1.x, pt1.y, 1 );
Point2d pt2 = pixel2cam ( keypoints_2[ m.trainIdx ].pt, K );
Mat y2 = ( Mat_<double> ( 3,1 ) << pt2.x, pt2.y, 1 );
Mat d = y2.t() * t_x * R * y1;
cout << "epipolar constraint = " << d << endl;
}
return 0;
}
void find_feature_matches ( const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches )
{
//-- 初始化
Mat descriptors_1, descriptors_2;
// used in OpenCV3
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// use this if you are in OpenCV2
// Ptr<FeatureDetector> detector = FeatureDetector::create ( "ORB" );
// Ptr<DescriptorExtractor> descriptor = DescriptorExtractor::create ( "ORB" );
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create ( "BruteForce-Hamming" );
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect ( img_1,keypoints_1 );
detector->detect ( img_2,keypoints_2 );
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> match;
//BFMatcher matcher ( NORM_HAMMING );
matcher->match ( descriptors_1, descriptors_2, match );
//-- 第四步:匹配点对筛选
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ )
{
double dist = match[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
printf ( "-- Max dist : %f \n", max_dist );
printf ( "-- Min dist : %f \n", min_dist );
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ) )
{
matches.push_back ( match[i] );
}
}
}
Point2d pixel2cam ( const Point2d& p, const Mat& K )
{
return Point2d
(
( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),
( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 )
);
}
void pose_estimation_2d2d ( std::vector<KeyPoint> keypoints_1,
std::vector<KeyPoint> keypoints_2,
std::vector< DMatch > matches,
Mat& R, Mat& t )
{
// 相机内参,TUM Freiburg2
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
//-- 把匹配点转换为vector<Point2f>的形式
vector<Point2f> points1;
vector<Point2f> points2;
for ( int i = 0; i < ( int ) matches.size(); i++ )
{
points1.push_back ( keypoints_1[matches[i].queryIdx].pt );
points2.push_back ( keypoints_2[matches[i].trainIdx].pt );
}
//-- 计算基础矩阵
Mat fundamental_matrix;
fundamental_matrix = findFundamentalMat ( points1, points2, CV_FM_8POINT );
cout<<"fundamental_matrix is "<<endl<< fundamental_matrix<<endl;
//-- 计算本质矩阵
Point2d principal_point ( 325.1, 249.7 ); //相机光心, TUM dataset标定值
double focal_length = 521; //相机焦距, TUM dataset标定值
Mat essential_matrix;
essential_matrix = findEssentialMat ( points1, points2, focal_length, principal_point );
cout<<"essential_matrix is "<<endl<< essential_matrix<<endl;
//-- 计算单应矩阵
Mat homography_matrix;
homography_matrix = findHomography ( points1, points2, RANSAC, 3 );
cout<<"homography_matrix is "<<endl<<homography_matrix<<endl;
//-- 从本质矩阵中恢复旋转和平移信息.
recoverPose ( essential_matrix, points1, points2, R, t, focal_length, principal_point );
cout<<"R is "<<endl<<R<<endl;
cout<<"t is "<<endl<<t<<endl;
OK!终于写完啦!有什么问题请在下方留言。其实主要还是《SLAM十四讲》内容,只不过我在此基础上加入了一些自己的理解和说明而已。
了解更多关于《计算机视觉与图形学》相关知识,请关注公众号:
下载我们视频中代码和相关讲义,请在公众号回复:计算机视觉课程资料
参考的链接:
《SLAM十四讲》
SVD(奇异值分解)小结
Cmd Markdown 公式指导手册
SVD解线性方程组——秘密大起底
SLAM十四讲代码