记录最近学习vSLAM的一些理论和代码实现。
三角化的数学描述
-
考虑某路标点 y 在若干个关键帧 k = 1,…,n 中看到。
y∈ R 4 R^4 R4,取齐次坐标。每次观测为 x k x_k xk=[ u k u_k uk, v k v_k vk,1] T ^T T,取归一化平面坐标(这样可以忽略掉内参)。 -
从World系到Camera系的投影矩阵 P k = [ R K , t k ] ∈ R 3 × 4 P_k=[R_K,t_k]∈R^{3×4} Pk=[RK,tk]∈R3×4。
-
投影关系: ∀ k , λ k x k = P k y . . . . ( 1 ) \forall k,\lambda_k x_k= P_k y....(1) ∀k,λkxk=Pky....(1)其中 λ k \lambda_k λk为观测点的深度值(未知)
-
根据上式第三行:
λ k = P k , 3 T y . . . . ( 2 ) \lambda_k=P^T_{k,3} y....(2) λk=Pk,3Ty....(2) 其中 P k , 3 T P^T_{k,3} Pk,3T为 P k P_k Pk的第3行 -
将(2)代入(1)的前两行:
u k P k , 3 T y = P k , 1 T y u_kP_{k,3}^T y=P_{k,1}^T y ukPk,3Ty=Pk,1Ty v k P k , 3 T y = P k , 2 T y v_kP_{k,3}^T y=P_{k,2}^T y vkPk,3Ty=Pk,2Ty -
每次观测将提供两个这样的方程,视 y y y为未知量,并将 y y y移到等式一侧:
[ u 1 P 1 , 3 T − P 1 , 1 T v 1 P 1 , 3 T − P 1 , 2 T ⋮ u n P n , 3 T − P n , 1 T u n P n , 3 T − P n , 2 T ] y = 0 → D y = 0 \begin{bmatrix} u_1P^T_{1,3}-P^T_{1,1} \\ v_1P^T_{1,3}-P^T_{1,2} \\ \vdots \\ u_nP^T_{n,3}-P^T_{n,1} \\ u_nP^T_{n,3}-P^T_{n,2} \end{bmatrix}y=0\rightarrow Dy=0 ⎣⎢⎢⎢⎢⎢⎡u1P1,3T−P1,1Tv1P1,3T−P1,2T⋮unPn,3T−Pn,1TunPn,3T−Pn,2T⎦⎥⎥⎥⎥⎥⎤y=0→Dy=0于是, y y y为 D D D零空间中的一个非零元素。 -
由于 D ∈ R 2 n × 4 D∈R^{2n×4} D∈R2n×4,在观测次数大于等于两次时,很可能 D D D满秩(因为容易得到4个线性无关组),无零空间。
-
寻找最小二乘解: m i n y ∣ ∣ D y ∣ ∣ 2 2 \underset{y}{min}||Dy||^2_2 ymin∣∣Dy∣∣22 s t . ∣ ∣ y ∣ ∣ = 1 st.||y||=1 st.∣∣y∣∣=1解法:对 D T D D^TD DTD进行SVD分解: D T D = V Σ V T D^TD=V\Sigma V^T DTD=VΣVT其中V为4×4的酉矩阵。或者还可以表示为: D T D = ∑ i = 1 4 σ i 2 u i u j T D^TD=\sum_{i=1}^4 \sigma^2_i u_iu_j^T DTD=i=1∑4σi2uiujT
其中 σ i \sigma_i σi为奇异值,且由大到小排列, u i u_i ui, u j u_j uj正交。 -
此时取 y = u 4 y=u_4 y=u4,那么该问题的目标函数值为 σ 4 \sigma_4 σ4。
10.判断该解有效性的条件: σ 4 < < σ 3 \sigma_4\ << \sigma_3 σ4 <<σ3。若该条件成立,则认为三角化有效,否则认为三角化无效。
代码实现
//
// Created by hyj on 18-11-11.
//
#include <iostream>
#include <vector>
#include <random>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/Eigenvalues>
struct Pose
{
Pose(Eigen::Matrix3d R, Eigen::Vector3d t):Rwc(R),qwc(R),twc(t) {};
Eigen::Matrix3d Rwc;
Eigen::Quaterniond qwc;
Eigen::Vector3d twc;
Eigen::Vector2d uv; // 这帧图像观测到的特征坐标
};
int main()
{
int poseNums = 10;
double radius = 8;
double fx = 1.;
double fy = 1.;
std::vector<Pose> camera_pose;
for(int n = 0; n < poseNums; ++n ) {
double theta = n * 2 * M_PI / ( poseNums * 4); // 1/4 圆弧
// 绕 z轴 旋转
Eigen::Matrix3d R;
R = Eigen::AngleAxisd(theta, Eigen::Vector3d::UnitZ());
Eigen::Vector3d t = Eigen::Vector3d(radius * cos(theta) - radius, radius * sin(theta), 1 * sin(2 * theta));
camera_pose.push_back(Pose(R,t));
}
// 随机数生成 1 个 三维特征点
std::default_random_engine generator;
std::uniform_real_distribution<double> xy_rand(-4, 4.0);
std::uniform_real_distribution<double> z_rand(8., 10.);
double tx = xy_rand(generator);
double ty = xy_rand(generator);
double tz = z_rand(generator);
Eigen::Vector3d Pw(tx, ty, tz);
// 这个特征从第三帧相机开始被观测,i=3
int start_frame_id = 3;
int end_frame_id = poseNums;
for (int i = start_frame_id; i < end_frame_id; ++i) {
Eigen::Matrix3d Rcw = camera_pose[i].Rwc.transpose();
Eigen::Vector3d Pc = Rcw * (Pw - camera_pose[i].twc);
double x = Pc.x();
double y = Pc.y();
double z = Pc.z();
camera_pose[i].uv = Eigen::Vector2d(x/z,y/z);
}
/// TODO::homework; 请完成三角化估计深度的代码
// 遍历所有的观测数据,并三角化
Eigen::Vector3d P_est; // 结果保存到这个变量
P_est.setZero();
/* 对应上述公式部分 */
auto loop_times = camera_pose.size() - start_frame_id;
Eigen::MatrixXd D((loop_times) * 2, 4);
for(int j = 0; j < loop_times; j++){
Eigen::MatrixXd T_tmp(3, 4);
T_tmp.block<3, 3>(0, 0) = camera_pose[j + start_frame_id].Rwc.transpose();
T_tmp.block<3, 1>(0, 3) = -camera_pose[j + start_frame_id].Rwc.transpose()
* camera_pose[j + start_frame_id].twc;
auto P_k1 = T_tmp.block<1, 4>(0, 0);
auto P_k2 = T_tmp.block<1, 4>(1, 0);
auto P_k3 = T_tmp.block<1, 4>(2, 0);
D.block<1, 4>(2 * j, 0) = camera_pose[j + start_frame_id].uv[0] * P_k3 - P_k1;
D.block<1, 4>(2 * j + 1, 0) = camera_pose[j + start_frame_id].uv[1] * P_k3 - P_k2;
}
Eigen::Matrix4d D_res = D.transpose() * D;
Eigen::JacobiSVD<Eigen::Matrix4d> svd(D_res, Eigen::ComputeFullU | Eigen::computeFullV); // 奇异值分解
auto res_U = svd.matrixU();
auto res_V = svd.matrixV();
auto tmp = res_U.rightCols(1);
std::cout << "res=" << tmp / tmp(3) << std::endl;
/* END */
// std::cout <<"D: \n"<< D.transpose()*D <<std::endl;
// std::cout <<"ground truth: \n"<< Pw.transpose() <<std::endl;
// std::cout <<"your result: \n"<< P_est.transpose() <<std::endl;
return 0;
}