VINS外参标定

由于使用的数据相对参数提供的不够精确导致结果精度不高,因此需要研究一下外参的在线估计。
VINS外参标定指的是对相机坐标系到IMU坐标系的变换矩阵进行在线标定与优化。

1、参数配置

在参数配置文件yaml中,参数estimate_extrinsic反映了外参的情况:
1、等于0表示这外参已经是准确的了,之后不需要优化;
2、等于1表示外参只是一个估计值,后续还需要将其作为初始值放入非线性优化中;
3、等于2表示不知道外参,需要进行标定,从代码estimator.cpp中的processImage()中的以下代码进入,主要是标定外参的旋转矩阵。

if (ESTIMATE_EXTRINSIC == 2) // 不知道相机外参
    {
     
        ROS_WARN("have no prior about extrinsic param, calibrate extrinsic param");
        RIC.push_back(Eigen::Matrix3d::Identity());
        TIC.push_back(Eigen::Vector3d::Zero());
        EX_CALIB_RESULT_PATH = OUTPUT_PATH + "/extrinsic_parameter.csv";

    }
    else // 知道相机外参
    {
     
        if ( ESTIMATE_EXTRINSIC == 1) // 虽然知道相机外参,但是在优化过程中还是去优化外参,这里的1只是标记了一种状态,并不是指优化的参数的数量
        {
     
            ROS_WARN(" Optimize extrinsic param around initial guess!");
            EX_CALIB_RESULT_PATH = OUTPUT_PATH + "/extrinsic_parameter.csv";
        }
        if (ESTIMATE_EXTRINSIC == 0) // 知道相机外参,而且在优化过程中该参数是固定的,不参与优化
            ROS_WARN(" fix extrinsic param ");

        cv::Mat cv_R, cv_T;
        fsSettings["extrinsicRotation"] >> cv_R;
        fsSettings["extrinsicTranslation"] >> cv_T;
        Eigen::Matrix3d eigen_R;
        Eigen::Vector3d eigen_T;
        cv::cv2eigen(cv_R, eigen_R);
        cv::cv2eigen(cv_T, eigen_T);
        Eigen::Quaterniond Q(eigen_R);
        eigen_R = Q.normalized();
        RIC.push_back(eigen_R);
        TIC.push_back(eigen_T);
        ROS_INFO_STREAM("Extrinsic_R : " << std::endl << RIC[0]);
        ROS_INFO_STREAM("Extrinsic_T : " << std::endl << TIC[0].transpose());
        
    } 

如果需要在线估计参数且给出初始值时,则需要先读取矩阵R,t,并将其从opencv矩阵转化为eigen矩阵,得到容器RIC,TIC的第一个矩阵分量。

2、原理

利用矩阵之间的对应关系,相机与IMU的外参固定不变。
VINS外参标定_第1张图片

其中q_bk_bk+1是陀螺仪预积分得到的,q_ck_ck+1是用8点法对前后帧对应的特征点进行计算得到的。

3、包含函数

在线估计外参的具体实现方式在InitialEXRotation类中,该类位于vins_estimator/src/initial/initial_ex_rotation.cpp/.h中,用于标定外参旋转矩阵。首先简要介绍一下InitialEXRotation类的成员:

class InitialEXRotation
{
     
public:
	InitialEXRotation();//构造函数
	//标定外参旋转矩阵
    bool CalibrationExRotation(vector<pair<Vector3d, Vector3d>> corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result);
private:
	//求解帧间cam坐标系的旋转矩阵
	Matrix3d solveRelativeR(const vector<pair<Vector3d, Vector3d>> &corres);
	//三角化验证Rt
    double testTriangulation(const vector<cv::Point2f> &l,
                             const vector<cv::Point2f> &r,
                             cv::Mat_<double> R, cv::Mat_<double> t);
    //本质矩阵SVD分解计算4组Rt值
    void decomposeE(cv::Mat E,
                    cv::Mat_<double> &R1, cv::Mat_<double> &R2,
                    cv::Mat_<double> &t1, cv::Mat_<double> &t2);
                    
    int frame_count;//帧计数器
    vector< Matrix3d > Rc;//帧间cam的R,由对极几何得到
    vector< Matrix3d > Rimu;//帧间IMU的R,由IMU预积分得到
    vector< Matrix3d > Rc_g;//每帧IMU相对于起始帧IMU的R
    Matrix3d ric;//cam到IMU的外参
};

4、估计实现

CalibrationExRotation() 标定外参旋转矩阵,该函数目的是标定外参的旋转矩阵。函数的入参 corres 是包含 两帧之间 的多个匹配点对的归一化坐标的vector数组,入参 delta_q 是通过 两帧间 陀螺仪积分得到相对旋转;

bool InitialEXRotation::CalibrationExRotation(vector<pair<Vector3d, Vector3d>> 
     corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result)
{
     
    /*记录第几次进入这个函数,标定的过程中会多次进入这个函数,
    直到标定成功,每进一次通过入参得到公式(6)这样一个约束。*/
    frame_count++;  
   /*过帧间的匹配点对计算得到本质矩阵,然后分解得到旋转矩阵R_ck+1^ck*/
    Rc.push_back(solveRelativeR(corres));
    Rimu.push_back(delta_q_imu.toRotationMatrix());//帧间陀螺仪积分得到R_bk+1^bk
    /*每次迭代前先用前一次估计的ric将R_bk+1^bk变换成R_ck+1^ck,即公式(1)*/
    Rc_g.push_back(ric.inverse() * delta_q_imu * ric);
 
    Eigen::MatrixXd A(frame_count * 4, 4); //构建公式(7)中的A矩阵
    A.setZero();
    int sum_ok = 0;
    for (int i = 1; i <= frame_count; i++)
    {
     
        Quaterniond r1(Rc[i]);
        Quaterniond r2(Rc_g[i]);
 
        /* angularDistance就是计算两个坐标系之间相对旋转矩阵在做轴角变换后(u * theta)
        的角度theta, theta越小说明两个坐标系的姿态越接近,这个角度距离用于后面计算权重,
        这里计算权重就是为了降低外点的干扰,意思就是为了防止出现误差非常大的R_bk+1^bk和 
        R_ck+1^ck约束导致估计的结果偏差太大*/
        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;
 
   
        /*L R 分别为四元数左乘和四元数右乘矩阵
         下面的这几步就是从公式(3)到公式(6)的过程 ,每次得到4行约束填入A矩阵中*/
        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);
    }
 
    //对A矩阵做SVD分解,最小奇异值对应的右向量就是四元数解
    JacobiSVD<MatrixXd> svd(A, ComputeFullU | ComputeFullV);
    //这里的四元数存储的顺序是[x,y,z,w]',即[qv qw]'
    Matrix<double, 4, 1> x = svd.matrixV().col(3); 
    Quaterniond estimated_R(x);
    ric = estimated_R.toRotationMatrix().inverse();
    Vector3d ric_cov;
    ric_cov = svd.singularValues().tail<3>();
 
    /* 至少会迭代WINDOW_SIZE次,并且第二小的奇异值大于0.25才认为标定成功
   (singularValues拿到的奇异值是从大到小存储的),
    意思就是最小的奇异值要足够接近于0,和第二小之间要有足够差距才行, 
    这样才算求出了最优解 */
    if (frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25)
    {
     
        calib_ric_result = ric;
        return true;
    }
    else
        return false;
}

1、solveRelativeR(corres)根据对极几何计算相邻帧间相机坐标系的旋转矩阵,这里的corres传入的是当前帧和其之前一帧的对应特征点对的归一化坐标。
1.1、将corres中对应点的二维坐标分别存入ll与rr中。

vector<cv::Point2f> ll, rr;
        for (int i = 0; i < int(corres.size()); i++)
        {
     
            ll.push_back(cv::Point2f(corres[i].first(0), corres[i].first(1)));
            rr.push_back(cv::Point2f(corres[i].second(0), corres[i].second(1)));
        }

1.2、根据对极几何求解这两帧的本质矩阵

  cv::Mat E = cv::findFundamentalMat(ll, rr);

1.3、对本质矩阵进行svd分解得到4组Rt解

 cv::Mat_<double> R1, R2, t1, t2;
  decomposeE(E, R1, R2, t1, t2);

1.4、采用三角化对每组Rt解进行验证,选择是正深度的Rt解

double ratio1 = max(testTriangulation(ll, rr, R1, t1), testTriangulation(ll, rr, R1, t2));
double ratio2 = max(testTriangulation(ll, rr, R2, t1), testTriangulation(ll, rr, R2, t2));
cv::Mat_<double> ans_R_cv = ratio1 > ratio2 ? R1 : R2;

1.5、这里的R是上一帧到当前帧的变换矩阵,对其求转置为当前帧相对于上一帧的姿态。

Matrix3d ans_R_eigen;
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                ans_R_eigen(j, i) = ans_R_cv(i, j);
        return ans_R_eigen;
}

你可能感兴趣的:(VINS外参标定)