极线几何&单应性矩阵 理论+实践

前期预备知识

在开始阅读下面文章之前,首先需要掌握SVD分解数学知识、相机成像模型、向量的内积和外积相关知识,然后在进行后续阅读才会事半功倍。下面我们先来详细讲解一下相关知识:

  • SVD分解数学知识

在讲解SVD分解之前,我们先看一下特征值分解(EVD),这里我们假设一个实对称 B m ∗ m B_{m*m} Bmm矩阵( B m ∗ m T = B m ∗ m B_{m*m}^{T} = B_{m*m} BmmT=Bmm),可将其分为:

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} Bmm=QΣQT=Qλ1λ2λmQT
这里的矩阵 Q m ∗ m Q_{m*m} Qmm为正交矩阵( Q m ∗ m Q m ∗ m T = I Q_{m*m} Q_{m*m}^{T} = I QmmQmmT=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 Bmn,m=n,矩阵中行列数不相等)。在这种情况下,要进行分解就需要引入一个概念奇异值分解(Singular Value Decomposition,SVD)。下面来看一下SVD的定义:
对于矩阵 B m ∗ n B_{m*n} Bmn,可以将其分解为:
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} Bmn=UΣVT=Uσ1000σ20000VT
这里的 U m ∗ m U_{m*m} Umm V n ∗ n V_{n*n} Vnn均为正交矩阵( 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} Umm V n ∗ n V_{n*n} Vnn均为正交矩阵。还需要说明的是 Σ 1 {\Sigma}_{1} Σ1 Σ 2 {\Sigma}_{2} Σ2的维度是不相同的, Σ 1 {\Sigma}_{1} Σ1 m ∗ m m*m mm方阵, Σ 2 {\Sigma}_{2} Σ2 n ∗ n n*n nn方阵。
Σ 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=σ12000σ220000mmΣ2=σ12000σ220000nn

虽然方阵 Σ 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 X2=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}} X2=(x12+x22++xn2)21。好啦,这里就可以将解齐次方程组的问题转化为:
    m i n ( ∣ ∣ B X ∣ ∣ 2 ) , ∣ ∣ X ∣ ∣ 2 = 1 min(||BX||_{2}),||X||_{2} = 1 min(BX2),X2=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} BX2=UΣ1VTX2=Σ1VTX2
    这里我们假设 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(Σ1Y2),Y2=1(这里需要说明一下, ∣ ∣ Y ∣ ∣ 2 = 1 ||Y||_{2} = 1 Y2=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=K1p1x2=K1p2
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=absin(θ),其中 θ \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} tx2=tRx1
然后,上面等式同时乘以 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} x2Ttx2=x2TtRx1
因为矩阵满足结合律,所以我们可以先计算 t ∧ x 2 t^{\wedge}x_{2} tx2,我们知道得到的向量方向会与向量x_{2} 垂直,所以 x 2 T t ∧ x 2 = 0 x_{2}^{T}t^{\wedge}x_{2} =0 x2Ttx2=0。因此,可以得到等式:
x 2 T t ∧ R x 1 = 0 x_{2}^{T}t^{\wedge}R x_{1}=0 x2TtRx1=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 p2TKTtRK1p1=0
我们在上面这个矩阵就可以得到两个概念一个为基础矩阵 F = K − T t ∧ R K − 1 F=K^{-T}t^{\wedge}R K^{-1} F=KTtRK1,另外一个为本质矩阵 E = t ∧ R E=t^{\wedge}R E=tR。它们之间最本质的区别在于,一个加入了内参矩阵 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=p2TKTEK1p1=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)e1e4e7e2e5e8e3e6e9u2v21=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 u11u21u12u22u11u21u13u23u14u24u15u25u16u26u17u27u18u28u11v21u12v22u11v21u13v23u14v24u15v25u16v26u17v27u18v28u11u12u11u13u14u15u16u17u18v11u21v12u22v11u21v13u23v14u24v15u25v16u26v17u27v18u28v11v21v12v22v11v21v13v23v14v24v15v25v16v26v17v27v18v28v11v12v11v13v14v15v16v17v18u21u22u21u23u24u25u26u27u28v21v22v21v23v24v25v26v27v28111111111e1e2e3e4e5e6e7e8e9=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 minEE2=minU(ΣΣ)VT2=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+(δ30)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(RdtnT)K1p1
我们使用 H H H表示 K ( R − t n T d ) K − 1 K(R-\frac{tn^T}{d})K^{-1} K(RdtnT)K1,因此 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)=h1h4h7h2h5h8h3h6h9u1v11
在实际应用中,往往会乘以一个非零因子使 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)=h1h4h7h2h5h8h3h61u1v11
我们可以得到对应解为:
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+h3h7u1u2h8v1u2v2=h4u1+h5v1+h6h7u1v2h8v1v2
也就是一组对应点可以得到两个方程组,我们需要四组点,来求解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 u110u120u130u140v110v120v130v140101010100u110u120u130u140v110v120v130v1401010101u11u21u11u21u12u22u12u22u13u23u13u23u14u24u14u24v11v21v11v21v12v22v12v22v13v23v13v23v14v24v14v24h1h2h3h4h5h6h7h8=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十四讲代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值