非线性优化 (曲线拟合) 问题:高斯牛顿、g2o 方法总结

您所在的位置:网站首页 函数拟合方法有哪些 非线性优化 (曲线拟合) 问题:高斯牛顿、g2o 方法总结

非线性优化 (曲线拟合) 问题:高斯牛顿、g2o 方法总结

2024-06-30 04:45| 来源: 网络整理| 查看: 265

 其实还有一个Ceres库可以进行优化,但是之前的博客已经具体分析了,所以这里就对其余两个进行了介绍,相关的内容是SLAM14讲里面的知识   一、理论部分

我们先用一个简单的例子来说明如何求解最小二乘问题,然后展示如何手写高斯牛顿法和优化库求解此问题

高斯牛顿法

  g2o曲线拟合 g2o (General Graphic Optimization , G 2 O )。它是一个基于 图优化 的库。图优化是一种将非线性优化与图论结合起来的理论,因此在使用它之前,我们花一点篇幅介绍一个图优化理论。 图优化理论简介 我们已经介绍了非线性最小二乘的求解方式。它们是由很多个误差项之和组成的。然 而,仅有一组优化变量和许多个误差项,我们并不清楚它们之间的关联 。比方说,某一个 优化变量 x j 存在于多少个误差项里呢?我们能保证对它的优化是有意义的吗?进一步,我 们希望能够直观地看到该优化问题长什么样 。于是,就说到了图优化。 图优化,是把优化问题表现成图( Graph ) 的一种方式。这里的 图 是图论意义上的图。 一个图由若干个顶点( Vertex ) ,以及连接着这些节点的 边( Edge ) 组成。进而,用 顶点 表示优化变量 ,用 边 表示 误差项。于是,对任意一个上述形式的非线性最小二乘问题,我们可以构建与之对应的一个 图 。  

图 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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3