紧耦合后端非线性优化-局部优化(Marginalization)

简介: 紧耦合后端非线性优化-局部优化(Marginalization)

0.前言


局部优化作为VSLAM当中常用的策略,其作用相当于激光SLAM中的局部地图的ICP or NDT优化(scan2localmap)。


如下图所示,在VIO当中,随着时间的推移,路标特征点(landmark)和相机的位姿pose越来越多,BA的计算量随着变量的增加而增加,即使BA的H矩阵是稀疏的,也吃不消。因此,我们要限制优化变量的多少,不能只一味的增加待优化的变量到BA里,而应该去掉一些变量。


那么如何丢变量就成了一个很重要的问题!在vins mono和vins fusion中都存在void Estimator::optimization()函数用来用滑窗执行Ceres优化,边缘化,更新滑窗内图像帧的状态(位姿、速度、偏置、外参、逆深度、相机与IMU时差),但是这个之前在VINS-FUSION 前端后端代码全详解忽略了这一重要的部分。所以在写完回环后发现,关注了全局优化的同时,局部优化却没有关注。


为此写着一篇文章来分析vins中的局部优化部分


87342acd691d4dcc8197cd9a9baad212.png


1. VIO中的状态向量与代价函数


aeb5cfc301c44e18a367923f485e6cc8.png


视觉惯性BA:这三项依次为边缘化的先验信息、IMU的测量残差、视觉的重投影误差

BA优化模型分为三部分:


1、视觉误差函数部分(滑动窗口中特征点在相机下视觉重投影残差)


2、IMU残差部分(滑动窗口中相邻帧间的IMU产生)


3、Marg边缘化残差部分(滑动窗口中去掉位姿和特征点约束)代码中使用Google开源的Ceres solver解决。


2. 视觉约束


这部分要拟合的目标可以通过重投影误差约束,求解的是对同一个路标点的观测值和估计值之间的误差,注意是在归一化平面上表示。


e00b923d1e174f748aa3368e172151ba.png


当某路标点在第i帧观测到并进行初始化操作得到路标点逆深度,当其在第j帧也被观测到时,估计其在第j帧中的坐标为:


f5be14e837864eaba350db7849ef064d.png


此时的视觉残差为:(左侧为根据i帧反推估计的位置,右侧为观测值)


3423b419c41249009aaa74566cb5e3fd.png


逆深度作为参数原因:


1)观测到的特征点深度可能非常大,难以进行优化;

2)可以减少实际优化的参数变量;

3)逆深度更加服从高斯分布。这里特征点的逆深度在第i帧初始化操作中得到。


我们可以看到在vins fusion中


//左相机在i时刻和j时刻分别观测到路标点
    ProjectionTwoFrameOneCamFactor *f_td = new ProjectionTwoFrameOneCamFactor(pts_i, pts_j, it_per_id.feature_per_frame[0].velocity, it_per_frame.velocity,
                                                     it_per_id.feature_per_frame[0].cur_td, it_per_frame.cur_td);
    problem.AddResidualBlock(f_td, loss_function, para_Pose[imu_i], para_Pose[imu_j], para_Ex_Pose[0], para_Feature[feature_index], para_Td[0]);
}
if(STEREO && it_per_frame.is_stereo)
{                
    Vector3d pts_j_right = it_per_frame.pointRight;
    if(imu_i != imu_j)
    {   //左相机在i时刻、右相机在j时刻分别观测到路标点
        ProjectionTwoFrameTwoCamFactor *f = new ProjectionTwoFrameTwoCamFactor(pts_i, pts_j_right, it_per_id.feature_per_frame[0].velocity, it_per_frame.velocityRight,
                                                     it_per_id.feature_per_frame[0].cur_td, it_per_frame.cur_td);
        problem.AddResidualBlock(f, loss_function, para_Pose[imu_i], para_Pose[imu_j], para_Ex_Pose[0], para_Ex_Pose[1], para_Feature[feature_index], para_Td[0]);
    }
    else
    {   //左相机和右相机在i时刻分别观测到路标点
        ProjectionOneFrameTwoCamFactor *f = new ProjectionOneFrameTwoCamFactor(pts_i, pts_j_right, it_per_id.feature_per_frame[0].velocity, it_per_frame.velocityRight,
                                                     it_per_id.feature_per_frame[0].cur_td, it_per_frame.cur_td);
        problem.AddResidualBlock(f, loss_function, para_Ex_Pose[0], para_Ex_Pose[1], para_Feature[feature_index], para_Td[0]);
    }

 

目前我们将以ProjectionTwoFrameOneCamFactor为例子介绍特征点逆深度求解


/**
 * 迭代优化每一步调用,计算变量x在当前状态下的残差,以及残差对变量的Jacobian,用于计算delta_x,更新变量x
 * 优化重投影误差
 * 1、优化变量:前一帧位姿,当前帧位姿,相机与IMU外参,特征点逆深度,相机与IMU时差。对匹配点构建重投影误差
 * 2、计算残差对优化变量的Jacobian
 * @param parameters    优化变量的值
 * @param residuals     output 残差
 * @param jacobians     output 残差对优化变量的Jacobian
*/
bool ProjectionTwoFrameOneCamFactor::Evaluate(double const *const *parameters, double *residuals, double **jacobians) const
{
    // 优化变量:前一帧位姿7,当前位姿7,相机与IMU外参7,特征点逆深度1,相机与IMU时差1
    TicToc tic_toc;
    Eigen::Vector3d Pi(parameters[0][0], parameters[0][1], parameters[0][2]);
    Eigen::Quaterniond Qi(parameters[0][6], parameters[0][3], parameters[0][4], parameters[0][5]);
    Eigen::Vector3d Pj(parameters[1][0], parameters[1][1], parameters[1][2]);
    Eigen::Quaterniond Qj(parameters[1][6], parameters[1][3], parameters[1][4], parameters[1][5]);
    Eigen::Vector3d tic(parameters[2][0], parameters[2][1], parameters[2][2]);
    Eigen::Quaterniond qic(parameters[2][6], parameters[2][3], parameters[2][4], parameters[2][5]);
    double inv_dep_i = parameters[3][0];
    double td = parameters[4][0];
    Eigen::Vector3d pts_i_td, pts_j_td;
    // 匹配点ij, 归一化相机点时差校正,对应到采集时刻的位置,因为IMU数据是对应图像采集时刻的
    pts_i_td = pts_i - (td - td_i) * velocity_i;
    pts_j_td = pts_j - (td - td_j) * velocity_j;
    // 转到相机系
    Eigen::Vector3d pts_camera_i = pts_i_td / inv_dep_i;
    // 转到IMU系
    Eigen::Vector3d pts_imu_i = qic * pts_camera_i + tic;
    // 转到世界系
    Eigen::Vector3d pts_w = Qi * pts_imu_i + Pi;
    // 转到j的IMU系
    Eigen::Vector3d pts_imu_j = Qj.inverse() * (pts_w - Pj);
    // 转到j的相机系
    Eigen::Vector3d pts_camera_j = qic.inverse() * (pts_imu_j - tic);
    Eigen::Map<Eigen::Vector2d> residual(residuals);
#ifdef UNIT_SPHERE_ERROR 
    residual =  tangent_base * (pts_camera_j.normalized() - pts_j_td.normalized());
#else
    // 计算归一化相机平面上的两点误差
    double dep_j = pts_camera_j.z();
    residual = (pts_camera_j / dep_j).head<2>() - pts_j_td.head<2>();
#endif
    residual = sqrt_info * residual;
    if (jacobians)
    {
        Eigen::Matrix3d Ri = Qi.toRotationMatrix();
        Eigen::Matrix3d Rj = Qj.toRotationMatrix();
        Eigen::Matrix3d ric = qic.toRotationMatrix();
        Eigen::Matrix<double, 2, 3> reduce(2, 3);
#ifdef UNIT_SPHERE_ERROR
        double norm = pts_camera_j.norm();
        Eigen::Matrix3d norm_jaco;
        double x1, x2, x3;
        x1 = pts_camera_j(0);
        x2 = pts_camera_j(1);
        x3 = pts_camera_j(2);
        norm_jaco << 1.0 / norm - x1 * x1 / pow(norm, 3), - x1 * x2 / pow(norm, 3),            - x1 * x3 / pow(norm, 3),
                     - x1 * x2 / pow(norm, 3),            1.0 / norm - x2 * x2 / pow(norm, 3), - x2 * x3 / pow(norm, 3),
                     - x1 * x3 / pow(norm, 3),            - x2 * x3 / pow(norm, 3),            1.0 / norm - x3 * x3 / pow(norm, 3);
        reduce = tangent_base * norm_jaco;
#else
        reduce << 1. / dep_j, 0, -pts_camera_j(0) / (dep_j * dep_j),
            0, 1. / dep_j, -pts_camera_j(1) / (dep_j * dep_j);
#endif
        reduce = sqrt_info * reduce;
        // 下面是计算残差对优化变量的Jacobian
        if (jacobians[0])
        {
            Eigen::Map<Eigen::Matrix<double, 2, 7, Eigen::RowMajor>> jacobian_pose_i(jacobians[0]);
            Eigen::Matrix<double, 3, 6> jaco_i;
            jaco_i.leftCols<3>() = ric.transpose() * Rj.transpose();
            jaco_i.rightCols<3>() = ric.transpose() * Rj.transpose() * Ri * -Utility::skewSymmetric(pts_imu_i);
            jacobian_pose_i.leftCols<6>() = reduce * jaco_i;
            jacobian_pose_i.rightCols<1>().setZero();
        }
        if (jacobians[1])
        {
            Eigen::Map<Eigen::Matrix<double, 2, 7, Eigen::RowMajor>> jacobian_pose_j(jacobians[1]);
            Eigen::Matrix<double, 3, 6> jaco_j;
            jaco_j.leftCols<3>() = ric.transpose() * -Rj.transpose();
            jaco_j.rightCols<3>() = ric.transpose() * Utility::skewSymmetric(pts_imu_j);
            jacobian_pose_j.leftCols<6>() = reduce * jaco_j;
            jacobian_pose_j.rightCols<1>().setZero();
        }
        if (jacobians[2])
        {
            Eigen::Map<Eigen::Matrix<double, 2, 7, Eigen::RowMajor>> jacobian_ex_pose(jacobians[2]);
            Eigen::Matrix<double, 3, 6> jaco_ex;
            jaco_ex.leftCols<3>() = ric.transpose() * (Rj.transpose() * Ri - Eigen::Matrix3d::Identity());
            Eigen::Matrix3d tmp_r = ric.transpose() * Rj.transpose() * Ri * ric;
            jaco_ex.rightCols<3>() = -tmp_r * Utility::skewSymmetric(pts_camera_i) + Utility::skewSymmetric(tmp_r * pts_camera_i) +
                                     Utility::skewSymmetric(ric.transpose() * (Rj.transpose() * (Ri * tic + Pi - Pj) - tic));
            jacobian_ex_pose.leftCols<6>() = reduce * jaco_ex;
            jacobian_ex_pose.rightCols<1>().setZero();
        }
        if (jacobians[3])
        {
            Eigen::Map<Eigen::Vector2d> jacobian_feature(jacobians[3]);
            jacobian_feature = reduce * ric.transpose() * Rj.transpose() * Ri * ric * pts_i_td * -1.0 / (inv_dep_i * inv_dep_i);
        }
        if (jacobians[4])
        {
            Eigen::Map<Eigen::Vector2d> jacobian_td(jacobians[4]);
            jacobian_td = reduce * ric.transpose() * Rj.transpose() * Ri * ric * velocity_i / inv_dep_i * -1.0  +
                          sqrt_info * velocity_j.head(2);
        }
    }
    sum_t += tic_toc.toc();
    return true;
}


…详情请参照古月居

相关文章
|
30天前
|
缓存 负载均衡 算法
后端架构设计中的优化技巧
【2月更文挑战第9天】 后端架构设计是一个复杂而关键的工作,不仅需要考虑系统的可靠性和扩展性,还需要保证系统的高性能。本文将介绍一些后端架构设计中的优化技巧,包括数据库设计、缓存优化、负载均衡等方面的内容,帮助开发者在设计后端架构时更好地提升系统性能。
53 1
|
30天前
|
存储 缓存 NoSQL
Redis多级缓存指南:从前端到后端全方位优化!
本文探讨了现代互联网应用中,多级缓存的重要性,特别是Redis在缓存中间件的角色。多级缓存能提升数据访问速度、系统稳定性和可扩展性,减少数据库压力,并允许灵活的缓存策略。浏览器本地内存缓存和磁盘缓存分别优化了短期数据和静态资源的存储,而服务端本地内存缓存和网络内存缓存(如Redis)则提供了高速访问和分布式系统的解决方案。服务器本地磁盘缓存因I/O性能瓶颈和复杂管理而不推荐用于缓存,强调了内存和网络缓存的优越性。
236 47
|
30天前
|
缓存 数据库 索引
高效后端开发:数据库优化策略详解
数据库优化是后端开发中不可或缺的一部分。本文将从多个方面详细阐述数据库优化策略,包括数据表设计、索引优化、查询性能优化等。通过本文的学习,读者可以了解如何优化数据库,提升后端应用性能和稳定性。
|
1天前
|
监控 前端开发 Java
JVM系列9-后端编译与优化
JVM系列9-后端编译与优化
6 1
|
13天前
|
消息中间件 缓存 负载均衡
构建高性能的后端服务:优化策略与实践
在当今互联网时代,构建高性能的后端服务至关重要。本文将深入探讨如何通过优化策略与实践来提升后端服务的性能。我们将从数据库优化、缓存策略、异步处理和负载均衡等方面展开讨论,帮助开发者构建出稳定、高效的后端架构。
15 2
|
13天前
|
消息中间件 数据库 网络架构
构建高效后端:微服务架构的优化策略
【5月更文挑战第31天】在这篇文章中,我们将深入探讨如何通过采用微服务架构来提升后端开发的效率和性能。我们将分析微服务架构的关键优势,并讨论如何克服实施过程中的挑战。通过具体的案例研究,我们将展示如何优化微服务架构以实现最佳的性能和可维护性。无论你是后端开发的新手还是经验丰富的专业人士,这篇文章都将为你提供有价值的见解和实用的技巧。
|
30天前
|
SQL 关系型数据库 MySQL
后端接口性能优化分析-数据库优化(下)
后端接口性能优化分析-数据库优化
87 1
|
30天前
|
前端开发 测试技术 持续交付
《跨界合作:前端与后端如何优化协作效率》
在当今软件开发领域,前端和后端开发团队通常是分开工作的,但他们的协作质量直接影响着项目的成功与否。本文将探讨如何通过优化前端与后端的协作方式,提高开发效率和项目质量,从而实现更好的跨界合作。
|
30天前
|
API 开发者 UED
构建高效微服务架构:后端开发的新趋势移动应用与系统:开发与优化的艺术
【4月更文挑战第30天】 随着现代软件系统对可伸缩性、灵活性和敏捷性的日益需求,传统的单体应用架构正逐渐向微服务架构转变。本文将探讨微服务架构的核心概念,分析其优势,并着重讨论如何利用最新的后端技术栈实现一个高效的微服务系统。我们将涵盖设计模式、服务划分、数据一致性、服务发现与注册、API网关以及容器化等关键技术点,为后端开发者提供一份实操指南。 【4月更文挑战第30天】 在数字化时代的浪潮中,移动应用和操作系统的紧密交织已成为日常生活和商业活动的基石。本文将深入探讨移动应用开发的关键技术、跨平台开发工具的选择以及移动操作系统的架构和性能优化策略。通过分析当前移动应用开发的挑战与机遇,我们将
|
30天前
|
存储 缓存 算法
后端技术优化与应用研究
后端技术优化与应用研究
18 1