其实还有一个Ceres库可以进行优化,但是之前的博客已经具体分析了,所以这里就对其余两个进行了介绍,相关的内容是SLAM14讲里面的知识
一、理论部分
我们先用一个简单的例子来说明如何求解最小二乘问题,然后展示如何手写高斯牛顿法和优化库求解此问题
高斯牛顿法
![](https://img-blog.csdnimg.cn/20201027085023183.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hsdHQzODM4,size_16,color_FFFFFF,t_70)
g2o曲线拟合
g2o (General Graphic Optimization
,
G
2
O
)。它是一个基于
图优化
的库。图优化是一种将非线性优化与图论结合起来的理论,因此在使用它之前,我们花一点篇幅介绍一个图优化理论。
图优化理论简介 我们已经介绍了非线性最小二乘的求解方式。它们是由很多个误差项之和组成的。然 而,仅有一组优化变量和许多个误差项,我们并不清楚它们之间的关联
。比方说,某一个 优化变量 x
j
存在于多少个误差项里呢?我们能保证对它的优化是有意义的吗?进一步,我 们希望能够直观地看到该优化问题长什么样
。于是,就说到了图优化。 图优化,是把优化问题表现成图(
Graph
)
的一种方式。这里的
图
是图论意义上的图。 一个图由若干个顶点(
Vertex
)
,以及连接着这些节点的
边(
Edge
)
组成。进而,用
顶点 表示优化变量
,用
边
表示
误差项。于是,对任意一个上述形式的非线性最小二乘问题,我们可以构建与之对应的一个
图
。
![](https://img-blog.csdnimg.cn/20201027090916564.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hsdHQzODM4,size_16,color_FFFFFF,t_70)
图
6-2
是一个简单的图优化例子。我们用三角形表示相机位姿节点,用圆形表示路标 点,它们构成了图优化的顶点;同时,蓝色线表示相机的运动模型,红色虚线表示观测模 型,它们构成了图优化的边。此时,虽然整个问题的数学形式仍是式(6.12
)那样,但现在 我们可以直观地看到问题的结构
了。如果我们希望,也可以做
去掉孤立顶点
或
优先优化边 数较多(或按图论的术语,度数较大)的顶点这样的改进。但是最基本的图优化,是用图 模型来表达一个非线性最小二乘的优化问题。而我们可以利用图模型的某些性质,做更好 的优化。 g2o 为
SLAM
提供了图优化所需的内容。下面我们来演示一下
g2o
的使用方法。
g2o 的编译与安装
在使用一个库之前,我们需要对它进行编译和安装。读者应该已经体验很多次这个过 程了,它们基本都是大同小异的。关于 g2o
,读者可以从
github
下载它:
https://github. com/RainerKuemmerle/g2o,或从本书提供的第三方代码库中获得。
解压代码包后,你会看到
g2o
库的所有源码,它也是一个
CMake
工程。我们先来安 装它的依赖项(部分依赖项与 Ceres
有重合):
sudo apt-get install libqt4-dev qt4-qmake libqglviewer-dev libsuitesparse-dev libcxsparse3.1.2
libcholmod-dev
然后,按照
cmake
的方式对
g2o
进行编译安装即可,我们略去该过程的说明。安装完 成后,g2o
的头文件将在
/usr/local/g2o
下,库文件在
/usr/local/lib/
下。现在,我们重新 考虑 Ceres
例程中的曲线拟合实验,在
g2o
中实验一遍。
使用 g2o 拟合曲线
为了使用
g2o
,首先要做的是将曲线拟合问题抽象成图优化。这个过程中,只要记住节点为优化变量,边为误差项即可。因此,曲线拟合的图优化问题可以画成图
6-3
的形式。
在曲线拟合问题中,整个问题只有一个顶点:曲线模型的参数
a, b, c
;而每个带噪声的数据点,构成了一个个误差项,也就是图优化的边。但这里的边与我们平时想的边不太一样,它们是一元边
(
Unary Edge
),即
只连接一个顶点
——因为我们整个图只有一个顶点。 所以在图 6-3
中,我们就只能把它画成自己连到自己的样子了。事实上,图优化中一条边可以连接一个、两个或多个顶点,这主要反映在每个误差与多少个优化变量有关。在稍微有些玄妙的说法中,我们把它叫做超边
(
Hyper Edge
),整个图叫做
超图
(
Hyper Graph
) ①。弄清了这个图模型之后,接下来就是在 g2o
中建立该模型,进行优化了。作为
g2o
的 用户,我们要做的事主要有以下几个步骤:
1. 定义顶点和边的类型;
2. 构建图;
3. 选择优化算法;
4. 调用 g2o 进行优化,返回结果。
二、实践部分
2.1 手写高斯牛顿法
#include
#include
#include
#include
#include
using namespace std;
using namespace Eigen;
int main(int argc, char **argv) {
double ar = 1.0, br = 2.0, cr = 1.0; // 真实参数值
double ae = 2.0, be = -1.0, ce = 5.0; // 估计参数值
int N = 100; // 数据点
double w_sigma = 1.0; // 噪声Sigma值
double inv_sigma = 1.0 / w_sigma;
cv::RNG rng; // OpenCV随机数产生器
vector x_data, y_data; // 数据
for (int i = 0; i < N; i++) {
double x = i / 100.0;
x_data.push_back(x);
y_data.push_back(exp(ar * x * x + br * x + cr) + rng.gaussian(w_sigma * w_sigma));
}
// 开始Gauss-Newton迭代
int iterations = 100; // 迭代次数
double cost = 0, lastCost = 0; // 本次迭代的cost和上一次迭代的cost
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
for (int iter = 0; iter < iterations; iter++) {
Matrix3d H = Matrix3d::Zero(); // Hessian = J^T W^{-1} J in Gauss-Newton
Vector3d b = Vector3d::Zero(); // bias
cost = 0;
for (int i = 0; i < N; i++) {
double xi = x_data[i], yi = y_data[i]; // 第i个数据点
double error = yi - exp(ae * xi * xi + be * xi + ce);
Vector3d J; // 雅可比矩阵
J[0] = -xi * xi * exp(ae * xi * xi + be * xi + ce); // de/da
J[1] = -xi * exp(ae * xi * xi + be * xi + ce); // de/db
J[2] = -exp(ae * xi * xi + be * xi + ce); // de/dc
H += inv_sigma * inv_sigma * J * J.transpose();
b += -inv_sigma * inv_sigma * error * J;
cost += error * error;
}
// 求解线性方程 Hx=b
Vector3d dx = H.ldlt().solve(b);
if (isnan(dx[0])) {
cout = lastCost) {
cout |