OpenCV基础矩阵求解解析笔记

文章目录

    • 1. 基础矩阵求解原理
      • 1.1 基础矩阵推导
        • 1.1.1 相机模型
        • 1.1.2 对极几何
        • 1.1.3 基础矩阵性质
      • 1.2 7 7 7点法求解基础矩阵
      • 1.3 8 8 8点法求解基础矩阵
      • 1.4 使用像素点坐标归一化优化基本矩阵求解
      • 1.5 使用RANSAC算法优化基本矩阵求解
      • 1.6 使用LMedS算法优化基本矩阵求解
    • 2. 基础矩阵求解代码解析

1. 基础矩阵求解原理

1.1 基础矩阵推导

在计算机视觉中,基础矩阵 F \boldsymbol{F} F是一个 3 × 3 3\times3 3×3矩阵,用于表示立体像对的像点之间的对应关系。

立体象对:从两个不同位置对同一区域所拍摄的一对相片。

1.1.1 相机模型

相机将三维世界的坐标(单位为米)映射到二维图像平面(单位为像素)的过程可以用一个几何模型进行描述。这种几何模型有很多种类,其中最简单的是针孔模型。它描述了光线通过针孔在成像平面投影的关系。

OpenCV基础矩阵求解解析笔记_第1张图片

P \boldsymbol{P} P点在相机坐标系下的坐标为 ( X , Y , Z ) (X,Y,Z) (X,Y,Z),其在物理成像平面上的投影为 P ′ \boldsymbol{P}' P,坐标为 ( X ′ , Y ′ ) (X',Y') (X,Y)。根据三角形相似关系,有:
Z f = − X X ′ = − Y Y ′ \frac{Z}{f}=-\frac{X}{X'}=-\frac{Y}{Y'} fZ=XX=YY
为了简化模型,我们把可以物理成像平面对称到相机前方,这样做可以把公式中的负号去掉,使式子更加简洁:
Z f = X X ′ = Y Y ′ \frac{Z}{f}=\frac{X}{X'}=\frac{Y}{Y'} fZ=XX=YY
整理上式可得:
X ′ = f X Z Y ′ = f Y Z X'=f\frac{X}{Z}\\ Y'=f\frac{Y}{Z} X=fZXY=fZY
物理成像平面上的点( P ′ = [ X ′ , Y ′ ] \boldsymbol{P}'=[X',Y'] P=[X,Y])还需映射至像素平面( P ′ = [ u , v ] \boldsymbol{P}'=[u,v] P=[u,v])。像素坐标系通常的定义方式是:原点 o ′ o' o位于图像的左上角, u u u轴向右与 x x x轴平行, v v v轴向下与 y y y轴平行。像素坐标系与成像平面之间相差了一个缩放和一个原点平移。我们设像素坐标在 u u u轴上缩放了 α \alpha α倍,在 v v v轴上缩放了 β \beta β倍,同时,原点平移了 [ c x , c y ] T [c_x,c_y]^T [cx,cy]T,那么,像素坐标可表示为:
{ u = α X ′ + c x v = β Y ′ + c y \left\{\begin{matrix} \begin{aligned} &u=\alpha X'+c_x\\ &v=\beta Y'+c_y \end{aligned} \end{matrix}\right. {u=αX+cxv=βY+cy
α f \alpha f αf合并为 f x f_x fx,将 β f \beta f βf合并为 f y f_y fy,可得:
{ u = f x X Z + c x v = f y Y Z + c y \left\{\begin{matrix} \begin{aligned} &u=f_x\frac{X}{Z}+c_x\\ &v=f_y\frac{Y}{Z}+c_y \end{aligned} \end{matrix}\right. u=fxZX+cxv=fyZY+cy
将上式写成矩阵形式,左侧使用齐次坐标,右侧使用非齐次坐标:
( u v 1 ) = 1 Z ( f x 0 c x 0 f y c y 0 0 1 ) ( X Y Z ) = def 1 Z K P \begin{pmatrix}u\\v\\1\end{pmatrix}=\frac{1}{Z}\begin{pmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{pmatrix}\begin{pmatrix}X\\Y\\Z\end{pmatrix}\overset{\text{def}}{=}\frac{1}{Z}\boldsymbol{K}\boldsymbol{P} uv1 =Z1 fx000fy0cxcy1 XYZ =defZ1KP
Z Z Z挪至左侧:
Z ( u v 1 ) = ( f x 0 c x 0 f y c y 0 0 1 ) ( X Y Z ) = def K P Z\begin{pmatrix}u\\v\\1\end{pmatrix}=\begin{pmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{pmatrix}\begin{pmatrix}X\\Y\\Z\end{pmatrix}\overset{\text{def}}{=}\boldsymbol{K}\boldsymbol{P} Z uv1 = fx000fy0cxcy1 XYZ =defKP
其中 K \boldsymbol{K} K表示相机的内参数矩阵

结合相机姿态,我们可以将世界坐标系中的点 P W \boldsymbol{P}_W PW映射至像素坐标系:
Z P u , v = Z [ u v 1 ] = K ( R P W + t ) Z\boldsymbol{P}_{u,v}=Z\begin{bmatrix}u\\v\\1\end{bmatrix}=\boldsymbol{K}(\boldsymbol{R}\boldsymbol{P}_W+\boldsymbol{t}) ZPu,v=Z uv1 =K(RPW+t)
其中相机姿态 R \boldsymbol{R} R t \boldsymbol{t} t又称为相机的外参矩阵。在上式中, P W \boldsymbol{P}_W PW为非齐次坐标,而 P u , v \boldsymbol{P}_{u,v} Pu,v为齐次坐标,如果两个坐标系都采用齐次坐标,那么上式可重写为:
Z P u , v = K [ R t ] P W = K T P W Z\boldsymbol{P}_{u,v}=\boldsymbol{K}\begin{bmatrix}\boldsymbol{R}&\boldsymbol{t}\end{bmatrix}\boldsymbol{P}_W=\boldsymbol{K}\boldsymbol{T}\boldsymbol{P}_W ZPu,v=K[Rt]PW=KTPW

1.1.2 对极几何

对极几何描述的是两幅图像之间的内在投影关系,与外部场景无关,只依赖于相机内参和这两幅图像之间的的相对姿态

OpenCV基础矩阵求解解析笔记_第2张图片

假设 P \boldsymbol{P} P是三维空间中的一个点, O 1 \boldsymbol{O}_1 O1 O 2 \boldsymbol{O}_2 O2分别表示两个相机中心, I 1 I_1 I1 I 2 I_2 I2分别表示两个成像平面, p 1 \boldsymbol{p}_1 p1 p 2 \boldsymbol{p}_2 p2分别表示 P \boldsymbol{P} P点在两个成像平面上的投影。由上图所示, P \boldsymbol{P} P p 1 \boldsymbol{p}_1 p1 p 2 \boldsymbol{p}_2 p2 O 1 \boldsymbol{O}_1 O1以及 O 2 \boldsymbol{O}_2 O2五点共面,这个平面被称为极平面 O 1 O 2 \boldsymbol{O}_1\boldsymbol{O}_2 O1O2的连线与成像平面 I 1 I_1 I1 I 2 I_2 I2分别相交于 e 1 \boldsymbol{e}_1 e1 e 2 \boldsymbol{e}_2 e2 O 1 O 2 \boldsymbol{O}_1\boldsymbol{O}_2 O1O2被称为基线 e 1 \boldsymbol{e}_1 e1 e 2 \boldsymbol{e}_2 e2被称为极点。在两个成像平面内,极点 e \boldsymbol{e} e与投影点 p \boldsymbol{p} p的连线 l 1 \boldsymbol{l}_1 l1 l 2 \boldsymbol{l}_2 l2被称为极线

假设我们知道 I 1 I_1 I1平面上的 p 1 \boldsymbol{p}_1 p1点,那么如何在 I 2 I_2 I2平面上找到 p 2 \boldsymbol{p}_2 p2点呢?我们通过两个相机中心 O 1 \boldsymbol{O}_1 O1 O 2 \boldsymbol{O}_2 O2,以及 p 1 \boldsymbol{p}_1 p1点,就可以确定极平面。 O 2 p 2 \boldsymbol{O}_2\boldsymbol{p}_2 O2p2射线也位于极平面之上,因此 p 2 \boldsymbol{p}_2 p2点必定出现在极平面与 I 2 I_2 I2平面的交线,即极线 l 2 \boldsymbol{l}_2 l2之上。该极线也是 O 1 p 1 \boldsymbol{O}_1\boldsymbol{p}_1 O1p1射线在 I 2 I_2 I2平面上的投影。对于双目立体匹配算法而言,对于与 p 1 \boldsymbol{p}_1 p1点对应的 p 2 \boldsymbol{p}_2 p2点的搜索不需要覆盖整个图像平面,而可以限制在极线 l 2 \boldsymbol{l}_2 l2之上。

下面我们来推导极线以及基础矩阵公式。根据相机模型可知, P \boldsymbol{P} P点在 I 1 I_1 I1 I 2 I_2 I2平面上的投影可由如下公式表示:
s 1 p 1 = K 1 T 1 P = M 1 P ,   s 2 p 2 = K 2 T 2 P = M 2 P s_1\boldsymbol{p}_1=\boldsymbol{K}_1\boldsymbol{T}_1\boldsymbol{P}=\boldsymbol{M}_1\boldsymbol{P},\ s_2\boldsymbol{p}_2=\boldsymbol{K}_2\boldsymbol{T}_2\boldsymbol{P}=\boldsymbol{M}_2\boldsymbol{P} s1p1=K1T1P=M1P, s2p2=K2T2P=M2P
其中 s s s表示 P \boldsymbol{P} P点深度(与 P \boldsymbol{P} P点在相机坐标系下的 Z Z Z分量相关)。令矩阵 M 1 + \boldsymbol{M}_1^+ M1+表示矩阵 M 1 \boldsymbol{M}_1 M1的伪逆矩阵,即 M 1 M 1 + = I \boldsymbol{M}_1\boldsymbol{M}_1^+=\boldsymbol{I} M1M1+=I。那么分析上式可知, M 1 + p 1 \boldsymbol{M}_1^+\boldsymbol{p}_1 M1+p1点位于 O 1 P \boldsymbol{O}_1\boldsymbol{P} O1P射线之上( M 1 + p 1 \boldsymbol{M}_1^+\boldsymbol{p}_1 M1+p1点的第 4 4 4个分量 w w w为比例因子,用于对非齐次坐标进行缩放)。我们将相机中心 O 1 \boldsymbol{O}_1 O1点以及 M 1 + p 1 \boldsymbol{M}_1^+\boldsymbol{p}_1 M1+p1点同时投影到 I 2 I_2 I2平面,既可推导出极线 l 2 \boldsymbol{l}_2 l2的表达式:
l 2 = e 2 × ( M 2 M 1 + p 1 ) = ( M 2 O 1 ) × ( M 2 M 1 + p 1 ) \begin{aligned} \boldsymbol{l}_2&=\boldsymbol{e}_2\times(\boldsymbol{M}_2\boldsymbol{M}_1^+\boldsymbol{p_1}) \\&=(\boldsymbol{M}_2\boldsymbol{O}_1)\times(\boldsymbol{M}_2\boldsymbol{M}_1^+\boldsymbol{p_1}) \\ \end{aligned} l2=e2×(M2M1+p1)=(M2O1)×(M2M1+p1)
由于 O 1 \boldsymbol{O}_1 O1点, P \boldsymbol{P} P点深度 s s s以及 M 1 + p 1 \boldsymbol{M}_1^+\boldsymbol{p}_1 M1+p1点比例因子 w w w仅会对最终结果产生缩放效果,不影响直线的表示,因此上式在书写过程中忽略掉了这些因子。

对于齐次坐标,两点叉乘可以表示过这两点的直线。假设像素平面上存在两点 p 1 \boldsymbol{p}_1 p1 p 2 \boldsymbol{p}_2 p2,其非齐次坐标分别为 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2),那么过这两点的直线可以表示为:
( y 1 − y 2 ) x + ( x 2 − x 1 ) y + ( x 1 y 2 − x 2 y 1 ) = 0 A x + B y + C = 0 (y_1-y_2)x+(x_2-x_1)y+(x_1y_2-x_2y_1)=0\\ Ax+By+C=0 (y1y2)x+(x2x1)y+(x1y2x2y1)=0Ax+By+C=0
使用齐次坐标表示上述两点, p 1 = ( x 1 , y 1 , 1 ) \boldsymbol{p}_1=(x_1,y_1,1) p1=(x1,y1,1) p 2 = ( x 2 , y 2 , 1 ) \boldsymbol{p}_2=(x_2,y_2,1) p2=(x2,y2,1),那么这两点的叉乘结果为:
p 1 × p 2 = ( x 1 , y 1 , 1 ) × ( x 2 , y 2 , 1 ) = ( y 1 − y 2 , x 2 − x 1 , x 1 y 2 − x 2 y 1 ) \boldsymbol{p}_1\times\boldsymbol{p}_2=(x_1,y_1,1)\times(x_2,y_2,1)=(y_1-y_2,x_2-x_1,x_1y_2-x_2y_1) p1×p2=(x1,y1,1)×(x2,y2,1)=(y1y2,x2x1,x1y2x2y1)
叉乘结果的三个分量与直线方程的 A A A B B B C C C系数一致,因此在齐次坐标下可以使用两点叉乘表示过这两点的直线。

我们将世界坐标系与第一个相机的坐标系相重合,那么有:
O 1 = [ 0 0 0 1 ] ,   M 1 = K 1 [ I 0 ] ,   M 2 = K 2 [ R t ] ,   M 1 + = [ K 1 − 1 0 T ] \boldsymbol{O}_1=\begin{bmatrix}0\\0\\0\\1\end{bmatrix},\ \boldsymbol{M}_1=\boldsymbol{K}_1\begin{bmatrix}\boldsymbol{I}&\boldsymbol{0}\end{bmatrix},\ \boldsymbol{M}_2=\boldsymbol{K}_2\begin{bmatrix}\boldsymbol{R}&\boldsymbol{t}\end{bmatrix},\ \boldsymbol{M}_1^+=\begin{bmatrix}\boldsymbol{K}_1^{-1}\\\boldsymbol{0}^T\end{bmatrix} O1= 0001 , M1=K1[I0], M2=K2[Rt], M1+=[K110T]
其中 R \boldsymbol{R} R t \boldsymbol{t} t表示两个相机之间的旋转平移量。将上述各值代入极线 l 2 \boldsymbol{l}_2 l2的表达式有:
l 2 = ( K 2 [ R t ] [ 0 0 0 1 ] ) × ( K 2 [ R t ] [ K 1 − 1 0 T ] p 1 ) = ( K 2 t ) ∧ ( K 2 R K 1 − 1 p 1 ) = K 2 − T t ∧ R K 1 − 1 p 1 \begin{aligned} \boldsymbol{l}_2&=\left(\boldsymbol{K}_2\begin{bmatrix}\boldsymbol{R}&\boldsymbol{t}\end{bmatrix}\begin{bmatrix}0\\0\\0\\1\end{bmatrix}\right)\times \left(\boldsymbol{K}_2\begin{bmatrix}\boldsymbol{R}&\boldsymbol{t}\end{bmatrix}\begin{bmatrix}\boldsymbol{K}_1^{-1}\\\boldsymbol{0}^T\end{bmatrix}\boldsymbol{p}_1\right)\\ &=\left(\boldsymbol{K}_2\boldsymbol{t}\right)^{\wedge}\left(\boldsymbol{K}_2\boldsymbol{R}\boldsymbol{K}_1^{-1}\boldsymbol{p}_1\right)\\ &=\boldsymbol{K}_2^{-T}\boldsymbol{t}^{\wedge}\boldsymbol{R}\boldsymbol{K}_1^{-1}\boldsymbol{p}_1\end{aligned} l2= K2[Rt] 0001 ×(K2[Rt][K110T]p1)=(K2t)(K2RK11p1)=K2TtRK11p1
其中 ∧ ^\wedge 表示反对称符号:
t ∧ = [ 0 − t 3 t 2 t 3 0 − t 1 − t 2 t 1 0 ] \boldsymbol{t}^{\wedge}=\begin{bmatrix} 0 & -t_3 & t_2\\ t_3 & 0 & -t_1\\ -t_2 & t_1 & 0 \end{bmatrix} t= 0t3t2t30t1t2t10
我们定义基础矩阵 F \boldsymbol{F} F的表达式如下:
F = K 2 − T t ∧ R K 1 − 1 \boldsymbol{F}=\boldsymbol{K}_2^{-T}\boldsymbol{t}^{\wedge}\boldsymbol{R}\boldsymbol{K}_1^{-1} F=K2TtRK11
于是极线 l 2 \boldsymbol{l}_2 l2可写为:
l 2 = F p 1 \boldsymbol{l}_2=\boldsymbol{F}\boldsymbol{p}_1 l2=Fp1
同理,可推导出极线 l 1 \boldsymbol{l}_1 l1的表达式为:
l 1 T = p 2 T F \boldsymbol{l}_1^T=\boldsymbol{p}_2^T\boldsymbol{F} l1T=p2TF
又因为 p 2 \boldsymbol{p}_2 p2点必定位于极线 l 2 \boldsymbol{l}_2 l2之上,那么有:
p 2 T F p 1 = 0 \boldsymbol{p}_2^T\boldsymbol{F}\boldsymbol{p}_1=0 p2TFp1=0
我们将上述关系式称为对极约束

1.1.3 基础矩阵性质

从基础矩阵 F \boldsymbol{F} F的构造方式上看,其有以下特性:

  • 基础矩阵 F \boldsymbol{F} F是由对极约束定义的。由于对极约束是等式为零的约束,所以对 F \boldsymbol{F} F乘以任意非零常数后,对极约束依然满足。我们把这件事情称为 F \boldsymbol{F} F在不同尺度下是等价的。
  • 根据 F = K 2 − T t ∧ R K 1 − 1 \boldsymbol{F}=\boldsymbol{K}_2^{-T}\boldsymbol{t}^{\wedge}\boldsymbol{R}\boldsymbol{K}_1^{-1} F=K2TtRK11,可以证明,基础矩阵 F \boldsymbol{F} F的秩为 2 2 2
  • 两组相机内参各有 4 4 4个待定参数,平移旋转量各有 3 3 3个待定参数,因此基础矩阵 F \boldsymbol{F} F一共有 14 14 14个待定参数。但由于 F \boldsymbol{F} F是一个 3 × 3 3\times 3 3×3的矩阵,那么其自由度最大为 9 9 9。考虑到 F \boldsymbol{F} F具有尺度等价性以及其秩为 2 2 2(行列式为 0 0 0)两个约束,所以基础矩阵 F \boldsymbol{F} F自由度为 9 − 2 = 7 9-2=7 92=7

关于性质 2 2 2的证明有:

​ 对 t ∧ \boldsymbol{t}^{\wedge} t进行初等变换:
t ∧ = [ 0 − t 3 t 2 t 3 0 − t 1 − t 2 t 1 0 ] → [ 0 − t 3 t 2 t 2 t 3 0 − t 1 t 2 − t 2 t 3 t 1 t 3 0 ] → [ 0 − t 3 t 1 t 2 t 1 0 t 1 t 3 − t 1 t 2 − t 2 t 3 t 1 t 3 0 ] → [ 0 0 0 0 t 3 − t 2 − t 2 t 1 0 ] \begin{aligned} &\boldsymbol{t}^{\wedge}=\begin{bmatrix} 0 & -t_3 & t_2\\ t_3 & 0 & -t_1\\ -t_2 & t_1 & 0 \end{bmatrix}\\&\rightarrow \begin{bmatrix} 0 & -t_3 & t_2\\ t_2t_3 & 0 & -t_1t_2\\ -t_2t_3 & t_1t_3 & 0 \end{bmatrix}\\&\rightarrow \begin{bmatrix} 0 & -t_3t_1 & t_2t_1\\ 0 & t_1t_3 & -t_1t_2\\ -t_2t_3 & t_1t_3 & 0 \end{bmatrix}\\&\rightarrow \begin{bmatrix} 0 & 0 & 0\\ 0 & t_3 & -t_2\\ -t_2 & t_1 & 0 \end{bmatrix} \end{aligned} t= 0t3t2t30t1t2t10 0t2t3t2t3t30t1t3t2t1t20 00t2t3t3t1t1t3t1t3t2t1t1t20 00t20t3t10t20

​ 由此可知 t ∧ \boldsymbol{t}^{\wedge} t的秩为 2 2 2 F \boldsymbol{F} F的其他构造矩阵均为可逆矩阵,与其相乘不影响秩,所以基础矩阵 F \boldsymbol{F} F的秩也为 2 2 2

1.2 7 7 7点法求解基础矩阵

考虑一对匹配点,它们的齐次像素点坐标分别为 p 1 = [ u 1 v 1 1 ] T \boldsymbol{p}_1=\begin{bmatrix}u_1&v_1&1\end{bmatrix}^T p1=[u1v11]T p 2 = [ u 2 v 2 1 ] T \boldsymbol{p}_2=\begin{bmatrix}u_2&v_2&1\end{bmatrix}^T p2=[u2v21]T。根据对极约束有:
( u 2 v 2 1 ) ( f 1 f 2 f 3 f 4 f 5 f 6 f 7 f 8 f 9 ) ( u 1 v 1 1 ) = 0 \begin{pmatrix}u_2&v_2&1\end{pmatrix} \begin{pmatrix} f_1 & f_2 & f_3\\ f_4 & f_5 & f_6\\ f_7 & f_8 & f_9 \end{pmatrix} \begin{pmatrix}u_1\\v_1\\1\end{pmatrix} =0 (u2v21) f1f4f7f2f5f8f3f6f9 u1v11 =0
将基础矩阵 F \boldsymbol{F} F展开,写成向量的形式:
f = [ f 1 f 2 f 3 f 4 f 5 f 6 f 7 f 8 f 9 ] T \boldsymbol{f}=\begin{bmatrix}f_1&f_2&f_3&f_4&f_5&f_6&f_7&f_8&f_9\end{bmatrix}^T f=[f1f2f3f4f5f6f7f8f9]T
那么对极约束可以写成与 f \boldsymbol{f} f有关的线性形式:
[ u 2 u 1 u 2 v 1 u 2 v 2 u 1 v 2 v 1 v 2 u 1 v 1 1 ] ⋅ f = 0 \begin{bmatrix}u_2u_1&u_2v_1&u_2&v_2u_1&v_2v_1&v_2&u_1&v_1&1\end{bmatrix}\cdot\boldsymbol{f}=0 [u2u1u2v1u2v2u1v2v1v2u1v11]f=0
现在我们考虑 7 7 7对匹配点,将它们整理成线性方程组( u i u^i ui v i v^i vi表示第 i i i个匹配点),有:
( u 2 1 u 1 1 u 2 1 v 1 1 u 2 1 v 2 1 u 1 1 v 2 1 v 1 1 v 2 1 u 1 1 v 1 1 1 u 2 2 u 1 2 u 2 2 v 1 2 u 2 2 v 2 2 u 1 2 v 2 2 v 1 2 v 2 2 u 1 2 v 1 2 1 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ u 2 7 u 1 7 u 2 7 v 1 7 u 2 7 v 2 7 u 1 7 v 2 7 v 1 7 v 2 7 u 1 7 v 1 7 1 ) ( f 1 f 2 f 3 f 4 f 5 f 6 f 7 f 8 f 9 ) = A f = 0 \begin{pmatrix} u_2^1u_1^1&u_2^1v_1^1&u_2^1&v_2^1u_1^1&v_2^1v_1^1&v_2^1&u_1^1&v_1^1&1\\ u_2^2u_1^2&u_2^2v_1^2&u_2^2&v_2^2u_1^2&v_2^2v_1^2&v_2^2&u_1^2&v_1^2&1\\ \vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\vdots\\ u_2^7u_1^7&u_2^7v_1^7&u_2^7&v_2^7u_1^7&v_2^7v_1^7&v_2^7&u_1^7&v_1^7&1 \end{pmatrix}\begin{pmatrix}f_1\\f_2\\f_3\\f_4\\f_5\\f_6\\f_7\\f_8\\f_9\end{pmatrix}=\boldsymbol{A}\boldsymbol{f}=\boldsymbol{0} u21u11u22u12u27u17u21v11u22v12u27v17u21u22u27v21u11v22u12v27u17v21v11v22v12v27v17v21v22v27u11u12u17v11v12v17111 f1f2f3f4f5f6f7f8f9 =Af=0
一般地,系数矩阵 A \boldsymbol{A} A的秩为 7 7 7,所以上述方程组的解集是 9 9 9维空间中通过坐标原点的一张 2 2 2维平面。令 f 1 \boldsymbol{f}_1 f1 f 2 \boldsymbol{f}_2 f2为方程组的两个单位正交解,考虑到基本矩阵 F \boldsymbol{F} F具有尺度等价性,那么可以用 f = s f 1 + ( 1 − s ) f 2 \boldsymbol{f}=s\boldsymbol{f}_1+(1-s)\boldsymbol{f}_2 f=sf1+(1s)f2表示方程组的解集,于是基本矩阵 F \boldsymbol{F} F可以表示为:
F = s F 1 + ( 1 − s ) F 2 \boldsymbol{F}=s\boldsymbol{F}_1+(1-s)\boldsymbol{F}_2 F=sF1+(1s)F2
由于基本矩阵 F \boldsymbol{F} F的秩为 2 2 2,因此可以得到一个关于 s s s的约束方程:
det ( s F 1 + ( 1 − s ) F 2 ) = 0 \text{det}(s\boldsymbol{F}_1+(1-s)\boldsymbol{F}_2)=0 det(sF1+(1s)F2)=0
这是一个关于 s s s 3 3 3次方程,因此有 1 1 1个解或 3 3 3个解(如果是复数解,则该解无效,因为基本矩阵 F \boldsymbol{F} F是一个实矩阵)。

关于单位正交解 f 1 \boldsymbol{f}_1 f1 f 2 \boldsymbol{f}_2 f2的求解采用 SVD \text{SVD} SVD分解计算。对系数矩阵 A \boldsymbol{A} A进行 SVD \text{SVD} SVD分解得: A = U Σ V T \boldsymbol{A}=\boldsymbol{U}\boldsymbol{\Sigma }\boldsymbol{V}^T A=UΣVT,由于系数矩阵 A \boldsymbol{A} A的秩为 7 7 7,所以正交矩 V \boldsymbol{V} V的最后两列列向量即单位正交解。

1.3 8 8 8点法求解基础矩阵

现在我们考虑 8 8 8对匹配点,将它们整理成线性方程组( u i u^i ui v i v^i vi表示第 i i i个匹配点),有:
( u 2 1 u 1 1 u 2 1 v 1 1 u 2 1 v 2 1 u 1 1 v 2 1 v 1 1 v 2 1 u 1 1 v 1 1 1 u 2 2 u 1 2 u 2 2 v 1 2 u 2 2 v 2 2 u 1 2 v 2 2 v 1 2 v 2 2 u 1 2 v 1 2 1 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ u 2 8 u 1 8 u 2 8 v 1 8 u 2 8 v 2 8 u 1 8 v 2 8 v 1 8 v 2 8 u 1 8 v 1 8 1 ) ( f 1 f 2 f 3 f 4 f 5 f 6 f 7 f 8 f 9 ) = A f = 0 \begin{pmatrix} u_2^1u_1^1&u_2^1v_1^1&u_2^1&v_2^1u_1^1&v_2^1v_1^1&v_2^1&u_1^1&v_1^1&1\\ u_2^2u_1^2&u_2^2v_1^2&u_2^2&v_2^2u_1^2&v_2^2v_1^2&v_2^2&u_1^2&v_1^2&1\\ \vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\vdots\\ u_2^8u_1^8&u_2^8v_1^8&u_2^8&v_2^8u_1^8&v_2^8v_1^8&v_2^8&u_1^8&v_1^8&1 \end{pmatrix}\begin{pmatrix}f_1\\f_2\\f_3\\f_4\\f_5\\f_6\\f_7\\f_8\\f_9\end{pmatrix}=\boldsymbol{A}\boldsymbol{f}=\boldsymbol{0} u21u11u22u12u28u18u21v11u22v12u28v18u21u22u28v21u11v22u12v28u18v21v11v22v12v28v18v21v22v28u11u12u18v11v12v18111 f1f2f3f4f5f6f7f8f9 =Af=0
一般地,系数矩阵 A \boldsymbol{A} A的秩为 8 8 8,所以上述方程组的解集是 9 9 9维空间中通过坐标原点的一条直线。其单位解的求解也可以采用 SVD \text{SVD} SVD分解计算。对系数矩阵 A \boldsymbol{A} A进行 SVD \text{SVD} SVD分解得: A = U Σ V T \boldsymbol{A}=\boldsymbol{U}\boldsymbol{\Sigma }\boldsymbol{V}^T A=UΣVT,正交矩 V \boldsymbol{V} V的最后一列列向量即单位解。

由于噪声等因素的原因,根据线性方程组求解的基本矩阵 F \boldsymbol{F} F可能不满足其内在性质,即矩阵的秩为 2 2 2。通常的做法是,对 8 8 8点法求解得基本矩阵 F \boldsymbol{F} F进行 SVD \text{SVD} SVD分解,将奇异值矩阵 Σ = diag ( σ 1 , σ 2 , σ 3 ) \boldsymbol{\Sigma}=\text{diag}(\sigma_1,\sigma_2,\sigma_3) Σ=diag(σ1,σ2,σ3)中最小的奇异值强制置为 0 0 0,再重新修正后基本矩阵 F 2 \boldsymbol{F}_2 F2。用这种方式求得的矩阵 F 2 \boldsymbol{F}_2 F2,在约束条件 det ( F 2 ) = 0 \text{det}(\boldsymbol{F}_2)=0 det(F2)=0下,与矩阵 F \boldsymbol{F} F Frobenius \text{Frobenius} Frobenius范数距离 ∣ ∣ F 2 − F ∣ ∣ F ||\boldsymbol{F}_2-\boldsymbol{F}||_F ∣∣F2FF最小。

低秩逼近定理:设 A \boldsymbol{A} A是秩为 r r r m × n m\times n m×n阶实矩阵,它的奇异值分解是 A = U Σ V T \boldsymbol{A}=\boldsymbol{U}\boldsymbol{\Sigma}\boldsymbol{V}^T A=UΣVT,并且定义 A k = U k Σ k V k T \boldsymbol{A}_k=\boldsymbol{U}_k\boldsymbol{\Sigma}_k\boldsymbol{V}_k^T Ak=UkΣkVkT A \boldsymbol{A} A的低秩近似,其中 k ≤ r k\le r kr Σ k = diag ( σ 1 , . . . , σ k ) \boldsymbol{\Sigma}_k=\text{diag}(\sigma_1,...,\sigma_k) Σk=diag(σ1,...,σk)是由前 k k k个最大的奇异值构成的对角矩阵, U k \boldsymbol{U}_k Uk V k \boldsymbol{V}_k Vk分别由 U \boldsymbol{U} U V \boldsymbol{V} V的前 k k k行构成 ,则对于任意酉不变范数 ‖ ⋅ ‖ U ‖\cdot‖_U U,有:
KaTeX parse error: Undefined control sequence: \sideset at position 19: …oldsymbol{A}_k=\̲s̲i̲d̲e̲s̲e̲t̲{}{}{\arg\min}_…

1.4 使用像素点坐标归一化优化基本矩阵求解

一般来说,像素坐标系原点位于图像左上角,此时匹配点坐标均为非负数。在这种情况下,系数矩阵 A \boldsymbol{A} A的元素数量级差异较大,最终会导致求解出的 F \boldsymbol{F} F矩阵不稳定。相关的证明可以参考文献 《 《 I n In In D e f e n s e Defense Defense o f of of t h e the the E i g h t − P o i n t Eight-Point EightPoint A l g o r i t h m Algorithm Algorithm 》 》 Hartley \text{Hartley} Hartley提出了一种改进的 8 8 8点法来解决上述问题,即在求解基础矩阵之前,对匹配点坐标进行归一化处理,这样可以减少噪声干扰,提高算法精度。其具体归一化步骤如下:

  1. 分别对两幅图像上的像素点集进行平移,使其质心位于坐标原点。
  2. 分别对两幅图像上的像素点集进行各向同性缩放,使其与坐标原点的平均距离为 2 \sqrt{2} 2

使用符号 T \boldsymbol{T} T表示归一化矩阵,其具有如下形式:
T = S [ 1 0 − u ‾ 0 1 − v ‾ 0 0 1 S ] \boldsymbol{T}=S\begin{bmatrix}1&0&-\overline{u}\\0&1&-\overline{v}\\0&0&\frac{1}{S}\end{bmatrix} T=S 100010uvS1
其中, u ‾ \overline{u} u v ‾ \overline{v} v表示像素点两个分量的平均值, S S S表示各向同性缩放系数:
u ‾ = 1 N ∑ i = 1 N u i ,   v ‾ = 1 N ∑ i = 1 N v i S = 2 N ∑ i = 1 N ( ( u i − u ‾ ) 2 + ( u i − v ‾ ) 2 ) \overline{u}=\frac{1}{N}\sum_{i=1}^{N}u_i,\ \overline{v}=\frac{1}{N}\sum_{i=1}^{N}v_i\\ S=\frac{\sqrt{2N}}{\sqrt{\sum_{i=1}^N\left((u_i-\overline{u})^2+(u_i-\overline{v})^2\right)}} u=N1i=1Nui, v=N1i=1NviS=i=1N((uiu)2+(uiv)2) 2N
归一化后,求解的基础矩阵记为 F ^ \hat{\boldsymbol{F}} F^,对其使用去归一化操作可以得到原始基础矩阵 F \boldsymbol{F} F
F = T T F ^ T \boldsymbol{F}=\boldsymbol{T}^T\hat{\boldsymbol{F}}\boldsymbol{T} F=TTF^T

1.5 使用RANSAC算法优化基本矩阵求解

我们在获取到多个对匹配点后,可以选取相应数量的匹配点求解基本矩阵。但是我们无法保证所有的匹配点都是匹配正确的,因此需要采取一定的方法对匹配点进行提纯。

RANSAC \text{RANSAC} RANSAC(随机采样一致性算法)算法是一种可以进行匹配点提纯的迭代算法。该算法假设我们所获取到的数据集由内点(尽管可能会受到噪声的影响,但其分布可以使用模型参数来解释)以及外点(也称为异常值,其分布无法使用模型参数来解释)组成。这些异常值可能是由于噪声的极值,错误的测量等因素而产生的。 RANSAC \text{RANSAC} RANSAC是通过反复随机采样数据集的方式来估计模型参数,直到估计出内点满足一定数目的模型参数。其具体的实现步骤可以分为以下几步:

  1. 从原始数据集中随机采样一个子数据集,该数据集中的数据被认为是假设内点
  2. 使用假设内点估计模型参数。
  3. 使用模型参数测试所有数据,根据某些模型相关的损失函数来对这些数据进行分类,与模型拟合良好的数据被归类到共识集
  4. 比较当前模型和之前最优模型共识集数据数目,记录数目更大的模型。
  5. 重复 1 − 4 1-4 14步,直至迭代结束或共识集数据数目满足一定阈值。
  6. 使用最优共识集的所有数据对模型进行重新估计已改进模型。

RANSAC \text{RANSAC} RANSAC算法的迭代次数 k k k可以是基于应用和数据集的特定要求来确定,并且可以基于实验来评估。我们假设 w w w是原始数据集内点概率,那么单次估计中所采样的 n n n个假设内点都为内点的概率为 w n w^n wn。我们需要保证在 k k k次迭代中至少有一次采样所有的假设内点都是真实内点的概率为 p p p,那么 k k k需要满足:
1 − p = ( 1 − w n ) k ⇒ k = log ⁡ ( 1 − p ) log ⁡ ( 1 − w n ) 1-p=(1-w^n)^k\Rightarrow k=\frac{\log(1-p)}{\log(1-w^n)} 1p=(1wn)kk=log(1wn)log(1p)
在上述推导中 n n n以及 p p p都是预设值,而 w w w无法预先确定,我们可以在迭代的过程中使用共识集数据数目/原始集数据数目来近似。

对于使用多个对匹配点进行基本矩阵的求解的算法,我们可以采用 RANSAC \text{RANSAC} RANSAC思路进行匹配点提纯优化。其中,关于内点的判断,我们使用像素点到对极线的距离作为损失函数,误差小于某个预设阈值 τ \tau τ的匹配点才会被判断为内点:
max ⁡ ( p 2 T F p 1 ( F p 1 ) x 2 + ( F p 1 ) y 2 , p 2 T F p 1 ( p 2 T F ) x 2 + ( p 2 T F ) y 2 ) < τ 2 \max\left(\frac{\boldsymbol{p}_2^T\boldsymbol{F}\boldsymbol{p}_1}{(\boldsymbol{F}\boldsymbol{p}_1)_x^2+(\boldsymbol{F}\boldsymbol{p}_1)_y^2},\frac{\boldsymbol{p}_2^T\boldsymbol{F}\boldsymbol{p}_1}{(\boldsymbol{p}_2^T\boldsymbol{F})_x^2+(\boldsymbol{p}_2^T\boldsymbol{F})_y^2}\right)<\tau^2 max((Fp1)x2+(Fp1)y2p2TFp1,(p2TF)x2+(p2TF)y2p2TFp1)<τ2

1.6 使用LMedS算法优化基本矩阵求解

除了 RANSAC \text{RANSAC} RANSAC算法外, LMedS \text{LMedS} LMedS(最小中值算法)算法也可以用于匹配点的提纯。 LMedS \text{LMedS} LMedS算法的最优模型标准不再是内点数目最多,而是所有数据平方误差中值最小。其具体的实现步骤可以分为以下几步:

  1. 从原始数据集中随机采样一个子数据集。
  2. 使用子数据集估计模型参数。
  3. 使用模型参数测试所有数据,根据某些模型相关的损失函数来对这些数据计算平方误差值,并求出平方误差中值
  4. 比较当前模型和之前最优模型平方误差中值,记录平方误差中值更小的模型。
  5. 重复 1 − 4 1-4 14步,直至迭代结束。
  6. 根据平方误差中值计算损失函数阈值 τ \tau τ,使用模型参数测试所有数据,将误差值小于阈值的数据归类为内点。如果内点数目不小于子集数目,则认为最优模型有效,否则认为最优模型无效。

关于第 6 6 6步,损失函数阈值计算公式如下:
τ = 1.4826 ( 1 + 5 n − p ) r \tau=1.4826\left(1+\frac{5}{n-p}\right)\sqrt{r} τ=1.4826(1+np5)r
其中 n n n表示原始集数据数目, p p p表示子集数据数目, r r r表示平方误差中值。该公式来自于文献 《 《 R o b u s t Robust Robust R e g r e s s i o n Regression Regression M e t h o d s Methods Methods f o r for for C o m p u t e r Computer Computer V i s i o n Vision Vision : : : A A A R e v i e w Review Review 》 》

2. 基础矩阵求解代码解析

OpenCV \text{OpenCV} OpenCV中,使用函数 cv::findFundamentalMat \text{cv::findFundamentalMat} cv::findFundamentalMat实现基础矩阵求解:

cv::Mat cv::findFundamentalMat(
    InputArray _points1,			// _points1:I1平面像素点
    InputArray _points2,			// _points2:I2平面像素点
    int method,						// method:求解方式
    								//	CV_FM_7POINT:7点法
    								//	CV_FM_8POINT:8点法
    								//	CV_FM_RANSAC:RANSAC法 + 7点法
    								// 	CV_FM_LMEDS:LMedS法 + 7点法
    double ransacReprojThreshold,	// ransacReprojThreshold:RANSAC法内点判断阈值
    double confidence,				// confidence:至少存在某次迭代过程中所有假设内点都是真实内点的概率,用于动态更新最大迭代次数
    int maxIters,					// maxIters:最大迭代次数
    OutputArray _mask				// _mask:内外点掩码
    								//	1:内点
    								//	0:外点
)
{
    CV_INSTRUMENT_REGION();

    Mat points1 = _points1.getMat(), points2 = _points2.getMat();
    Mat m1, m2, F;
    int npoints = -1;

    for( int i = 1; i <= 2; i++ )
    {
        Mat& p = i == 1 ? points1 : points2;
        Mat& m = i == 1 ? m1 : m2;
        npoints = p.checkVector(2, -1, false);
        if( npoints < 0 )
        {
            npoints = p.checkVector(3, -1, false);
            if( npoints < 0 )
                CV_Error(Error::StsBadArg, "The input arrays should be 2D or 3D point sets");
            if( npoints == 0 )
                return Mat();
            convertPointsFromHomogeneous(p, p);
            						// 将齐次坐标转换为非齐次坐标:(x, y, z) -> (x / z, y / z)
        }
        p.reshape(2, npoints).convertTo(m, CV_32F);
    }

    CV_Assert( m1.checkVector(2) == m2.checkVector(2) );

    if( npoints < 7 )
        return Mat();

    Ptr cb = makePtr();
    int result;

    if( npoints == 7 || method == FM_8POINT )
    {
        result = cb->runKernel(m1, m2, F);
        							// 使用 7 点或8 点法求解基本矩阵
        if( _mask.needed() )
        {
            _mask.create(npoints, 1, CV_8U, -1, true);
            Mat mask = _mask.getMat();
            CV_Assert( (mask.cols == 1 || mask.rows == 1) && (int)mask.total() == npoints );
            mask.setTo(Scalar::all(1));
        }
    }
    else
    {
        if( ransacReprojThreshold <= 0 )
            ransacReprojThreshold = 3;
        if( confidence < DBL_EPSILON || confidence > 1 - DBL_EPSILON )
            confidence = 0.99;

        if( (method & ~3) == FM_RANSAC && npoints >= 15 )
            result = createRANSACPointSetRegistrator(cb, 7, ransacReprojThreshold, confidence, maxIters)->run(m1, m2, F, _mask);
        							// 使用 RANSAC 法求解基本矩阵
        else
            result = createLMeDSPointSetRegistrator(cb, 7, confidence)->run(m1, m2, F, _mask);
        							// 使用 LMeDS 法求解基本矩阵
    }

    if( result <= 0 )
        return Mat();

    return F;
}

7 7 7点法相关代码如下:

static int run7Point( const Mat& _m1, const Mat& _m2, Mat& _fmatrix )
    								// _m1:I1平面像素点(输入)
    								// _m2:I2平面像素点(输入)
    								// _fmatrix:基础矩阵(输出)
{
    double a[7*9], w[7], u[9*9], v[9*9], c[4], r[3] = {0};
    double* f1, *f2;
    double t0, t1, t2;
    Mat A( 7, 9, CV_64F, a );
    Mat U( 7, 9, CV_64F, u );
    Mat Vt( 9, 9, CV_64F, v );
    Mat W( 7, 1, CV_64F, w );
    Mat coeffs( 1, 4, CV_64F, c );
    Mat roots( 1, 3, CV_64F, r );
    const Point2f* m1 = _m1.ptr();
    const Point2f* m2 = _m2.ptr();
    double* fmatrix = _fmatrix.ptr();
    int i, k, n;

    Point2d m1c(0, 0), m2c(0, 0);
    double t, scale1 = 0, scale2 = 0;
    const int count = 7;

    // compute centers and average distances for each of the two point sets
    for( i = 0; i < count; i++ )
    {
        m1c += Point2d(m1[i]); 		// 累加特征点集合 1 各点坐标,用于计算特征点集合 1 中心
        m2c += Point2d(m2[i]); 		// 累加特征点集合 2 各点坐标,用于计算特征点集合 2 中心
    }

    // calculate the normalizing transformations for each of the point sets:
    // after the transformation each set will have the mass center at the coordinate origin
    // and the average distance from the origin will be ~sqrt(2).
    t = 1./count;
    m1c *= t;
    m2c *= t;

    for( i = 0; i < count; i++ )
    {
        scale1 += norm(Point2d(m1[i].x - m1c.x, m1[i].y - m1c.y));
        							// 累加特征点集合 1 各点距中心距离,用于计算特征点集合 1 缩放系数
        scale2 += norm(Point2d(m2[i].x - m2c.x, m2[i].y - m2c.y));
        							// 累加特征点集合 2 各点距中心距离,用于计算特征点集合 2 缩放系数
    }

    scale1 *= t;
    scale2 *= t;

    if( scale1 < FLT_EPSILON || scale2 < FLT_EPSILON )
        return 0;

    scale1 = std::sqrt(2.)/scale1;
    scale2 = std::sqrt(2.)/scale2;

    // form a linear system: i-th row of A(=a) represents
    // the equation: (m2[i], 1)'*F*(m1[i], 1) = 0
    for( i = 0; i < 7; i++ )
    {
        /* 对特征点集合各点进行平移缩放 */
        double x0 = (m1[i].x - m1c.x)*scale1;
        double y0 = (m1[i].y - m1c.y)*scale1;
        double x1 = (m2[i].x - m2c.x)*scale2;
        double y1 = (m2[i].y - m2c.y)*scale2;

        /* 构建 A 矩阵 */
        a[i*9+0] = x1*x0;
        a[i*9+1] = x1*y0;
        a[i*9+2] = x1;
        a[i*9+3] = y1*x0;
        a[i*9+4] = y1*y0;
        a[i*9+5] = y1;
        a[i*9+6] = x0;
        a[i*9+7] = y0;
        a[i*9+8] = 1;
    }

    // A*(f11 f12 ... f33)' = 0 is singular (7 equations for 9 variables), so
    // the solution is linear subspace of dimensionality 2.
    // => use the last two singular vectors as a basis of the space
    // (according to SVD properties)
    SVDecomp( A, W, U, Vt, SVD::MODIFY_A + SVD::FULL_UV );
    								// 对矩阵 A 进行 SVD 分解
    f1 = v + 7*9;					// 取第 8 个特征向量作为 A * (f11 f12 ... f33)' = 0 的第 1 个特解(矩阵 A 秩为 7,第 8 个特征值为 0)
    f2 = v + 8*9;					// 取第 9 个特征向量作为 A * (f11 f12 ... f33)' = 0 的第 2 个特解,与第 1 个特解正交(矩阵 A 秩为 7,第 9 个特征值为 0)

    // f1, f2 is a basis => lambda*f1 + mu*f2 is an arbitrary fundamental matrix,
    // as it is determined up to a scale, normalize lambda & mu (lambda + mu = 1),
    // so f ~ lambda*f1 + (1 - lambda)*f2.
    // use the additional constraint det(f) = det(lambda*f1 + (1-lambda)*f2) to find lambda.
    // it will be a cubic equation.
    // find c - polynomial coefficients.
    /* 根据基础矩阵秩为 2 的特性:det(f) = det(lambda * f1 + (1 - lambda) * f2) = 0,建立关于 lambda 的 3 次方程并求解 */
    for( i = 0; i < 9; i++ )
        f1[i] -= f2[i];

    t0 = f2[4]*f2[8] - f2[5]*f2[7];
    t1 = f2[3]*f2[8] - f2[5]*f2[6];
    t2 = f2[3]*f2[7] - f2[4]*f2[6];

    c[3] = f2[0]*t0 - f2[1]*t1 + f2[2]*t2;

    c[2] = f1[0]*t0 - f1[1]*t1 + f1[2]*t2 -
    f1[3]*(f2[1]*f2[8] - f2[2]*f2[7]) +
    f1[4]*(f2[0]*f2[8] - f2[2]*f2[6]) -
    f1[5]*(f2[0]*f2[7] - f2[1]*f2[6]) +
    f1[6]*(f2[1]*f2[5] - f2[2]*f2[4]) -
    f1[7]*(f2[0]*f2[5] - f2[2]*f2[3]) +
    f1[8]*(f2[0]*f2[4] - f2[1]*f2[3]);

    t0 = f1[4]*f1[8] - f1[5]*f1[7];
    t1 = f1[3]*f1[8] - f1[5]*f1[6];
    t2 = f1[3]*f1[7] - f1[4]*f1[6];

    c[1] = f2[0]*t0 - f2[1]*t1 + f2[2]*t2 -
    f2[3]*(f1[1]*f1[8] - f1[2]*f1[7]) +
    f2[4]*(f1[0]*f1[8] - f1[2]*f1[6]) -
    f2[5]*(f1[0]*f1[7] - f1[1]*f1[6]) +
    f2[6]*(f1[1]*f1[5] - f1[2]*f1[4]) -
    f2[7]*(f1[0]*f1[5] - f1[2]*f1[3]) +
    f2[8]*(f1[0]*f1[4] - f1[1]*f1[3]);

    c[0] = f1[0]*t0 - f1[1]*t1 + f1[2]*t2;

    // solve the cubic equation; there can be 1 to 3 roots ...
    n = solveCubic( coeffs, roots );

    if( n < 1 || n > 3 )
        return n;

    // transformation matrices
    Matx33d T1( scale1, 0, -scale1*m1c.x, 0, scale1, -scale1*m1c.y, 0, 0, 1 );
    Matx33d T2( scale2, 0, -scale2*m2c.x, 0, scale2, -scale2*m2c.y, 0, 0, 1 );

    /* 根据求出的 n 个解,生成 n 个基础矩阵 */
    for( k = 0; k < n; k++, fmatrix += 9 )
    {
        // for each root form the fundamental matrix
        double lambda = r[k], mu = 1.;
        double s = f1[8]*r[k] + f2[8];

        // normalize each matrix, so that F(3,3) (~fmatrix[8]) == 1
        if( fabs(s) > DBL_EPSILON )
        {
            mu = 1./s;
            lambda *= mu;
            fmatrix[8] = 1.;
        }
        else
            fmatrix[8] = 0.;

        for( i = 0; i < 8; i++ )
            fmatrix[i] = f1[i]*lambda + f2[i]*mu;
        							// F = lambda * F1 + (1-lambda) * F2

        // de-normalize
        Mat F(3, 3, CV_64F, fmatrix);
        F = T2.t() * F * T1;		// 去归一化

        // make F(3,3) = 1
        if(fabs(F.at(8)) > FLT_EPSILON )
            F *= 1. / F.at(8);
        							// 通过缩放操作将基础矩阵 F(3, 3) 元素归一化到 1
    }

    return n;
}

8 8 8点法相关代码如下:

static int run8Point( const Mat& _m1, const Mat& _m2, Mat& _fmatrix )
    								// _m1:I1平面像素点(输入)
    								// _m2:I2平面像素点(输入)
    								// _fmatrix:基础矩阵(输出)
{
    Point2d m1c(0,0), m2c(0,0);
    double t, scale1 = 0, scale2 = 0;

    const Point2f* m1 = _m1.ptr();
    const Point2f* m2 = _m2.ptr();
    CV_Assert( (_m1.cols == 1 || _m1.rows == 1) && _m1.size() == _m2.size());
    int i, count = _m1.checkVector(2);

    // compute centers and average distances for each of the two point sets
    for( i = 0; i < count; i++ )
    {
        m1c += Point2d(m1[i]);		// 累加特征点集合 1 各点坐标,用于计算特征点集合 1 中心
        m2c += Point2d(m2[i]);		// 累加特征点集合 2 各点坐标,用于计算特征点集合 2 中心
    }

    // calculate the normalizing transformations for each of the point sets:
    // after the transformation each set will have the mass center at the coordinate origin
    // and the average distance from the origin will be ~sqrt(2).
    t = 1./count;
    m1c *= t;
    m2c *= t;

    for( i = 0; i < count; i++ )
    {
        scale1 += norm(Point2d(m1[i].x - m1c.x, m1[i].y - m1c.y));
        							// 累加特征点集合 1 各点距中心距离,用于计算特征点集合 1 缩放系数
        scale2 += norm(Point2d(m2[i].x - m2c.x, m2[i].y - m2c.y));
        							// 累加特征点集合 2 各点距中心距离,用于计算特征点集合 2 缩放系数
    }

    scale1 *= t;
    scale2 *= t;

    if( scale1 < FLT_EPSILON || scale2 < FLT_EPSILON )
        return 0;

    scale1 = std::sqrt(2.)/scale1;
    scale2 = std::sqrt(2.)/scale2;

    Matx A;

    // form a linear system Ax=0: for each selected pair of points m1 & m2,
    // the row of A(=a) represents the coefficients of equation: (m2, 1)'*F*(m1, 1) = 0
    // to save computation time, we compute (At*A) instead of A and then solve (At*A)x=0.
    for( i = 0; i < count; i++ )
    {
        /* 对特征点集合各点进行平移缩放 */
        double x1 = (m1[i].x - m1c.x)*scale1;
        double y1 = (m1[i].y - m1c.y)*scale1;
        double x2 = (m2[i].x - m2c.x)*scale2;
        double y2 = (m2[i].y - m2c.y)*scale2;
        /* 构建 At * A 矩阵 */
        Vec r( x2*x1, x2*y1, x2, y2*x1, y2*y1, y2, x1, y1, 1 );
        A += r*r.t();
    }

    Vec W;
    Matx V;

    eigen(A, W, V);					// 对 At * A 矩阵求解特征根以及特征值

    for( i = 0; i < 9; i++ )
    {
        if( fabs(W[i]) < DBL_EPSILON )
            break;
    }

    if( i < 8 )						// At * A 的秩不小 8
        return 0;

    Matx33d F0( V.val + 9*8 ); 		// take the last column of v as a solution of Af = 0
    								// At * A 的秩最大为 8,因此可以取其第 9 个特征向量作为 (At * A)x = 0 的特解

    // make F0 singular (of rank 2) by decomposing it with SVD,
    // zeroing the last diagonal element of W and then composing the matrices back.

    Vec3d w;
    Matx33d U;
    Matx33d Vt;

    SVD::compute( F0, w, U, Vt); 	// 对基础矩阵 F0 进行 SVD 分解
    w[2] = 0.; 						// 根据基础矩阵秩为 2 的特性,将第 3 个特征值强制置为零

    F0 = U*Matx33d::diag(w)*Vt;		// 重新调整基础矩阵 F0

    // apply the transformation that is inverse
    // to what we used to normalize the point coordinates
    Matx33d T1( scale1, 0, -scale1*m1c.x, 0, scale1, -scale1*m1c.y, 0, 0, 1 );
    Matx33d T2( scale2, 0, -scale2*m2c.x, 0, scale2, -scale2*m2c.y, 0, 0, 1 );

    F0 = T2.t()*F0*T1;				// 去归一化

    // make F(3,3) = 1
    if( fabs(F0(2,2)) > FLT_EPSILON )
        F0 *= 1./F0(2,2);			// 通过缩放操作将基础矩阵 F(3, 3) 元素归一化到 1

    Mat(F0).copyTo(_fmatrix);

    return 1;
}

RANSAC \text{RANSAC} RANSAC相关代码如下:

bool RANSACPointSetRegistrator::run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const CV_OVERRIDE
{
    bool result = false;
    Mat m1 = _m1.getMat(), m2 = _m2.getMat();
    Mat err, mask, model, bestModel, ms1, ms2;

    int iter, niters = MAX(maxIters, 1);
    int d1 = m1.channels() > 1 ? m1.channels() : m1.cols;
    int d2 = m2.channels() > 1 ? m2.channels() : m2.cols;
    int count = m1.checkVector(d1), count2 = m2.checkVector(d2), maxGoodCount = 0;

    RNG rng((uint64)-1);

    CV_Assert( cb );
    CV_Assert( confidence > 0 && confidence < 1 );

    CV_Assert( count >= 0 && count2 == count );
    /* 如果匹配点数小于模型拟合所需的最小匹配点数,则返回失败 */
    if( count < modelPoints )
        return false;

    Mat bestMask0, bestMask;

    if( _mask.needed() )
    {
        _mask.create(count, 1, CV_8U, -1, true);
        bestMask0 = bestMask = _mask.getMat();
        CV_Assert( (bestMask.cols == 1 || bestMask.rows == 1) && (int)bestMask.total() == count );
    }
    else
    {
        bestMask.create(count, 1, CV_8U);
        bestMask0 = bestMask;
    }

    /* 如果匹配点数等于模型拟合所需的最小匹配点数,则直接拟合模型并返回成功 */
    if( count == modelPoints )
    {
        if( cb->runKernel(m1, m2, bestModel) <= 0 )
            return false;
        bestModel.copyTo(_model);
        bestMask.setTo(Scalar::all(1));
        return true;
    }

    /* 迭代拟合模型 */
    for( iter = 0; iter < niters; iter++ )
    {
        int i, nmodels;
        if( count > modelPoints )
        {
            bool found = getSubset( m1, m2, ms1, ms2, rng, 10000 );
            						// 随机采样子数据集
            if( !found )
            {
                if( iter == 0 )
                    return false;
                break;
            }
        }

        nmodels = cb->runKernel( ms1, ms2, model );
        							// 使用子数据集求解基础矩阵
        if( nmodels <= 0 )
            continue;
        CV_Assert( model.rows % nmodels == 0 );
        Size modelSize(model.cols, model.rows/nmodels);

        for( i = 0; i < nmodels; i++ )
        {
            Mat model_i = model.rowRange( i*modelSize.height, (i+1)*modelSize.height );
            int goodCount = findInliers( m1, m2, model_i, err, mask, threshold );
            						// 使用求解的基础矩阵对原始数据集进行内外点划分

            if( goodCount > MAX(maxGoodCount, modelPoints-1) )
                					// 如果内点数大于最优矩阵内点数,则记录该矩阵
            {
                std::swap(mask, bestMask);
                model_i.copyTo(bestModel);
                maxGoodCount = goodCount;
                niters = RANSACUpdateNumIters( confidence, (double)(count - goodCount)/count, modelPoints, niters );
                					// 根据当前矩阵估计的外点率更新最大迭代次数
            }
        }
    }

    if( maxGoodCount > 0 )
    {
        if( bestMask.data != bestMask0.data )
        {
            if( bestMask.size() == bestMask0.size() )
                bestMask.copyTo(bestMask0);
            else
                transpose(bestMask, bestMask0);
        }
        bestModel.copyTo(_model);
        result = true;
    }
    else
        _model.release();

    return result;
}

LMedS \text{LMedS} LMedS相关代码如下:

bool LMeDSPointSetRegistrator::run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const CV_OVERRIDE
{
    const double outlierRatio = 0.45;
    								// 假设数据集外点率为 0.45
    bool result = false;
    Mat m1 = _m1.getMat(), m2 = _m2.getMat();
    Mat ms1, ms2, err, errf, model, bestModel, mask, mask0;

    int d1 = m1.channels() > 1 ? m1.channels() : m1.cols;
    int d2 = m2.channels() > 1 ? m2.channels() : m2.cols;
    int count = m1.checkVector(d1), count2 = m2.checkVector(d2);
    double minMedian = DBL_MAX;

    RNG rng((uint64)-1);

    CV_Assert( cb );
    CV_Assert( confidence > 0 && confidence < 1 );

    CV_Assert( count >= 0 && count2 == count );
    if( count < modelPoints )
        return false;

    if( _mask.needed() )
    {
        _mask.create(count, 1, CV_8U, -1, true);
        mask0 = mask = _mask.getMat();
        CV_Assert( (mask.cols == 1 || mask.rows == 1) && (int)mask.total() == count );
    }

    if( count == modelPoints )
    {
        if( cb->runKernel(m1, m2, bestModel) <= 0 )
            return false;
        bestModel.copyTo(_model);
        mask.setTo(Scalar::all(1));
        return true;
    }

    int iter, niters = RANSACUpdateNumIters(confidence, outlierRatio, modelPoints, maxIters);
    								// 根据假设外点率计算最大迭代次数
    niters = MAX(niters, 3);

    for( iter = 0; iter < niters; iter++ )
    {
        int i, nmodels;
        if( count > modelPoints )
        {
            bool found = getSubset( m1, m2, ms1, ms2, rng );
            						// 随机采样子数据集
            if( !found )
            {
                if( iter == 0 )
                    return false;
                break;
            }
        }

        nmodels = cb->runKernel( ms1, ms2, model );
        if( nmodels <= 0 )
            continue;

        CV_Assert( model.rows % nmodels == 0 );
        Size modelSize(model.cols, model.rows/nmodels);

        for( i = 0; i < nmodels; i++ )
        {
            Mat model_i = model.rowRange( i*modelSize.height, (i+1)*modelSize.height );
            cb->computeError( m1, m2, model_i, err );
            if( err.depth() != CV_32F )
                err.convertTo(errf, CV_32F);
            else
                errf = err;
            CV_Assert( errf.isContinuous() && errf.type() == CV_32F && (int)errf.total() == count );
            std::nth_element(errf.ptr(), errf.ptr() + count/2, errf.ptr() + count);
            double median = errf.at(count/2);
            						// 误差平方中值

            if( median < minMedian )
                					// 如果误差平方中值小于最优矩阵误差平方中值,则记录该矩阵
            {
                minMedian = median;
                model_i.copyTo(bestModel);
            }
        }
    }

    if( minMedian < DBL_MAX )
    {
        double sigma = 2.5*1.4826*(1 + 5./(count - modelPoints))*std::sqrt(minMedian);
        sigma = MAX( sigma, 0.001 );
        							// 估计模型内外点划分阈值

        count = findInliers( m1, m2, bestModel, err, mask, sigma );
        							// 使用阈值划分数据集内外点
        if( _mask.needed() && mask0.data != mask.data )
        {
            if( mask0.size() == mask.size() )
                mask.copyTo(mask0);
            else
                transpose(mask, mask0);
        }
        bestModel.copyTo(_model);
        result = count >= modelPoints;
        							// 如果内点数目不小于子集数目,则认为基础矩阵有效,反之则无效
    }
    else
        _model.release();

    return result;
}

你可能感兴趣的:(SLAM,opencv,矩阵,计算机视觉)