在计算机视觉中,基础矩阵 F \boldsymbol{F} F是一个 3 × 3 3\times3 3×3矩阵,用于表示立体像对的像点之间的对应关系。
立体象对:从两个不同位置对同一区域所拍摄的一对相片。
相机将三维世界的坐标(单位为米)映射到二维图像平面(单位为像素)的过程可以用一个几何模型进行描述。这种几何模型有很多种类,其中最简单的是针孔模型。它描述了光线通过针孔在成像平面投影的关系。
设 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=−X′X=−Y′Y
为了简化模型,我们把可以物理成像平面对称到相机前方,这样做可以把公式中的负号去掉,使式子更加简洁:
Z f = X X ′ = Y Y ′ \frac{Z}{f}=\frac{X}{X'}=\frac{Y}{Y'} fZ=X′X=Y′Y
整理上式可得:
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
对极几何描述的是两幅图像之间的内在投影关系,与外部场景无关,只依赖于相机内参和这两幅图像之间的的相对姿态。
假设 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 (y1−y2)x+(x2−x1)y+(x1y2−x2y1)=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)=(y1−y2,x2−x1,x1y2−x2y1)
叉乘结果的三个分量与直线方程的 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+=[K1−10T]
其中 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][K1−10T]p1)=(K2t)∧(K2RK1−1p1)=K2−Tt∧RK1−1p1
其中 ∧ ^\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∧= 0t3−t2−t30t1t2−t10
我们定义基础矩阵 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=K2−Tt∧RK1−1
于是极线 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
我们将上述关系式称为对极约束。
从基础矩阵 F \boldsymbol{F} F的构造方式上看,其有以下特性:
关于性质 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∧= 0t3−t2−t30t1t2−t10 → 0t2t3−t2t3−t30t1t3t2−t1t20 → 00−t2t3−t3t1t1t3t1t3t2t1−t1t20 → 00−t20t3t10−t20 由此可知 t ∧ \boldsymbol{t}^{\wedge} t∧的秩为 2 2 2。 F \boldsymbol{F} F的其他构造矩阵均为可逆矩阵,与其相乘不影响秩,所以基础矩阵 F \boldsymbol{F} F的秩也为 2 2 2。
考虑一对匹配点,它们的齐次像素点坐标分别为 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} u21u11u22u12⋮u27u17u21v11u22v12⋮u27v17u21u22⋮u27v21u11v22u12⋮v27u17v21v11v22v12⋮v27v17v21v22⋮v27u11u12⋮u17v11v12⋮v1711⋮1 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+(1−s)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+(1−s)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+(1−s)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的最后两列列向量即单位正交解。
现在我们考虑 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} u21u11u22u12⋮u28u18u21v11u22v12⋮u28v18u21u22⋮u28v21u11v22u12⋮v28u18v21v11v22v12⋮v28v18v21v22⋮v28u11u12⋮u18v11v12⋮v1811⋮1 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 ∣∣F2−F∣∣F最小。
低秩逼近定理:设 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 k≤r, Σ 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}_…
一般来说,像素坐标系原点位于图像左上角,此时匹配点坐标均为非负数。在这种情况下,系数矩阵 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 Eight−Point A l g o r i t h m Algorithm Algorithm 》 》 》。 Hartley \text{Hartley} Hartley提出了一种改进的 8 8 8点法来解决上述问题,即在求解基础矩阵之前,对匹配点坐标进行归一化处理,这样可以减少噪声干扰,提高算法精度。其具体归一化步骤如下:
使用符号 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 100010−u−vS1
其中, 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=1∑Nui, v=N1i=1∑NviS=∑i=1N((ui−u)2+(ui−v)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
我们在获取到多个对匹配点后,可以选取相应数量的匹配点求解基本矩阵。但是我们无法保证所有的匹配点都是匹配正确的,因此需要采取一定的方法对匹配点进行提纯。
RANSAC \text{RANSAC} RANSAC(随机采样一致性算法)算法是一种可以进行匹配点提纯的迭代算法。该算法假设我们所获取到的数据集由内点(尽管可能会受到噪声的影响,但其分布可以使用模型参数来解释)以及外点(也称为异常值,其分布无法使用模型参数来解释)组成。这些异常值可能是由于噪声的极值,错误的测量等因素而产生的。 RANSAC \text{RANSAC} RANSAC是通过反复随机采样数据集的方式来估计模型参数,直到估计出内点满足一定数目的模型参数。其具体的实现步骤可以分为以下几步:
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)} 1−p=(1−wn)k⇒k=log(1−wn)log(1−p)
在上述推导中 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
除了 RANSAC \text{RANSAC} RANSAC算法外, LMedS \text{LMedS} LMedS(最小中值算法)算法也可以用于匹配点的提纯。 LMedS \text{LMedS} LMedS算法的最优模型标准不再是内点数目最多,而是所有数据平方误差中值最小。其具体的实现步骤可以分为以下几步:
关于第 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+n−p5)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 》 》 》。
在 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;
}