前言:简单的三角化
理想情况下,两张图像中关联的同一个三维特征点 P \ P P 满足下述方程:
{
s
2
x
2
=
P
s
1
R
21
x
1
+
t
21
=
P
s
2
x
2
=
s
1
R
1
x
1
+
t
1
\left\{ \begin{aligned} s_2x_2 = P \\ s_1R_{21}x_1 + t_{21}=P\\ \end{aligned} \right.\\ s_2x_2 = s_1R_1x_1 + t_1\\
{s2x2=Ps1R21x1+t21=Ps2x2=s1R1x1+t1
其中
s
1
、
s
2
\ s_1、s_2
s1、s2 为深度信息,
x
1
、
x
2
\ x_1、x_2
x1、x2对应归一化坐标,同时
R
21
、
t
21
\ R_{21}、t_{21}
R21、t21 十分精准,可以通过等式两边同时叉乘
x
2
\ x_2
x2直接解出
s
1
、
s
2
\ s_1、s_2
s1、s2 :
s
2
x
2
×
x
2
=
0
=
s
1
x
2
×
R
21
x
1
+
x
2
×
t
21
s_2x_2\times x_2 = 0 = s_1x_2\times R_{21}x_1 + x_2\times t_{21}\\
s2x2×x2=0=s1x2×R21x1+x2×t21
《SLAM十四讲》(第二版)p177中已经讲得较为详细。
最小二乘法求解
推广形式
实际情况中, 上述公式中的
R
21
、
t
21
\ R_{21}、t_{21}
R21、t21 并不精确,且存在多帧图像“tracking”到同一个特征点,基于上述“存在误差、多组数据”可以很自然地想到最小二乘法。
首先将公式拓展成一般形式:
l
o
s
s
i
=
s
i
x
i
×
x
i
=
δ
i
=
s
1
x
i
×
R
i
1
x
1
+
x
i
×
t
i
1
loss_i = s_ix_i\times x_i = \delta_i = s_1x_i\times R_{i1}x_1 + x_i\times t_{i1}\\
lossi=sixi×xi=δi=s1xi×Ri1x1+xi×ti1
为方便表示,令
X
1
=
s
1
x
1
\ X_1=s_1x_1
X1=s1x1,
X
1
X_1
X1表示在图像1坐标系下
P
\ P
P点坐标:
l
o
s
s
i
=
x
i
×
R
i
1
X
1
+
x
2
×
t
i
1
=
δ
i
L
o
s
s
=
1
2
∑
i
=
2
n
l
o
s
s
i
2
loss_i = x_i\times R_{i1}X_1 + x_2\times t_{i1} = \delta_i\\ Loss = \frac{1}{2}\sum_{i=2}^{n}lossi^2
lossi=xi×Ri1X1+x2×ti1=δiLoss=21i=2∑nlossi2
因为子函数
l
o
s
s
i
\ loss_i
lossi 全部为线性,可以转换成求
A
X
1
=
0
\ AX_1 = 0
AX1=0 最小二乘问题,采用SVD求解
A
\ A
A 的零空间向量(近似的零空间向量,认为特征值最小的特征向量近似于零空间向量),求出的零空间向量则为
X
1
\ X_1
X1的具体数值。
进一步简化
X
1
\ X_1
X1 扩展成齐次坐标后,(注意之后
X
1
\ X_1
X1 可能为齐次坐标也可能非齐次)公式如下:
l
o
s
s
i
=
x
i
×
T
i
1
X
1
loss_i = x_i\times T_{i1}X_1 \\
lossi=xi×Ti1X1
l
o
s
s
i
=
[
0
−
1
v
i
1
0
−
u
i
−
v
i
u
i
0
]
T
i
1
X
1
loss_i = \begin{bmatrix} 0 & -1 & v_i\\ 1 & 0 & -u_i\\-v_i & u_i &0 \end{bmatrix} T_{i1}X_1 \\
lossi=⎣⎡01−vi−10uivi−ui0⎦⎤Ti1X1
l
o
s
s
i
=
[
−
T
i
1
r
o
w
2
+
v
i
T
i
1
r
o
w
3
T
i
1
r
o
w
1
−
u
i
T
i
1
r
o
w
3
−
v
i
T
i
1
r
o
w
1
+
u
i
T
i
1
r
o
w
2
]
X
1
loss_i =\begin{bmatrix} -T_{i1row2} +v_iT_{i1row3} \\ T_{i1row1} -u_iT_{i1row3}\\-v_i T_{i1row1}+u_iT_{i1row2}\end{bmatrix}X_1
lossi=⎣⎡−Ti1row2+viTi1row3Ti1row1−uiTi1row3−viTi1row1+uiTi1row2⎦⎤X1
第三行可以由上两行线性变换所得,可以简化成:
l
o
s
s
i
=
[
−
T
i
1
r
o
w
2
+
v
i
T
i
1
r
o
w
3
T
i
1
r
o
w
1
−
u
i
T
i
1
r
o
w
3
]
X
1
loss_i =\begin{bmatrix} -T_{i1row2} +v_iT_{i1row3} \\ T_{i1row1} -u_iT_{i1row3}\end{bmatrix}X_1
lossi=[−Ti1row2+viTi1row3Ti1row1−uiTi1row3]X1
整个
L
o
s
s
Loss
Loss为:
L
o
s
s
i
=
[
−
T
21
r
o
w
2
+
v
2
T
21
r
o
w
3
T
21
r
o
w
1
−
u
2
T
21
r
o
w
3
.
.
.
.
−
T
n
1
r
o
w
2
+
v
2
T
n
1
r
o
w
3
T
n
1
r
o
w
1
−
u
2
T
n
2
r
o
w
3
]
X
1
Loss_i =\begin{bmatrix} -T_{21row2} +v_2T_{21row3} \\ T_{21row1} -u_2T_{21row3}\\ .... \\ -T_{n1row2} +v_2T_{n1row3} \\ T_{n1row1} -u_2T_{n2row3}\end{bmatrix}X_1
Lossi=⎣⎢⎢⎢⎢⎡−T21row2+v2T21row3T21row1−u2T21row3....−Tn1row2+v2Tn1row3Tn1row1−u2Tn2row3⎦⎥⎥⎥⎥⎤X1
利用线性代数性质,整齐一点的写法:(主要是针对写代码时候看得舒服)
L
o
s
s
i
=
[
u
2
T
21
r
o
w
3
−
T
21
r
o
w
1
v
2
T
21
r
o
w
3
−
T
21
r
o
w
2
.
.
.
.
u
n
T
n
1
r
o
w
3
−
T
n
1
r
o
w
1
v
n
T
n
1
r
o
w
3
−
T
n
1
r
o
w
2
]
X
1
Loss_i =\begin{bmatrix} u_2T_{21row3} -T_{21row1} \\ v_2T_{21row3}-T_{21row2}\\ .... \\ u_nT_{n1row3} -T_{n1row1} \\ v_nT_{n1row3}-T_{n1row2} \end{bmatrix}X_1
Lossi=⎣⎢⎢⎢⎢⎡u2T21row3−T21row1v2T21row3−T21row2....unTn1row3−Tn1row1vnTn1row3−Tn1row2⎦⎥⎥⎥⎥⎤X1
滑动窗口中的三角化——VINS为例
VINS中滑动窗口优化的逆深度为当前滑动窗口中第一次看到该特征点的图像下的逆深度。同时又加入相机外参,显得稍微复杂些。
{
T
w
b
0
T
b
c
X
1
=
P
T
w
b
i
T
b
c
X
i
=
P
\left\{ \begin{aligned} T_{wb_0}T_{bc}X_1 = P \\ T_{wb_i}T_{bc}X_i = P\\ \end{aligned} \right.\\
{Twb0TbcX1=PTwbiTbcXi=P
公式展开,并进行简单的等式变换后也可得如下的形式:
s
i
x
i
=
s
1
R
i
′
x
1
+
t
i
′
s_ix_i = s_1R_i^{'}x_1 + t_i^{'}
sixi=s1Ri′x1+ti′
只不过
R
i
′
、
t
i
′
R_i^{'}、t_i^{'}
Ri′、ti′是通过数学变换,努力向之前已有的公式上“蹭”。
Latex敲得头大,直接手写。
对应VINS中的代码:
void FeatureManager::triangulate(Vector3d Ps[], Vector3d tic[], Matrix3d ric[])
{
for (auto &it_per_id : feature)
{
it_per_id.used_num = it_per_id.feature_per_frame.size();
if (!(it_per_id.used_num >= 2 && it_per_id.start_frame < WINDOW_SIZE - 2))
continue;
if (it_per_id.estimated_depth > 0)
continue;
int imu_i = it_per_id.start_frame, imu_j = imu_i - 1;
ROS_ASSERT(NUM_OF_CAM == 1);
Eigen::MatrixXd svd_A(2 * it_per_id.feature_per_frame.size(), 4);
int svd_idx = 0;
Eigen::Matrix<double, 3, 4> P0;
Eigen::Vector3d t0 = Ps[imu_i] + Rs[imu_i] * tic[0];
Eigen::Matrix3d R0 = Rs[imu_i] * ric[0];
P0.leftCols<3>() = Eigen::Matrix3d::Identity();
P0.rightCols<1>() = Eigen::Vector3d::Zero();
for (auto &it_per_frame : it_per_id.feature_per_frame)
{
imu_j++;
Eigen::Vector3d t1 = Ps[imu_j] + Rs[imu_j] * tic[0];
Eigen::Matrix3d R1 = Rs[imu_j] * ric[0];
Eigen::Vector3d t = R0.transpose() * (t1 - t0);
Eigen::Matrix3d R = R0.transpose() * R1;
Eigen::Matrix<double, 3, 4> P;
//对应公式中最后要求的R' t'
P.leftCols<3>() = R.transpose();
P.rightCols<1>() = -R.transpose() * t;
Eigen::Vector3d f = it_per_frame.point.normalized();
svd_A.row(svd_idx++) = f[0] * P.row(2) - f[2] * P.row(0);
svd_A.row(svd_idx++) = f[1] * P.row(2) - f[2] * P.row(1);
if (imu_i == imu_j)
continue;
}
ROS_ASSERT(svd_idx == svd_A.rows());
Eigen::Vector4d svd_V = Eigen::JacobiSVD<Eigen::MatrixXd>(svd_A, Eigen::ComputeThinV).matrixV().rightCols<1>();
double svd_method = svd_V[2] / svd_V[3];
//it_per_id->estimated_depth = -b / A;
//it_per_id->estimated_depth = svd_V[2] / svd_V[3];
it_per_id.estimated_depth = svd_method;
//it_per_id->estimated_depth = INIT_DEPTH;
if (it_per_id.estimated_depth < 0.1)
{
it_per_id.estimated_depth = INIT_DEPTH;
}
}
}