视觉SLAM十四讲作业练习(4)非线性优化部分

.5非线性优化

(0)回顾 SLAM问题的数学表达

定义:x:表示自身位置,x1,x2 … xk

​ y: 表示地图的路标,y1, y2 … yn

运动:从 k-1 时刻到 k 时刻,位置 x 如何变化,因此,运动方程即 k 时刻的位置需要借助 k-1 时刻的位置与输入uk确定。即:
x k = f ( x k − 1 , u k , w k ) x_k=f(x_{k-1},u_k,w_k) xk=f(xk1,uk,wk)
其中uk为输入,wk为该过程中加入的噪声。

观测:k 时刻在 xk 处探测到某个路标 yj,产生的观测数据为zkj。即:
z k , j = h ( y j , x k , v k , j ) z_{k,j}=h(y_j,x_k,v_{k,j}) zk,j=h(yj,xk,vk,j)
最基本的SLAM问题:已知运动测量读数 u 以及传感器读数 z 时,如何求解定位问题(估计 x )和建图问题(估计 y )。此时便将SLAM问题建模成了一个状态估计问题。

(1)状态估计问题

1)最大似然估计

运动方程与观测方程亦可以写成:
{ x k = f ( x k − 1 , u k ) + w k z k , j = h ( y j , x k ) + v k , j \begin{cases} x_k=f(x_{k-1},u_k)+w_k\\ z_{k,j}=h(y_j,x_k)+v_{k,j}\\ \end{cases} {xk=f(xk1,uk)+wkzk,j=h(yj,xk)+vk,j
设两个噪声项均满足零均值的高斯分布(正态分布),即:
w k ∼ N ( 0 , R k ) , v k ∼ N ( 0 , Q k , j ) w_k \sim N(0,R_k) ,v_k \sim N(0,Q_{k,j}) wkN(0,Rk),vkN(0,Qk,j)
其中,R,Q为协方差矩阵。

a)整体的分析

定义:所有时刻的机器人位姿和路标点坐标为
x = { x 1 , x 2 , ⋯ x k } , y = { y 1 , y 2 , ⋯ y N } x=\left\{\begin{matrix}x_1,x_2,\cdots x_k\end{matrix}\right\},y=\left\{\begin{matrix}y_1,y_2,\cdots y_N\end{matrix}\right\} x={x1,x2,xk},y={y1,y2,yN}
u表示所有时刻的输入,z表示所有时刻的观测数据。在已知输入数据u和观测数据z的条件下,求x,y的条件概率分布:
P ( x , y ∣ z , u ) P(x,y|z,u) P(x,yz,u)
应用贝叶斯法则:
P ( x , y ∣ z , u ) = P ( z , u ∣ x , y ) P ( x , y ) P ( z , u ) P(x,y|z,u)=\frac{P(z,u|x,y)P(x,y)}{P(z,u)} P(x,yz,u)=P(z,u)P(z,ux,y)P(x,y)
首先明确,x与y均是假设的位置,并不一定就在那里

例:机器人从 x i x_i xi 位置,想到 x i + 1 x_{i+1} xi+1 位置

原因:机器人在 x i x_i xi 这个位置,此处有 y j y_j yj 路标。

结果:因为在 x i x_i xi 这个位置,机器人通过传感器测得了观测数据 z i z_i zi ,同时为接下来的运动输入了 u u u

(1) P ( x , y ∣ z , u ) P(x,y|z,u) P(x,yz,u):后验概率,知道结果推原因。看到正确的观测结果,对位置的看法(是否在这个位置)。

(2) P ( x , y ) P(x,y) P(x,y):先验概率,知道原因推结果,在这个位置(假设)的概率(没有新证据之前)。

(3) P ( z , u ∣ x , y ) P(z,u|x,y) P(z,ux,y):似然概率,在这个位置(假设)符合观测结果(证据)的比例

因为无法求后验分布,所以选择求一个状态最优估计,使得在该状态下后验概率最大化:
( x , y ) M A P ∗ = a r g    m a x    P ( x , y ∣ z , u ) = a r g    m a x    P ( z , u ∣ x , y ) P ( x , y ) (x,y)^*_{MAP}=arg\;max\;P(x,y|z,u) = arg\;max\;P(z,u|x,y)P(x,y) (x,y)MAP=argmaxP(x,yz,u)=argmaxP(z,ux,y)P(x,y)
由贝叶斯法则,得求解最大后验概率等价于最大化似然和先验的乘积。但是我们不知道机器人位姿与路标,所以缺失先验,因此求解最大似然估计(MLE):
( x , y ) M L E ∗ = a r g    m a x    P ( z , u ∣ x , y ) (x,y)^*_{MLE}= arg\;max\;P(z,u|x,y) (x,y)MLE=argmaxP(z,ux,y)
至此,分析的都是所有时刻的x,y,u,z。

b)多维向量的正态分布模型

针对向量的正态分布称为多变量正态分布 x ∼ N ( μ , Σ ) x\sim N(\mu,\Sigma) xN(μ,Σ), 其概率密度函数为:
p ( x ) = d e t ( 2 π Σ ) − 1 2 e − 1 2 ( x − μ ) T Σ − 1 ( x − μ ) p(x)=det(2\pi\Sigma)^{-\frac{1}{2}}e^{-\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu)} p(x)=det(2πΣ)21e21(xμ)TΣ1(xμ)
其中,μ为均值向量,Σ是一个半正定的对称矩阵称为协方差矩阵covariance matrix

取负对数,得:
− ln ⁡ ( p ( x ) ) = 1 2 ln ⁡ ( 2 π    d e t ( Σ ) ) + 1 2 ( x − μ ) T Σ − 1 ( x − μ ) -\ln(p(x)) = \frac{1}{2}\ln(2\pi\;det(\Sigma))+\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu) ln(p(x))=21ln(2πdet(Σ))+21(xμ)TΣ1(xμ)
即将求最大似然转化为求最小化后面的二次型。

2)最小二乘问题
c)局部分析

整体的x,y是由无数个独立的xi,yk构成的
P ( z , u ∣ x , y ) = ∏ P ( u k ∣ x k − 1 , x k ) ∏ P ( z k , j ∣ x k , y i ) P(z,u|x,y)=\prod P(u_k|x_{k-1},x_k)\prod P(z_{k,j}|x_k,y_i) P(z,ux,y)=P(ukxk1,xk)P(zk,jxk,yi)
此处的公式不知如何得到。

w k ∼ N ( 0 , R k ) , v k ∼ N ( 0 , Q k , j ) w_k \sim N(0,R_k) ,v_k \sim N(0,Q_{k,j}) wkN(0,Rk),vkN(0,Qk,j),得 x k ∼ N ( f ( x k − 1 , u k ) , R k ) x_k \sim N(f(x_{k-1},u_k),R_k) xkN(f(xk1,uk),Rk) z j , k ∼ N ( h ( y j , x k ) , Q k , j ) z_{j,k} \sim N(h(y_j,x_k),Q_{k,j}) zj,kN(h(yj,xk),Qk,j)

且令:
e u , k = x k − f ( x k − 1 , u k ) e_{u,k}=x_k-f(x_{k-1},u_k) eu,k=xkf(xk1,uk)

e z , j , k = z k , j − h ( x k , y i ) e_{z,j,k}=z_{k,j}-h(x_k,y_i) ez,j,k=zk,jh(xk,yi)

可以求得:
m i n    J ( x , y ) = ∑ e u , k T R k − 1 e u , k + ∑ ∑ e z , j , k T Q k , j − 1 e z , j , k min \;J(x,y)= \sum e_{u,k}^TR_k^{-1}e_{u,k}+\sum \sum e_{z,j,k}^TQ_{k,j}^{-1}e_{z,j,k} minJ(x,y)=eu,kTRk1eu,k+ez,j,kTQk,j1ez,j,k

(2)非线性最小二乘

牛顿法:

增量方程:
Δ x ∗ = a r g    m i n ( F ( x ) + J ( x ) T Δ x + 1 2 Δ x T H Δ x ) \Delta x^*=arg \; min(F(x)+J(x)^T\Delta x+\frac{1}{2}\Delta x^TH\Delta x) Δx=argmin(F(x)+J(x)TΔx+21ΔxTHΔx)
求导:
J + H Δ x = 0 J+H\Delta x = 0 J+HΔx=0

高斯牛顿法:

J ( x ) J T ( x ) Δ x = − J ( x ) f ( x ) J(x)J^T(x)\Delta x=-J(x)f(x) J(x)JT(x)Δx=J(x)f(x)

高斯牛顿法的增量方程(程序中应用的):
( ∑ i = 1 100 J i − 1 J i T ) Δ x k = ∑ i = 1 100 − J i − 1 e i (\sum_{i=1}^{100}J_i^{-1}J_i^T)\Delta x_k = \sum_{i=1}^{100}-J_i^{-1}e_i (i=1100Ji1JiT)Δxk=i=1100Ji1ei

6.曲线拟合实验

(1)手写高斯牛顿

#include <iostream>
#include <opencv2/opencv.hpp>
#include <Eigen/Core>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int main(int argc, char **argv) {
    // 1.定义真实的参数值与估计值
    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值
    cv::RNG rng;                                 // OpenCV随机数产生器

    // 2.生成一系列的x与y,并将其放入容器中
    vector<double> 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));
    }

    // 3.开始Gauss-Newton迭代
    int iterations = 100;    // 迭代次数
    double cost = 0, lastCost = 0;  // 本次迭代的cost和上一次迭代的cost

    for (int iter = 0; iter < iterations; iter++) {
        //3.1明确目标,求出H与b
        Matrix3d H = Matrix3d::Zero();             // Hessian = J^T J in Gauss-Newton
        Vector3d b = Vector3d::Zero();             // bias
        cost = 0;
        //3.2根据高斯牛顿算法求
        //每一次迭代的x,y都不同因为ae,be,ce不同
        for (int i = 0; i < N; i++) {
            double xi = x_data[i], yi = y_data[i];  // 第i个数据点
            // start your code here
            double error = 0;   // 第i个数据点的计算误差
            double ye = (exp(ae*xi*xi + be*xi +ce));
            error = yi - ye; // 填写计算error的表达式 真实值的减去估计值
            Vector3d J; // 雅可比矩阵
            J[0] = -xi*xi*ye;  // de/da
            J[1] = -xi*ye;  // de/db
            J[2] = -ye;  // de/dc

            //H与b都是一个整体的概念
            H += J * J.transpose(); // GN近似的H
            b += -error * J;
            // end your code here

            cost += error * error;
        }

        // 求解线性方程 Hx=b,建议用ldlt
 	// start your code here
        Vector3d dx;
        dx = H.ldlt().solve(b);
		if (dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2] < 0.00001) {
			cout << "Not much improvement. Stop here." << endl;
			break;
		}
	// end your code here

        if (isnan(dx[0])) {
            cout << "result is nan!" << endl;
            break;
        }

        if (iter > 0 && cost > lastCost) {
            // 误差增长了,说明近似的不够好
            cout << "cost: " << cost << ", last cost: " << lastCost << endl;
            break;
        }

        // 3.3迭代的目的是更新abc估计值
        ae += dx[0];
        be += dx[1];
        ce += dx[2];

        lastCost = cost;

        cout << "total cost: " << cost << endl;
    }

    cout << "estimated abc = " << ae << ", " << be << ", " << ce << endl;
    return 0;
}

(2)使用Ceres库

#include <iostream>
#include <opencv2/core/core.hpp>
#include <ceres/ceres.h>

using namespace std;

// 1.构建代价函数
struct CURVE_FITTING_COST {
    CURVE_FITTING_COST(double x, double y) : _x(x), _y(y) {}
    // 残差的计算 --- abc: 模型参数, 有3维   residual: 残差
    template<typename T>
    bool operator()(const T *const abc, 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;             // 其实可以改为double x=i/double(N)
        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;
    }

    // 2.构建最小二乘问题
    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                      // 待估计参数
        );
    }

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

    ceres::Solver::Summary summary;                // 优化信息
    ceres::Solve(options, &problem, &summary);     // 开始优化 求解

    // 输出结果
    cout << summary.BriefReport() << endl;
    cout << "estimated a,b,c = ";
    for (auto a:abc) cout << a << " ";
    cout << endl;

    return 0;
}

总结:

​ (1)进一步理解了slam问题的数学表达,引入状态估计,进而引出最小二乘法。

​ (2)主要了解求解最小二乘问题的方法:高斯牛顿法

​ (3)通过手动编写高斯牛顿法的求解程序简单了解运行步骤,简单了解优化库Ceres的使用。

​ (4)不足之处:需要补充一些C++的知识,主要是STL。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值