注
vins-mono是使用 机器人手眼标定的方法,计算出外参中的旋转 R c i R^i_{c} Rci ,也可以是 R c b R^b_{c} Rcb (把imu坐标系当作body坐标系)
本文将详细进行推导,以及与代码对应;
1. 理论
1.1 前言
对于 k k k与 k + 1 k+1 k+1帧相机之间的位姿 R c k + 1 c k R^{c_k}_{c_{k+1}} Rck+1ck,可以直接通过两帧之间的视觉测量(对极约束,分解本质矩阵E计算得到),也可以先通过IMU积分获得 R i k + 1 i k R^{i_k}_{i_{k+1}} Rik+1ik ,然后根据 R c i R^i_{c} Rci,经过下面的变换
R
c
k
+
1
c
k
=
R
i
c
R
i
k
+
1
i
k
R
c
i
(
1
)
R^{c_k}_{c_{k+1}} =R^c_{i} R^{i_k}_{i_{k+1}} R^i_{c} (1)
Rck+1ck=RicRik+1ikRci(1)
也可以得到
R
c
k
+
1
c
k
R^{c_k}_{c_{k+1}}
Rck+1ck
由相机和imu分别计算出来的 R c k + 1 c k R^{c_k}_{c_{k+1}} Rck+1ck 存在差异,构建最小二乘问题, A x = 0 Ax=0 Ax=0,SVD分解A矩阵,解出非零解,即最佳的 R c i R^i_{c} Rci
1.2 利用旋转约束估计外参数
相邻两个时刻 k, k+1 之间 R i k + 1 c k R^{c_k}_{i_{k+1}} Rik+1ck 存在 :
R i c R i k + 1 i k = R c k + 1 c k R i c R^c_{i} R^{i_k}_{i_{k+1}} = R^{c_k}_{c_{k+1}}R^c_{i} RicRik+1ik=Rck+1ckRic
q i c ⊗ q i k + 1 i k = q c k + 1 c k ⊗ q i c q^c_{i} \otimes q^{i_k}_{i_{k+1}} = q^{c_k}_{c_{k+1}}\otimes q^c_{i} qic⊗qik+1ik=qck+1ck⊗qic
四元数
q
=
[
x
y
z
w
]
T
=
[
q
v
q
w
]
q=[x y z w]^{T}=\left[\begin{array}{c} q_{v} \\ q_{w} \end{array}\right]
q=[xyzw]T=[qvqw] 的左乘矩阵与右乘矩阵:
[
q
]
L
=
q
w
I
+
[
[
q
v
]
×
q
v
−
q
v
T
0
]
,
[
q
]
R
=
q
w
I
+
[
−
[
q
v
]
×
q
v
−
q
v
T
0
]
\quad[q]_{L}=q_{w} I+\left[\begin{array}{cc} {\left[q_{v}\right]_{\times}} & q_{v} \\ -q_{v}^{T} & 0 \end{array}\right], \quad[q]_{R}=q_{w} I+\left[\begin{array}{cc} -\left[q_{v}\right]_{\times} & q_{v} \\ -q_{v}^{T} & 0 \end{array}\right]
[q]L=qwI+[[qv]×−qvTqv0],[q]R=qwI+[−[qv]×−qvTqv0]
得:
[
q
i
k
+
1
i
k
]
R
q
i
c
=
[
q
c
k
+
1
c
k
]
L
q
i
c
[q^{i_k}_{i_{k+1}} ]_{R} q^c_{i} =[q^{c_k}_{c_{k+1}}]_{L} q^c_{i}
[qik+1ik]Rqic=[qck+1ck]Lqic
{ [ q c k + 1 c k ] L − [ q i k + 1 i k ] R } q i c = 0 \{[q^{c_k}_{c_{k+1}}]_{L} - [q^{i_k}_{i_{k+1}} ]_{R} \} q^c_{i} = 0 {[qck+1ck]L−[qik+1ik]R}qic=0
通过多对
q
c
k
+
1
c
k
,
q
i
k
+
1
i
k
q^{c_k}_{c_{k+1}},q^{i_k}_{i_{k+1}}
qck+1ck,qik+1ik 可以构建超定方程
A
=
{
[
q
i
k
i
k
+
1
1
]
L
−
[
q
c
k
c
k
+
1
1
]
R
[
q
i
k
i
k
+
1
2
]
L
−
[
q
c
k
c
k
+
1
2
]
R
⋯
[
q
i
k
i
k
+
1
n
]
L
−
[
q
c
k
c
k
+
1
n
]
R
}
,
A
⋅
q
i
c
=
0
A=\left\{\begin{array}{l}{\left[q_{i_ki_{k+1}}^1\right]}_L-{\left[q_{c_kc_{k+1}}^1\right]}_R\\{\left[q_{i_ki_{k+1}}^2\right]}_L-{\left[q_{c_kc_{k+1}}^2\right]}_R\\\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\cdots\\{\left[q_{i_ki_{k+1}}^n\right]}_L-{\left[q_{c_kc_{k+1}}^n\right]}_R\end{array}\right\},\quad A\cdot q_i^c=0
A=⎩⎪⎪⎨⎪⎪⎧[qikik+11]L−[qckck+11]R[qikik+12]L−[qckck+12]R⋯[qikik+1n]L−[qckck+1n]R⎭⎪⎪⎬⎪⎪⎫,A⋅qic=0
通过SVD分解 解出
q
i
c
q_i^c
qic ,转为旋转矩阵
R
i
c
R_i^c
Ric,取逆得
R
c
i
R_c^i
Rci
2. 代码实践
bool InitialEXRotation::CalibrationExRotation(vector<pair<Vector3d, Vector3d>> corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result)
- corres 两帧之间匹配的特征点 归一化坐标,通过对极约束,构建本质矩阵E,分解得到两帧之间的旋转矩阵, solveRelativeR(corres) 函数实现
- delta_q_imu 为相邻两时刻 IMU预计分旋转量,四元数表示
- 传入进来的 匹配的归一化坐标,IMU预计分旋转量,会先存在vector中,因为需要多对信息,构建超定方程矩阵 A
- calib_ric_result 为待求的外参数 R c i R_c^i Rci
Rc.push_back(solveRelativeR(corres));
Rimu.push_back(delta_q_imu.toRotationMatrix());
Rc_g.push_back(ric.inverse() * delta_q_imu * ric);
- Rc 存储直接通过相机测量得到 R c k + 1 c k R^{c_k}_{c_{k+1}} Rck+1ck
- Rimu 存储传入的 delta_q_imu
- Rc_g 对应上面的公式 (1),通过imu陀螺仪计算出来的 R c k + 1 c k R^{c_k}_{c_{k+1}} Rck+1ck ,用于与相机测量得到 R c k + 1 c k R^{c_k}_{c_{k+1}} Rck+1ck,进行比较,若差异过大,使用简易的核函数弱化异常值
当 frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25
成立,则完成计算
bool InitialEXRotation::CalibrationExRotation(vector<pair<Vector3d, Vector3d>> corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result)
{
frame_count++;
Rc.push_back(solveRelativeR(corres));
Rimu.push_back(delta_q_imu.toRotationMatrix());
Rc_g.push_back(ric.inverse() * delta_q_imu * ric);
Eigen::MatrixXd A(frame_count * 4, 4);
A.setZero();
int sum_ok = 0;
for (int i = 1; i <= frame_count; i++)
{
Quaterniond r1(Rc[i]);
Quaterniond r2(Rc_g[i]);
double angular_distance = 180 / M_PI * r1.angularDistance(r2);
ROS_DEBUG(
"%d %f", i, angular_distance);
double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0;
++sum_ok;
Matrix4d L, R;
double w = Quaterniond(Rc[i]).w();
Vector3d q = Quaterniond(Rc[i]).vec();
L.block<3, 3>(0, 0) = w * Matrix3d::Identity() + Utility::skewSymmetric(q);
L.block<3, 1>(0, 3) = q;
L.block<1, 3>(3, 0) = -q.transpose();
L(3, 3) = w;
Quaterniond R_ij(Rimu[i]);
w = R_ij.w();
q = R_ij.vec();
R.block<3, 3>(0, 0) = w * Matrix3d::Identity() - Utility::skewSymmetric(q);
R.block<3, 1>(0, 3) = q;
R.block<1, 3>(3, 0) = -q.transpose();
R(3, 3) = w;
A.block<4, 4>((i - 1) * 4, 0) = huber * (L - R);
}
JacobiSVD<MatrixXd> svd(A, ComputeFullU | ComputeFullV);
Matrix<double, 4, 1> x = svd.matrixV().col(3);
Quaterniond estimated_R(x);
ric = estimated_R.toRotationMatrix().inverse();
//cout << svd.singularValues().transpose() << endl;
//cout << ric << endl;
Vector3d ric_cov;
ric_cov = svd.singularValues().tail<3>();
if (frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25)
{
calib_ric_result = ric;
return true;
}
else
return false;
}