文章目录

  • 前言·与同主题博文的不同
  • 1.代码修改
    • 1.1阻尼因子 µ 随着迭代变化的曲线图
    • 1.2完成曲线y = ax^2^ + bx + c的参数估计
    • 1.3实现其他更优秀的阻尼因子策略
  • 2.公式推导
  • 3.证明式(9)

前言·与同主题博文的不同

延续该系列博文的风格,本博文尽量补充同主题博文所没有涉及到的关键点。对于第三章,所补充的关键点包含以下几个方面:第1题实现其他阻尼因子策略所涉及到的几乎所有细节(参考的论文,公式与代码的对应关系,代码修改后函数的整体部分),第2题中公式推导最开始表达式的由来,第3题的证明给出矩阵相乘的具体展开式。有任何问题都欢迎大家在评论区批评交流。

1.代码修改

样例代码给出了使用 LM 算法来估计曲线 y = exp(ax^2+ bx + c)参数 a, b, c 的完整过程。
1 请绘制样例代码中 LM 阻尼因子 µ 随着迭代变化的曲线图
2 将曲线函数改成 y = ax^2 + bx + c,请修改样例代码中残差计算,雅克比计算等函数,完成曲线参数估计
3 实现其他更优秀的阻尼因子策略,并给出实验对比

首先对样例代码做一个简单的介绍。app文件夹中有曲线拟合函数CurveFitting.cpp,backend文件夹是后端,求LM问题的一些函数在这个文件夹中,其中problem.cc的作用是定义和求解最小二乘问题,vertex是顶点,表示我们需要优化的状态量,edge就是构建出来的残差项。

1.1阻尼因子 µ 随着迭代变化的曲线图

编译并运行给定的样例代码,得到如下图所示结果,其中lambda就表示阻尼因子。


将上图中的lambda值整理后,用MATLAB生成对应的曲线图,代码和曲线图分别如下:

x=(0:11);%迭代次数
y=[0.001, 699.051, 1864.14, 1242.76, 414.252, 138.084, 46.028, 15.3427, 5.11423, 1.70474, 0.568247, 0.378832];%阻尼因子序列
plot (x,y,'-o','LineWidth',1.5);grid on;%画图


特别说明:图中的阻尼因子序列并不包含迭代过程中计算出的所有阻尼因子,只有满足所使用的阻尼因子更新策略中阻尼因子输出条件的才会被记录。程序中使用的是Nielsen 策略(具体实现查看problem.cc中的IsGoodStepInLM函数)。

1.2完成曲线y = ax2 + bx + c的参数估计

首先需要明确修改文件CurveFitting.cpp中的哪几处代码。因为需要将y = exp(ax2 + bx + c)改为y = ax2+ bx + c,小编以“exp”为关键字,利用搜索功能找到所有与exp相关的代码,经过分析,发现有三处需要修改。
第一处是main函数中的观测值y,代码如下:

// 构造 N 次观测for (int i = 0; i < N; ++i) {double x = i/100.;double n = noise(generator);// 观测 ydouble y = a*x*x + b*x + c + n;

第二处是构建残差,代码如下:

// 计算曲线模型误差virtual void ComputeResidual() override{Vec3 abc = verticies_[0]->Parameters();  // 估计的参数residual_(0) = abc(0)*x_*x_ + abc(1)*x_ + abc(2)  - y_;  // 构建残差}

第三处是计算残差对变量的雅克比,代码如下:

// 计算残差对变量的雅克比virtual void ComputeJacobians() override{Eigen::Matrix<double, 1, 3> jaco_abc;  // 误差为1维,状态量 3 个,所以是 1x3 的雅克比矩阵jaco_abc << x_ * x_ , x_  , 1 ;jacobians_[0] = jaco_abc;}

编译并运行修改之后的代码,结果如下:

可以发现参数估计误差较大。由于只修改了原代码中需要估计的曲线参数,并不会引起算法失效,初步分析,误差较大的原因可能是数据点数量不足。于是将数据点由100增加到1000,所需要修改的代码如下:

int main()
{double a=1.0, b=2.0, c=1.0;         // 真实参数值int N = 1000;                          // 数据点double w_sigma= 1.;                 // 噪声Sigma值

之后再次编译运行,结果如下:

可以看出,参数估计精度已经足够高。

1.3实现其他更优秀的阻尼因子策略

首先要说明的是,1.3这个部分小编是站在之前的优秀博文从零开始手写VIO第三期作业总结(仅供参考)的肩膀上完成的,在其中增加了自己思考的过程,并将其以阅读性较强的方式记录如下。
课件中提示可以参考论文《The Levenberg-Marquardt method for nonlinear least squares curve-fitting problems》中的阻尼因子策略。该论文中共有三种阻尼因子策略,如下图:

第三种策略与样例代码中的策略一样,都是前面提到的Nielsen 策略。下面在原样例代码problem.cc的基础上实现论文中的第一种阻尼因子策略。
通过对比分析上图中两种策略的不同点来修改样例代码。首先两种方法对阻尼因子初始化的方式不同,所以修改ComputeLambdaInitLM函数代码如下,这里一定要注意的是前面与变量currentChi_相关的语句必须保留,这里与程序中其它部分相关联。另外,为了之后与Nielsen 策略作对比,这里的阻尼因子的初值选为0.001,与1.1部分中阻尼因子的初值相同。

void Problem::ComputeLambdaInitLM() {currentChi_ = 0.0;// TODO:: robust cost chi2for (auto edge: edges_) {currentChi_ += edge.second->Chi2();}if (err_prior_.rows() > 0)currentChi_ += err_prior_.norm();stopThresholdLM_ = 1e-6 * currentChi_;          // 迭代条件为 误差下降 1e-6 倍   currentLambda_ = 1e-3;
}

其次,两种策略计算状态量的变化量和ρ的方式不同,论文中公式(12),公式(13),公式(15)和公式(16)如下图:

经过分析,公式(12)和公式(13)的主要区别在于两者对Hessian Matrix修改的方式不一样。
修改AddLambdatoHessianLM函数:

void Problem::AddLambdatoHessianLM() {ulong size = Hessian_.cols();assert(Hessian_.rows() == Hessian_.cols() && "Hessian is not square");for (ulong i = 0; i < size; ++i) {Hessian_(i, i) += currentLambda_ * Hessian_(i, i);}
}

修改RemoveLambdaHessianLM函数:

void Problem::RemoveLambdaHessianLM() {ulong size = Hessian_.cols();assert(Hessian_.rows() == Hessian_.cols() && "Hessian is not square");// TODO:: 这里不应该减去一个,数值的反复加减容易造成数值精度出问题?而应该保存叠加lambda前的值,在这里直接赋值for (ulong i = 0; i < size; ++i) {Hessian_(i, i) /= 1.0 + currentLambda_;}
}

最后公式(15)和公式(16)的主要区别在于分母计算的不同,修改IsGoodStepInLM函数:

bool Problem::IsGoodStepInLM() {// 统计所有的残差double tempChi = 0.0;for (auto edge : edges_) {edge.second->ComputeResidual();tempChi += edge.second->Chi2();}// compute rhoassert(Hessian_.rows() == Hessian_.cols() && "Hessian is not square");ulong size = Hessian_.cols();MatXX diag_hessian(MatXX::Zero(size, size));for (ulong i = 0; i < size; ++i) {diag_hessian(i, i) = Hessian_(i, i);}double scale = delta_x_.transpose() * (currentLambda_ * diag_hessian * delta_x_ + b_);double rho = (currentChi_ - tempChi) / scale;// update currentLambda_double epsilon = 0.0;double L_down = 9.0;double L_up = 11.0;if (rho > epsilon && isfinite(tempChi)) {currentLambda_ = std::max(currentLambda_ / L_down, 1e-7);currentChi_ = tempChi;return true;}else {currentLambda_ = std::min(currentLambda_ * L_up, 1e7);return false;}
}

对于程序中参数的选取问题,建议有兴趣的读者阅读论文本身。
对修改之后的代码进行编译,运行之后得到下图:

对比分析:第一、两种策略参数估计精度相当;第二、两种策略在阻尼因子的初值相同的条件下,论文中的第一种方法所需要迭代的次数更少。说明有时候简单的策略会产生更好的效果。

2.公式推导

对于这里涉及的公式推导,同主题博文基本都是直接给出了公式推导演变的过程,并没有给出公式最开始的表达式从何而来,这是小编在下面要重点阐述的部分。
需要推导的题目如下:

严格来说,公式推导需要铺垫大量的数学理论背景知识,这是小编所做不到的。但是没有理论铺垫又会让推导无据可循。小编采取的折中的办法是,以课件中f35的推导为理论依据,作为准则,所给两道题目中的推导除公式最开始的表达式外,推导过程只是在数值和符号上与f35的推导有所区别。课件中f35的推导过程如下图,最关键的点在于下图中红框中的-δbgk是如何得到的!

下面是f15和g12的推导,详细说明了推导最开始的表达式从何而来。前面所说的f35的-δbgk的由来可以参考f15中-δbgk的由来。


图片中提到的课件中P37和P44的内容截图如下:

3.证明式(9)

这道题的证明给出矩阵相乘的具体展开式。


这道题的证明小编也是站在之前的优秀博文从零开始手写VIO第三期作业总结(仅供参考)的肩膀上完成的,在这里对原博主Zkangsen表示感谢!

从零开始手写VIO第三章作业(含关键点细节及思维过程)相关推荐

  1. 《视觉SLAM进阶:从零开始手写VIO》第二讲作业-IMU仿真、MU imu_utils标定

    <视觉SLAM进阶:从零开始手写VIO>第二讲作业-IMU仿真.MU imu_utils标定 作业题目: 1 仿真代码解析 仿真代码地址:https://github.com/HeYiji ...

  2. 《视觉SLAM进阶:从零开始手写VIO》第一讲作业

    目录 1 视觉与IMU融合之后有何优势? 2 有哪些常见的视觉+IMU融合方案?有没有工业界应用的例子? 3 在学术界,VIO研究有哪些新进展?有没有将学习方法应用到VIO的例子? 4 四元数和李代数 ...

  3. 深蓝学院《从零开始手写VIO》作业三

    深蓝学院<从零开始手写VIO>作业三 深蓝学院<从零开始手写VIO>作业三 1. 代码修改 2. 公式推导 3. 公式证明: 深蓝学院<从零开始手写VIO>作业三 ...

  4. 深蓝学院《从零开始手写VIO》作业七

    深蓝学院<从零开始手写VIO>作业七 深蓝学院<从零开始手写VIO>作业七 深蓝学院<从零开始手写VIO>作业七 将第二讲中的仿真数据(视觉特征,imu数据)接入V ...

  5. 深蓝学院《从零开始手写VIO》作业六

    深蓝学院<从零开始手写VIO>作业五 深蓝学院<从零开始手写VIO>作业六 1. 证明题 2. 代码题 深蓝学院<从零开始手写VIO>作业六 1. 证明题 证明Dy ...

  6. 深蓝学院《从零开始手写VIO》作业五

    深蓝学院<从零开始手写VIO>作业五 1. 完成Bundle Adjustment求解器 2. 完成测试函数 3. 论文总结 1. 完成Bundle Adjustment求解器 完成单目 ...

  7. 深蓝学院《从零开始手写VIO》作业一

    深蓝学院<从零开始手写VIO>作业一 深蓝学院<从零开始手写VIO>作业一 1. VIO文献阅读 1.1 视觉与IMU进行融合之后有何优势? 1.2 有哪些常见的视觉+IMU融 ...

  8. 从零开始手写VIO 第二章 IMU传感器

    第二章 IMU传感器 课程代码: https://github.com/kahowang/Visual_Internal_Odometry/tree/main/%E7%AC%AC%E4%BA%8C%E ...

  9. 《视觉SLAM进阶:从零开始手写VIO》第三讲 基于优化的IMU预积分与视觉信息融合 作业

    <视觉SLAM进阶:从零开始手写VIO>第三讲 基于优化的IMU预积分与视觉信息融合 作业 文章目录 <视觉SLAM进阶:从零开始手写VIO>第三讲 基于优化的IMU预积分与视 ...

最新文章

  1. 李彦宏:从没觉得百度模仿谷歌;马化腾:做ICO数字货币有很多风险
  2. 机器人会模仿人类微笑了,但我总觉得这笑容……
  3. python基础算法-归并排序
  4. 微信小程序设置文本左对齐居中对齐右对齐setTextAlign的使用说明
  5. 经典算法面试题目-置矩阵行列元素为0(1.7)
  6. Robot Cleaner I
  7. .NET程序崩溃了怎么抓 Dump ? 我总结了三种方案
  8. 【Azure Show】|第七期 特别版线上沙龙直播回顾. 嘉宾张坤段清华谭国欣柯克黄炜锵...
  9. 使用 IntraWeb (10) - CSS
  10. js文件/图片从电脑里面拖拽到浏览器上传文件/图片
  11. 第十一课 Solidity语言编辑器REMIX指导大全
  12. C# 事务的创建,提交和回滚
  13. 一个厂商网站的SQL安全检测 (啊D、明小子)
  14. java 代码箭头代表什么_箭头运算符' - '在Java中做什么?
  15. 什么是网络监控?OpManager 网络监控解决方案
  16. Mac OS X 键盘快捷键
  17. 曾仕强经典语录-《易经的奥秘》
  18. 多张JPG如何合并成一个PDF文档
  19. 嵌入式中的BSP---BSP到底是什么?
  20. readb(), readw(), readl(),writeb(), writew(), writel() 宏函数

热门文章

  1. 2017711010137 赵栋 《面向对象程序设计》第四章学习总结
  2. 201771010137 赵栋 《面向对象程序设计(java)》第十三周学习总结
  3. 本土程序员杀进硅谷的第一步---突破英语瓶颈
  4. 最简单的讲解:梯度下降法
  5. 【python】算法设计:回文素数
  6. 【JavaWeb】AJAX
  7. open falcon mysql参数_open-falcon 监控MySQL及自定义监控指标
  8. MySQL数据库企业级开发技术
  9. SpringBoot企业级开发
  10. 0x80073712_win10系统-更新失败提示“0x80073712”如何解决?