Vins-Fusion之 相机—IMU在线标定(十一)

计算找到相机和 IMU 之间的旋转关系(外参旋转 ric)

cpp 复制代码
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);//把 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);// 如果角度差大,说明当前估计的ric不好,降低权重
        ROS_DEBUG(
            "%d %f", i, angular_distance);

        double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0;//使用 Huber 权重函数,对大角度差进行抑制
        ++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();
    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;
}

这个是整个Camera和IMU标定的过程代码,通过:相机看到的旋转 = 转换关系 × IMU感受到的旋转 × 转换关系的逆 求解转换关系。

阶段1:收集数据
cpp 复制代码
frame_count++;  // 计数器+1(第几次调用)
Rc.push_back(solveRelativeR(corres));  // 视觉算出的旋转
Rimu.push_back(delta_q_imu.toRotationMatrix());  // IMU算出的旋转
Rc_g.push_back(ric.inverse() * delta_q_imu * ric);  // 把IMU旋转转换到相机系

含义:

  • Rc[i]:第 i 次调用时,视觉算出的两帧相机相对旋转
  • Rimu[i]:第 i 次调用时,IMU 算出的两帧 IMU 相对旋转
  • Rc_g[i]:用当前估计的 ric 把 IMU 旋转转换到相机系
阶段2:构建方程
cpp 复制代码
Eigen::MatrixXd A(frame_count * 4, 4);  // 大矩阵:行数=帧数×4,列数=4
A.setZero();  // 初始化为0

目的:构建线性方程组 A×x=0,其中 x 是外参旋转的四元数

cpp 复制代码
for (int i = 1; i <= frame_count; i++)  // 遍历所有收集的数据
{
    // 1. 计算角度差(用于权重)
    Quaterniond r1(Rc[i]);      // 视觉旋转(四元数)
    Quaterniond r2(Rc_g[i]);    // IMU旋转转换到相机系(四元数)
    
    double angular_distance = 180 / M_PI * r1.angularDistance(r2);
    // 如果角度差大,说明当前估计的ric不好,降低权重
    double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0;

Huber 权重:

  • 角度差 ≤ 5°:权重 = 1.0
  • 角度差 > 5°:权重 = 5.0 / 角度差(降低影响)
cpp 复制代码
    // 2. 构造四元数乘法的矩阵形式
    Matrix4d L, R;  // 两个4×4矩阵
    
    // L矩阵:表示 q_camera × q_ext 的乘法
    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;
    
    // R矩阵:表示 q_ext × q_imu 的乘法
    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;

四元数乘法的矩阵表示:

阶段3:求解方程
cpp 复制代码
JacobiSVD<MatrixXd> svd(A, ComputeFullU | ComputeFullV);  // SVD分解
Matrix<double, 4, 1> x = svd.matrixV().col(3);  // 取最小奇异值对应的向量
Quaterniond estimated_R(x);  // 这就是估计的外参旋转(四元数)
ric = estimated_R.toRotationMatrix().inverse();  // 转换为旋转矩阵并求逆

SVD 求解:

  • 对 A×x=0做 SVD:A=UΣVT
  • 最小奇异值对应的 V 的列就是最优解
  • 取最后一列(第3列,索引从0开始)

为什么求逆?

  • 代码中估计的是 ric 的逆,所以最后要再求一次逆
阶段4:判断收敛
cpp 复制代码
Vector3d ric_cov;
ric_cov = svd.singularValues().tail<3>();  // 取后3个奇异值
if (frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25)
{
    calib_ric_result = ric;  // 标定成功!
    return true;
}
else
    return false;  // 继续收集数据

收敛判断:

  • frame_count >= WINDOW_SIZE:收集了足够的数据(通常10帧)
  • ric_cov(1) > 0.25:第二个奇异值足够大,说明解稳定
相关推荐
地平线开发者18 分钟前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考25 分钟前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习
冬奇Lab1 小时前
OpenClaw 源码精读(2):Channel & Routing——一条消息如何找到它的 Agent?
人工智能·开源·源码阅读
冬奇Lab1 小时前
一天一个开源项目(第38篇):Claude Code Telegram - 用 Telegram 远程用 Claude Code,随时随地聊项目
人工智能·开源·资讯
格砸2 小时前
从入门到辞职|从ChatGPT到OpenClaw,跟上智能时代的进化
前端·人工智能·后端
可观测性用观测云3 小时前
可观测性 4.0:教系统如何思考
人工智能
sunny8653 小时前
Claude Code 跨会话上下文恢复:从 8 次纠正到 0 次的工程实践
人工智能·开源·github
小笼包包仔3 小时前
OpenClaw 多Agent软件开发最佳实践指南
人工智能
smallyoung3 小时前
AgenticRAG:智能体驱动的检索增强生成
人工智能
_skyming_3 小时前
OpenCode 如何做到结果不做自动质量评估,为什么结果还不错?
人工智能