视觉slam十四讲 6.非线性优化

目录

  • 1.状态估计问题
    • 1.2 最小二乘的引出
  • 2.非线性最小二乘
    • 2.1 一阶和二阶梯度法
    • 2.2 高斯牛顿法
    • 2.3 列文伯格-马夸尔特方法(阻尼牛顿法)
  • 3.实践:Ceres
    • 3.1 Ceres简介
      • 3.2 使用Ceres拟合曲线
  • 4.实践:g2o
    • 4.1 图优化理论简介
    • 4.2 使用g2o拟合曲线

公式编辑:https://codecogs.com/latex/eqneditor.php

1.状态估计问题

1.1 最大后验与最大似然

  1. 经典 SLAM 模型:


    在这里插入图片描述

    其中 x k x_{k} xk为相机的位姿, u u u 为输入数据,即为采集到的数据
    假如我们在 x k x_{k} xk处观测到路标 y j y_{j} yj,对应到图像上的像素位置 z k , j z_{k,j} zk,j,那么我们的观测方程可以表示为:
    s z k , j = K e x p ( ξ ∧ ) y i sz_{k,j}=Kexp\left ( \xi ^{\wedge } \right )y_{i} szk,j=Kexp(ξ)yi

  2. 在运动和观测方程中,通常假设两个噪声项 w k w_{k} wk v k v_{k} vk满足零均值的高斯分布:
    w k   N ( 0 , R k ) , v k   N ( 0 , Q k , j ) w_{k}~N\left ( 0,R_{k} \right ),v_{k}~N\left ( 0,Q_{k,j} \right ) wk N(0,Rk),vk N(0,Qk,j)

  3. 状态估计问题:通过带噪声的数据 z z z u u u ,推断位姿 x x x 和地图 y y y(以及它们的概率分布),其中有优化方法有:
    (1)扩展卡尔曼滤波器(EKF)求解,关心的是当前时刻的状态估计,而对直之前的状态没有多加考虑。
    (2)非线性优化,使用所有时刻采集到的数据进行状态估计,被认为优于传统滤波器,成为当前视觉slam的主流。
    非线性优化把所待估计的变量都放在一个状态变量中:
    x = { x 1 , . . . , x N , y 1 , . . . , y M } x=\left \{ x_{1},...,x_{N},y_{1},...,y_{M} \right \} x={x1,...,xN,y1,...,yM}
    已知:输入读数 u u u,观测值 z z z。(带噪声)
    目标: P ( x ∣ z , u ) P\left ( x|z,u \right ) P(xz,u),只考虑 z z z时: P ( x ∣ z ) P\left ( x|z \right ) P(xz)

  4. 贝叶斯法则:
    P ( x ∣ z ) = P ( z ∣ x ) P ( x ) P ( z ) → P ( z ∣ x ) P ( x ) {\color{Red} P\left ( x|z \right )=\frac{P\left ( z|x \right )P\left ( x \right )}{P\left ( z \right )} \rightarrow {\color{Red} } P\left ( z|x \right )P\left ( x \right )} P(xz)=P(z)P(zx)P(x)P(zx)P(x)

  5. 贝叶斯法则左侧通常称为后验概率。它右侧的 P ( z ∣ x ) P\left ( z|x \right ) P(zx)称为似然,另一部分 P ( x ) P\left ( x\right ) P(x)称为先验.

  6. 直接求后验分布较困难,转而求一个状态最优估计,使得在该状态下,后验概率最大化(MPA):
    x ∗ M A P = a r g m a x P ( x ∣ z ) = a r g m a x P ( z ) x^{*}MAP=argmax P\left ( x|z \right )=arg maxP\left ( z \right ) xMAP=argmaxP(xz)=argmaxP(z)

    若不知道机器人位姿大概在什么地方,此时就没有了先验。进而可以求解 x x x的最大似然估计:
    x M L E ∗ = a r g m a x P ( z ∣ x ) x^{*}_{MLE}=argmaxP\left ( z|x \right ) xMLE=argmaxP(zx)
    最大似然估计,可以理解成:“在什么样的状态下,最可能产生现在观测到的数据

1.2 最小二乘的引出

  1. 对于某一次观测:
    z k , j = h ( y j , x k ) + v k , j z_{k,j}=h\left ( y_{j} ,x_{k}\right )+v_{k,j} zk,j=h(yj,xk)+vk,j

    假设噪声项 v k v_{k} vk N ( 0 , Q k , j ) N\left ( 0,Q_{k,j} \right ) N(0,Qk,j),所以观测数据的条件概率为:
    P ( z k , j ∣ x k , y j ) = N ( h ( y i , x k ) , Q k , j ) P\left ( z_{k,j}|x_{k} ,y_{j}\right )=N\left ( h\left ( y_{i},x_{k} \right ) ,Q_{k,j}\right ) P(zk,jxk,yj)=N(h(yi,xk),Qk,j)

  2. 为了计算使它最大化的 x k , y k x_{k},y_{k} xk,yk,使用最小化负对数的方式,来一个高斯分布的最大似然。考虑一个任意的高维高斯分布 v k v_{k} vk N ( μ , Σ ) N\left ( μ,Σ\right ) N(μ,Σ) ,它的概率密度函数展开形式为:
    P ( x ) = 1 ( 2 π ) N d e t ( ∑ ) e x p ( − 1 2 ( x − μ ) T Σ − 1 ( x − μ ) ) P\left ( x \right )=\frac{1}{\sqrt{\left ( 2\pi \right )^Ndet(\sum )}}exp(-\frac{1}{2}\left ( x-\mu \right )^T \Sigma ^{-1}\left ( x-\mu \right )) P(x)=(2π)Ndet() 1exp(21(xμ)TΣ1(xμ))
    取负对数:
    − ln ⁡ ( P ( x ) ) = 1 2 ln ⁡ ( ( 2 π ) N d e t ( Σ ) ) + 1 2 ( x − μ ) T Σ − 1 ( x − μ ) -\ln \left ( P\left ( x \right ) \right )=\frac{1}{2}\ln \left ( \left ( 2\pi \right ) ^N det\left ( \Sigma \right )\right )+\frac{1}{2}\left ( x-\mu \right )^T\Sigma ^{-1}\left ( x-\mu \right ) ln(P(x))=21ln((2π)Ndet(Σ))+21(xμ)TΣ1(xμ)

  3. 第一项与 x x x 无关,可以略去。代入 SLAM 的观测模型,相当于在求:
    x ∗ = a r g m i n ( ( z k , j − h ( x k , y j ) ) T Q k , j − 1 ( z k , j − h ( x k , y j ) ) ) x^{*}=arg min\left ( (z_{k,j}-h(x_{k},y_{j})) ^TQ_{k,j}^{-1}(z_{k,j}-h(x_{k},y_{j}))\right ) x=argmin((zk,jh(xk,yj))TQk,j1(zk,jh(xk,yj))) 该式等价于最小化噪声项(即误差)的平方( Σ Σ Σ 范数意义下)

  4. 对于所有的运动和任意的观测,定义数据与估计值之间的误差: e v , k = x k − f ( x k − 1 , u k ) e v , j , k = z k , j − f ( x k , y ) e_{v,k}=x_{k}-f\left ( x_{k-1} ,u_{k}\right )\\ e_{v,j,k}=z_{k,j}-f\left ( x_{k} ,y_{}\right ) ev,k=xkf(xk1,uk)ev,j,k=zk,jf(xk,y)

    并求该误差的平方之和:
    J ( x ) = ∑ k e T v , k R k − 1 e v , k + ∑ k ∑ j e y , k , j T Q k , j − 1 e y , k , j J(x)=\sum _{k}e_{T}^{v,k}R_{k}^{-1}e_{v,k}+\sum _{k}\sum _{j}e_{y,k,j}^{T}Q_{k,j}^{-1}e_{y,k,j} J(x)=keTv,kRk1ev,k+kjey,k,jTQk,j1ey,k,j
    这样就得到一个总体意义下的最小二乘问题,它的最优解等价于状态的最大似然估计。由于噪声的存在,当我们把估计的轨迹与地图代入SLAM的运动、观测方程时,并不会很完美,可以对状态进行微调,使得整体的误差下降到一个极小值。

  5. SLAM中的最小二乘问题具有一些特定的结构:
    (1)整个问题的目标函数由许多个误差的(加权的)平方和组成。虽然总体的状态变量维数很高,但每个误差项都是简单的,仅与一俩个状态变量有关,运动误差只与 x k − 1 , x k x_{k-1},x_{k} xk1,xk有关,观测误差只与 x k , y j x_{k},y_{j} xk,yj有关,每个误差是个小规模约束,把这误差项的小雅克比矩阵放在整体的雅克比矩阵中。称每个误差项对应优化变量为参数快
    整体误差由很多小误差项之和组成的问题,其增量方程具有一定的稀疏性
    (2)如果使用李代数表示,那么该问题转换成**无约束最小二乘问题*,用旋转矩阵描述姿态会引入自身的约束
    (3)使用二范数度量误差,相当于欧式空间中距离的平方。

2.非线性最小二乘

最简单的最小二乘问题:
min ⁡ x 1 2 ∥ f ( x ) ∥ 2 2 \min_{x}\frac{1}{2}\left \| f(x) \right \|_{2}^{2} xmin21f(x)22
为求其最小值,则需要求其导数,然后得到其求解 x的最优解
对于不方便求解的最小二乘问题,可以用迭代的方法,从初始值出发,不断的跟新当前的优化变量,使目标函数下降,具体步骤有:
(1)给定某个初始值。
(2)对于第k次迭代,寻找增量Δxk, 使得 ∥ f ( x k + Δ x k ) ∥ 2 2 \left \| f(x_{k}+\Delta x_{k}) \right \|_{2}^{2} f(xk+Δxk)22这里应该是二范数)达到最小。
(3)若 Δ x k \Delta x_{k} Δxk足够小,则停止。
(4)否则,令 x k + 1 = x k + Δ x k x_{k+1}=x_{k}+\Delta x_{k} xk+1=xk+Δxk,返回第2步。

2.1 一阶和二阶梯度法

  1. 将目标函数在 x x x 附近进行泰勒展开:
    ∥ f ( x + Δ x ) ∥ 2 2 ≈ ∥ f ( x ) ∥ 2 2 + J ( x ) Δ x + 1 2 Δ x T H Δ x \left \| f(x+\Delta x) \right \|_{2}^{2}\approx \left \| f(x) \right \|_{2}^{2}+J(x)\Delta x+\frac{1}{2}\Delta x^TH\Delta x f(x+Δx)22f(x)22+J(x)Δx+21ΔxTHΔx
    J J J ∥ f ( x ) ∥ 2 2 \left \| f(x) \right \|_{2}^{2} f(x)22关于 x x x的导数(雅克比矩阵), H H H是二阶导数(海塞矩阵)
  2. 最速梯度下降法:只保留一阶梯度,增量的方向为:
    Δ x ∗ = − J T ( x ) \Delta x^{*}=-J^T(x) Δx=JT(x)
  3. 牛顿法:保留二阶梯度,增量方程为:
    Δ x ∗ = a r g m i n ∥ f ( x ) ∥ 2 2 + J ( x ) Δ x + 1 2 x T H Δ x \Delta x^{*}=arg min\left \| f(x) \right \|_{2}^{2}+J(x)\Delta x+\frac{1}{2}x^TH\Delta x Δx=argminf(x)22+J(x)Δx+21xTHΔx
    求右侧等式关于 ∆x的导数并令它为零,得到了增量的解:
    H Δ X = − J T H\Delta X=-J^T HΔX=JT
  4. 两种方法的问题:
    (1)最速下降法过于贪心,容易走出锯齿路线,反而增加了迭代次数。
    (2)牛顿法则需要计算目标函数的 H矩阵,这在问题规模较大时非常困难,通常倾向于避免 H H H 的计算。

2.2 高斯牛顿法

  1. 高斯牛顿法,它的思想是将 f ( x ) f\left (x \right ) f(x)进行泰勒展开(目标函数不是 f ( x ) f\left (x \right ) f(x)):
    f ( x + Δ x ) ≈ f ( x ) + J ( x ) Δ x f(x+\Delta x)\approx f(x)+J(x)\Delta x f(x+Δx)f(x)+J(x)Δx
    f ( J ) f\left (J \right ) f(J)是一个 m ∗ n m*n mn雅克比矩阵,当前的目标是为了寻找下降矢量 ∆x,使得 ∥ f ( x + Δ x ) 2 ∥ \left \| f\left ( x+\Delta x \right )^2 \right \| f(x+Δx)2达到最小。

  2. 为了求 Δ x k \Delta x_{k} Δxk,需要解一个线性的最小二乘问题
    Δ x ∗ = a r g min ⁡ Δ x 1 2 ∥ f ( x ) + J ( x ) Δ x ∥ 2 \Delta x^*=arg \min_{\Delta x}\frac{1}{2}\left \| f(x)+J(x) \Delta x\right \|^{2} Δx=argΔxmin21f(x)+J(x)Δx2
    考虑的是 Δ x k \Delta x_{k} Δxk的导数(而不是 x x x) ,先展开目标函数的平方项
    1 2 ∥ f ( x ) + J ( x ) Δ x ∥ 2 = 1 2 ( f ( x ) + J ( x ) Δ x ) T ( f ( x ) + J ( x ) Δ x ) = 1 2 ( ∥ f ( x ) ∥ ) 2 2 + 2 f ( x ) T J ( x ) Δ x + Δ x J ( x ) T J ( x ) Δ x ) \frac{1}{2}\left \| f(x)+J(x) \Delta x\right \|^{2}=\frac{1}{2}(f(x)+J(x)\Delta x)^T(f(x)+J(x)\Delta x)\\ =\frac{1}{2}(\left \| f(x) \right \|)_{2}^{2}+2f(x)^TJ(x)\Delta x+\Delta xJ(x)^TJ(x)\Delta x) 21f(x)+J(x)Δx2=21(f(x)+J(x)Δx)T(f(x)+J(x)Δx)=21(f(x))22+2f(x)TJ(x)Δx+ΔxJ(x)TJ(x)Δx)

    上式关于 Δ x \Delta x Δx的导数,并令其为零:
    J ( x ) T J ( x ) Δ x = − J ( x ) T f ( x ) J(x)^TJ(x)\Delta x=-J(x)^Tf(x) J(x)TJ(x)Δx=J(x)Tf(x)

    这个方程称之为增量方程,也称之为高斯牛顿方程正规方程,将左边的系数设为 H H H,右边的系数设为 g g g,则有 H Δ x = g HΔx=g HΔx=g

  3. 高斯牛顿法求解的算法步骤可写成:
    (1)给定初始值 x 0 x_{0} x0
    (2)对于第 k k k次迭代,求出当前的雅克比矩阵 J ( x k ) J\left ( x_{k} \right ) J(xk) 和误差 f ( x k ) f\left ( x_{k} \right ) f(xk)
    (3)求解增量方程: H Δ x k = g H\Delta x_{k}=g HΔxk=g
    (4)若 Δ x k \Delta x_{k} Δxk足够小,则停止。否则,令 x k + 1 = x k + Δ x k x_{k+1}=x_{k}+\Delta x_{k} xk+1=xk+Δxk,返回第二步

  4. 高斯牛顿法的缺点:
    (1)要求 H H H是可逆的,而且是正定的,如果出现 H H H矩阵奇异或者病态,此时增量的稳定性较差,导致算法不收敛
    (2)步长问题,若求出来的 Δ x k \Delta x_{k} Δxk太长,则可能出现局部近似不够准确,无法保证迭代收敛。

2.3 列文伯格-马夸尔特方法(阻尼牛顿法)

  1. 信赖区域方法 (Trust Region Method):给 Δ x \Delta x Δx添加一个信赖区域(Trust Region),不能让它太大而使得近似不准确

  2. 考虑使用如下公式来判断泰勒近似是否够好
    ρ = f ( x + Δ x ) − Δ x J ( x ) Δ x \rho =\frac{f(x+\Delta x)-\Delta x}{J(x)\Delta x} ρ=J(x)Δxf(x+Δx)Δx
    (1)如果 ρ ρ ρ 接近于 1,则近似是好的。
    (2)如果 ρ ρ ρ太小,说明实际减小的值远少于近似减小的值,则认为近似比较差,需要缩小近似范围。
    (3)如果 ρ ρ ρ 比较大,则说明实际下降的比预计的更大,我们可以放大近似范围。

  3. 改良版的非线性优化框架
    视觉slam十四讲 6.非线性优化_第1张图片
    这里近似范围扩大的倍数和阈值都是经验值,可以替换成其他数值。上述约束中相当于把增量限定在半径为 μ \mu μ的球里面,认为在球内的才有效。带上 D D D后成为椭圆,把 D D D取成单位阵 I I I,相当于把 Δ x \Delta x Δx约束在一个球内。 D D D也可为非负数对角阵。

  4. 由于是有约束优化,可以利用拉格朗日乘子将其转化为一个无约束优化问题:
    min ⁡ Δ x k 1 2 ∥ f ( x k + J ( x k Δ x k ) ) ∥ 2 + λ 2 ∥ D Δ x ∥ 2 \min_{\Delta x_{k}}\frac{1}{2}\left \| f(x_{k}+J(x_{k}\Delta x_{k})) \right \|^2+\frac{\lambda }{2}\left \| D\Delta x \right \|^2 Δxkmin21f(xk+J(xkΔxk))2+2λDΔx2

    类似高斯牛顿法展开,可得其增量的线性方程:
    ( H + λ D T D ) Δ X = g (H+\lambda D^TD)\Delta X=g (H+λDTD)ΔX=g

    考虑它的简化形式,即 D = I D=I D=I,那么相当于求解:
    ( H + λ I ) Δ x = g (H+\lambda I)\Delta x=g (H+λI)Δx=g

(1)当参数 λ λ λ较小时, H H H占主导地位,说明二次近似模型在该范围内是比较好的,该方法接近于高斯牛顿法。
(2)当参数 λ λ λ较大时,列文伯格-马夸尔特方法接近于一阶梯度下降法,说明二次近似不好。
(3)该方法可在一定程度上避免线性方程组的系数矩阵的非奇异和病态问题),提供更稳定、更准确的增量 Δ x Δx Δx

  1. 非线性优化的框架分为Line Search和Trust Region两类。
    (1)Line Search:先固定搜索方向,后寻找步长,以最速梯度法和高斯牛顿法为代表。
    (2)Trust Region:先固定搜索区域,再考虑找该区域的最优点,以列文伯格-马夸尔特方法为代表

3.实践:Ceres

3.1 Ceres简介

Ceres库面向通用的最小二乘问题的求解,需要定义优化问题,设置一些选项,输入Ceres求解。一般形式如下(带边界核函数最小二乘):

min ⁡ x 1 2 ∑ i ( ∥ ( f i ( x i 1 , . . . x i n ) ) ∥ 2 ) s . t l j ≤ x j ≤ μ j \min_{x} \frac{1}{2}\sum _{i}\left ( \left \| (f_{i}(x_{i1},...x_{in})) \right \|^2 \right )\\ s.t \qquad \qquad l_{j}\leq x_{j}\leq \mu _{j} xmin21i((fi(xi1,...xin))2)s.tljxjμj
优化变量: x i 1 , . . . x i n x_{i1},...x_{in} xi1,...xin ,代价函数 f i f_{i} fi , ρ \rho ρ为恒等函数时,得到一个无约束的最小二乘问题。

3.2 使用Ceres拟合曲线

曲线方程的形式为:
y = e x p ( a x 2 + b x + c ) + w y=exp(ax^2+bx+c)+w y=exp(ax2+bx+c)+w
a , b , c 为 参 数 , a,b,c为参数, a,b,cw 为 噪 声 , 假 设 我 们 有 N 个 关 于 为噪声,假设我们有N个关于 Nx,y$观测数据点,根据这些点求曲线的参数,可以求解下面的最小二乘来狙击曲线参数。
min ⁡ a , b , c 1 2 ∑ i = 1 N ∥ y i − e x p ( a x i 2 + b x i + c ) ∥ 2 \min_{a,b,c}\frac{1}{2}\sum _{i=1}^{N}\left \| y_{i}-exp(ax_{i}^2+bx_{i}+c)\right \|^2 a,b,cmin21i=1Nyiexp(axi2+bxi+c)2

参考:
Ceres官网:http://www.ceres-solver.org/
一文助你Ceres 入门——Ceres Solver新手向全攻略:https://blog.csdn.net/cqrtxwd/article/details/78956227
Ceres: Tutorial: Non-linear Least Squares:http://www.ceres-solver.org/nnls_tutorial.html
基于范围的for循环:https://blog.csdn.net/lixiaogang_theanswer/article/details/79969012

#include 
#include 
#include 
#include 

using namespace std;

// 代价函数的计算模型
//首先要定义求解问题的cost function
struct CURVE_FITTING_COST
{
    //在结构体中首先构造构造函
    CURVE_FITTING_COST ( double x, double y ) : _x ( x ), _y ( y ) {}////结构体的构造函数初始化列表
    //相当于CURVE_FITTING_COST ( double x,double y) {_x=x; _y=y}
    // 残差的计算
    //template的使用是为了简化不同类型的函数和类的重复定义,模版实例化时可以替换任意类型,不仅包括内置类型(int等),也包括自定义类型class,实例化之后才知道的数据的类型。
    template <typename T>
    bool operator() (
        const T* const abc,     // 模型参数,有3维
        T* residual ) const     // 残差
    {
        residual[0] = T ( _y ) - ceres::exp ( abc[0]*T ( _x ) *T ( _x ) + abc[1]*T ( _x ) + abc[2] ); // y-exp(ax^2+bx+c)
        return true;
    }
    const double _x, _y;    // x,y数据
};

int main ( int argc, char** argv )
{
    double a=1.0, b=2.0, c=1.0;         // 真实参数值
    int N=100;                          // 数据点
    double w_sigma=1.0;                 // 噪声Sigma值
    cv::RNG rng;                        // OpenCV随机数产生器
    double abc[3] = {0,0,0};            // abc参数的估计值

    vector<double> x_data, y_data;      // 数据

    cout<<"generating data: "<<endl;
    for ( int i=0; i<N; i++ )
    {
        double x = i/100.0;
        x_data.push_back ( x );
        y_data.push_back (
            exp ( a*x*x + b*x + c ) + rng.gaussian ( w_sigma )
        );
        cout<<x_data[i]<<" "<<y_data[i]<<endl;
    }

    // 构建最小二乘问题
    ceres::Problem problem;
    for ( int i=0; i<N; i++ )
    {
        problem.AddResidualBlock (     // 向问题中添加误差项
        // 使用自动求导,模板参数:误差类型,输出维度,输入维度,维数要与前面struct中一致
            new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3> ( 
                new CURVE_FITTING_COST ( x_data[i], y_data[i] )
            ),
            nullptr,            // 核函数,这里不使用,为空
            abc                 // 待估计参数
        );
    }

    // 配置求解器
    ceres::Solver::Options options;     // 这里有很多配置项可以填
    options.linear_solver_type = ceres::DENSE_QR;  // 增量方程如何求解
    options.minimizer_progress_to_stdout = true;   // 输出到cout

    ceres::Solver::Summary summary;                // 优化信息
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    ceres::Solve ( options, &problem, &summary );  // 开始优化
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>( t2-t1 );
    cout<<"solve time cost = "<<time_used.count()<<" seconds. "<<endl;

    // 输出结果
    cout<<summary.BriefReport() <<endl;
    cout<<"estimated a,b,c = ";
    //表示的则是基于范围的for循环
    for ( auto a:abc ) cout<<a<<" ";
    cout<<endl;

    return 0;
}

代码中需要关注的有这么几点:

(1)定义代价函数的方式是自定义一个结构体。只需要在结构体中实现operator()方法,就算是给Ceres提供了一个调用接口,由Ceres内部在合适的时候调用该方法计算代价函数。
(2)构建最小二乘问题时,需要将所有误差项依次添加进去。而每个误差项又是由前面定义的结构体构成的。需要注意的是,每个误差项需要指定代价函数求导的方法,有三种方法可供选择:自动求导、数值求导和自行推导。一般采用自动求导,既方便,又节约运行时时间。
(3)以自动求导为例,ceres::AutoDiffCostFunction是个模板类,后两个模板参数为输出维度和输入维度,必须与前面定义的结构体中的residual和abc的维度一样。
(4)使用ceres::Solver::Options配置求解器。这个类有许多字段,每个字段都提供了一些枚举值供用户选择。所以需要时只要查一查文档就知道怎么设置了。

调用build/curve_fitting查看优化结果:

generating data: 
0 2.71828
0.01 2.93161
0.02 2.12942
0.03 2.46037
0.04 4.18814
0.05 2.73368
0.06 2.42751
……
0.94 44.285
0.95 42.8312
0.96 47.7941
0.97 48.5931
0.98 51.8487
0.99 51.0258
iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  1.824887e+04    0.00e+00    1.38e+03   0.00e+00   0.00e+00  1.00e+04        0    3.88e-05    9.01e-05
   1  2.748700e+39   -2.75e+39    0.00e+00   7.67e+01  -1.52e+35  5.00e+03        1    6.85e-05    1.82e-04
   2  2.429783e+39   -2.43e+39    0.00e+00   7.62e+01  -1.35e+35  1.25e+03        1    2.18e-05    2.16e-04
   3  1.213227e+39   -1.21e+39    0.00e+00   7.30e+01  -6.73e+34  1.56e+02        1    1.87e-05    2.44e-04
   4  1.852387e+37   -1.85e+37    0.00e+00   5.56e+01  -1.03e+33  9.77e+00        1    1.80e-05    2.69e-04
   5  6.714689e+31   -6.71e+31    0.00e+00   2.96e+01  -3.85e+27  3.05e-01        1    1.82e-05    2.95e-04
   6  9.500531e+12   -9.50e+12    0.00e+00   9.50e+00  -8.39e+08  4.77e-03        1    1.78e-05    3.20e-04
   7  1.776982e+04    4.79e+02    1.83e+03   2.58e-01   1.18e+00  1.43e-02        1    4.06e-05    3.68e-04
   8  1.599969e+04    1.77e+03    3.45e+03   5.53e-01   1.46e+00  4.29e-02        1    3.86e-05    4.15e-04
   9  1.060557e+04    5.39e+03    7.62e+03   7.33e-01   1.68e+00  1.29e-01        1    3.92e-05    4.62e-04
  10  3.669783e+03    6.94e+03    9.60e+03   5.25e-01   1.39e+00  3.86e-01        1    3.76e-05    5.06e-04
  11  5.397541e+02    3.13e+03    5.00e+03   2.66e-01   1.12e+00  1.16e+00        1    3.74e-05    5.51e-04
  12  1.484444e+02    3.91e+02    1.22e+03   8.46e-02   1.02e+00  3.48e+00        1    3.74e-05    5.95e-04
  13  1.216815e+02    2.68e+01    3.76e+02   4.17e-02   1.01e+00  1.04e+01        1    3.73e-05    6.40e-04
  14  9.290109e+01    2.88e+01    2.42e+02   9.10e-02   1.01e+00  3.13e+01        1    3.86e-05    6.85e-04
  15  6.674330e+01    2.62e+01    1.09e+02   1.33e-01   1.00e+00  9.39e+01        1    3.73e-05    7.30e-04
  16  5.936574e+01    7.38e+00    2.14e+01   1.08e-01   9.94e-01  2.82e+02        1    3.72e-05    7.75e-04
  17  5.653118e+01    2.83e+00    1.36e+01   1.57e-01   9.98e-01  8.45e+02        1    4.25e-05    8.26e-04
  18  5.310764e+01    3.42e+00    8.50e+00   2.81e-01   9.89e-01  2.53e+03        1    3.74e-05    8.71e-04
  19  5.125939e+01    1.85e+00    2.84e+00   2.98e-01   9.90e-01  7.60e+03        1    3.77e-05    9.16e-04
  20  5.097693e+01    2.82e-01    4.34e-01   1.48e-01   9.95e-01  2.28e+04        1    3.72e-05    9.60e-04
  21  5.096854e+01    8.39e-03    3.24e-02   2.87e-02   9.96e-01  6.84e+04        1    3.69e-05    1.00e-03
solve time cost = 0.00104852 seconds. 
Ceres Solver Report: Iterations: 22, Initial cost: 1.824887e+04, Final cost: 5.096854e+01, Termination: CONVERGENCE
estimated a,b,c = 0.891943 2.17039 0.944142 

4.实践:g2o

4.1 图优化理论简介

图优化,是把优化问题表现成图的一种形式,顶点表示优化变量边表示误差项,对于任意一个形式的非最小二乘问题,可以构建与之对应的一个图。

4.2 使用g2o拟合曲线

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std; 

// 曲线模型的顶点,模板参数:优化变量维度和数据类型
class CurveFittingVertex: public g2o::BaseVertex<3, Eigen::Vector3d>
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    virtual void setToOriginImpl() // 重置
    {
        _estimate << 0,0,0;
    }
    
    virtual void oplusImpl( const double* update ) // 更新
    {
        _estimate += Eigen::Vector3d(update);
    }
    // 存盘和读盘:留空
    virtual bool read( istream& in ) {}
    virtual bool write( ostream& out ) const {}
};

// 误差模型 模板参数:观测值维度,类型,连接顶点类型
class CurveFittingEdge: public g2o::BaseUnaryEdge<1,double,CurveFittingVertex>
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    CurveFittingEdge( double x ): BaseUnaryEdge(), _x(x) {}
    // 计算曲线模型误差
    void computeError()
    {
        const CurveFittingVertex* v = static_cast<const CurveFittingVertex*> (_vertices[0]);
        const Eigen::Vector3d abc = v->estimate();
        _error(0,0) = _measurement - std::exp( abc(0,0)*_x*_x + abc(1,0)*_x + abc(2,0) ) ;
    }
    virtual bool read( istream& in ) {}
    virtual bool write( ostream& out ) const {}
public:
    double _x;  // x 值, y 值为 _measurement
};

int main( int argc, char** argv )
{
    double a=1.0, b=2.0, c=1.0;         // 真实参数值
    int N=100;                          // 数据点
    double w_sigma=1.0;                 // 噪声Sigma值
    cv::RNG rng;                        // OpenCV随机数产生器
    double abc[3] = {0,0,0};            // abc参数的估计值

    vector<double> x_data, y_data;      // 数据
    
    cout<<"generating data: "<<endl;
    for ( int i=0; i<N; i++ )
    {
        double x = i/100.0;
        x_data.push_back ( x );
        y_data.push_back (
            exp ( a*x*x + b*x + c ) + rng.gaussian ( w_sigma )
        );
        cout<<x_data[i]<<" "<<y_data[i]<<endl;
    }
    
    // 构建图优化,先设定g2o
    typedef g2o::BlockSolver< g2o::BlockSolverTraits<3,1> > Block;  // 每个误差项优化变量维度为3,误差值维度为1
    Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>(); // 线性方程求解器
    Block* solver_ptr = new Block( linearSolver );      // 矩阵块求解器
    // 梯度下降方法,从GN, LM, DogLeg 中选
    g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg( solver_ptr );
    // g2o::OptimizationAlgorithmGaussNewton* solver = new g2o::OptimizationAlgorithmGaussNewton( solver_ptr );
    // g2o::OptimizationAlgorithmDogleg* solver = new g2o::OptimizationAlgorithmDogleg( solver_ptr );
    g2o::SparseOptimizer optimizer;     // 图模型
    optimizer.setAlgorithm( solver );   // 设置求解器
    optimizer.setVerbose( true );       // 打开调试输出
    
    // 往图中增加顶点
    CurveFittingVertex* v = new CurveFittingVertex();
    v->setEstimate( Eigen::Vector3d(0,0,0) );
    v->setId(0);
    optimizer.addVertex( v );
    
    // 往图中增加边
    for ( int i=0; i<N; i++ )
    {
        CurveFittingEdge* edge = new CurveFittingEdge( x_data[i] );
        edge->setId(i);
        edge->setVertex( 0, v );                // 设置连接的顶点
        edge->setMeasurement( y_data[i] );      // 观测数值
        edge->setInformation( Eigen::Matrix<double,1,1>::Identity()*1/(w_sigma*w_sigma) ); // 信息矩阵:协方差矩阵之逆
        optimizer.addEdge( edge );
    }
    
    // 执行优化
    cout<<"start optimization"<<endl;
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    optimizer.initializeOptimization();
    optimizer.optimize(100);
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>( t2-t1 );
    cout<<"solve time cost = "<<time_used.count()<<" seconds. "<<endl;
    
    // 输出优化值
    Eigen::Vector3d abc_estimate = v->estimate();
    cout<<"estimated model: "<<abc_estimate.transpose()<<endl;
    
    return 0;
}

优化结果:

generating data: 
0 2.71828
0.01 2.93161
0.02 2.12942
0.03 2.46037
……
0.96 47.7941
0.97 48.5931
0.98 51.8487
0.99 51.0258
start optimization
iteration= 0	 chi2= 30373.727656	 time= 9.4326e-05	 cumTime= 9.4326e-05	 edges= 100	 schur= 0	 lambda= 699.050482	 levenbergIter= 7
iteration= 1	 chi2= 13336.948288	 time= 5.0567e-05	 cumTime= 0.000144893	 edges= 100	 schur= 0	 lambda= 1864.134619	 levenbergIter= 3
iteration= 2	 chi2= 6946.262996	 time= 4.1451e-05	 cumTime= 0.000186344	 edges= 100	 schur= 0	 lambda= 1242.756412	 levenbergIter= 1
iteration= 3	 chi2= 271.023166	 time= 4.0473e-05	 cumTime= 0.000226817	 edges= 100	 schur= 0	 lambda= 414.252137	 levenbergIter= 1
iteration= 4	 chi2= 118.903887	 time= 4.2708e-05	 cumTime= 0.000269525	 edges= 100	 schur= 0	 lambda= 138.084046	 levenbergIter= 1
iteration= 5	 chi2= 113.568660	 time= 4.0511e-05	 cumTime= 0.000310036	 edges= 100	 schur= 0	 lambda= 46.028015	 levenbergIter= 1
iteration= 6	 chi2= 107.476457	 time= 4.0557e-05	 cumTime= 0.000350593	 edges= 100	 schur= 0	 lambda= 15.342672	 levenbergIter= 1
iteration= 7	 chi2= 103.014522	 time= 4.0922e-05	 cumTime= 0.000391515	 edges= 100	 schur= 0	 lambda= 5.114224	 levenbergIter= 1
iteration= 8	 chi2= 101.988348	 time= 4.0378e-05	 cumTime= 0.000431893	 edges= 100	 schur= 0	 lambda= 1.704741	 levenbergIter= 1
iteration= 9	 chi2= 101.937388	 time= 4.0728e-05	 cumTime= 0.000472621	 edges= 100	 schur= 0	 lambda= 0.568247	 levenbergIter= 1
iteration= 10	 chi2= 101.937021	 time= 4.226e-05	 cumTime= 0.000514881	 edges= 100	 schur= 0	 lambda= 0.378831	 levenbergIter= 1
iteration= 11	 chi2= 101.937020	 time= 4.0703e-05	 cumTime= 0.000555584	 edges= 100	 schur= 0	 lambda= 0.252554	 levenbergIter= 1
iteration= 12	 chi2= 101.937020	 time= 4.9381e-05	 cumTime= 0.000604965	 edges= 100	 schur= 0	 lambda= 1.346956	 levenbergIter= 3
iteration= 13	 chi2= 101.937020	 time= 4.1188e-05	 cumTime= 0.000646153	 edges= 100	 schur= 0	 lambda= 0.897971	 levenbergIter= 1
iteration= 14	 chi2= 101.937020	 time= 5.331e-05	 cumTime= 0.000699463	 edges= 100	 schur= 0	 lambda= 38.313418	 levenbergIter= 4
iteration= 15	 chi2= 101.937020	 time= 6.0704e-05	 cumTime= 0.000760167	 edges= 100	 schur= 0	 lambda= 204.338228	 levenbergIter= 3
iteration= 16	 chi2= 101.937020	 time= 4.0342e-05	 cumTime= 0.000800509	 edges= 100	 schur= 0	 lambda= 136.225485	 levenbergIter= 1
iteration= 17	 chi2= 101.937020	 time= 7.0952e-05	 cumTime= 0.000871461	 edges= 100	 schur= 0	 lambda= 24378500205.440712	 levenbergIter= 8
iteration= 18	 chi2= 101.937020	 time= 4.9227e-05	 cumTime= 0.000920688	 edges= 100	 schur= 0	 lambda= 1560224013148.205566	 levenbergIter= 3
solve time cost = 0.00200526 seconds. 
estimated model: 0.890912   2.1719 0.943629

你可能感兴趣的:(SLAM,#,VSLAM)