slam典型应用手搓

一.雅可比矩阵的计算

假设我们有一个三维状态向量 [x, y, z],并且定义一个新的观测模型。输出两个观测值[u, v],其中

u = x^2 + y^2 + z, v = sin(x) + cos(y) + e^z,演示雅可比矩阵的计算

#include <iostream>
#include <Eigen/Dense>
#include <cmath>

/*
我们可以使用一个更复杂的例子来演示雅可比矩阵的计算。假设我们有一个三维状态向量 [x, y, z],并且定义一个新的观测模型。输出两个观测值[u, v],其中
u = x^2 + y^2 + z, v = sin(x) + cos(y) + e^z
*/


// 状态向量:[x, y, theta]
Eigen::Vector3d state; 

// 观测模型
Eigen::Vector2d observationModel(const Eigen::Vector3d& state){
    // 假设观测为一个3D点
    double x = state[0];
    double y = state[1];
    double z = state[2];

    double u = x * x + y * y + z;
    double v = std::sin(x) + std::cos(y) + std::exp(z);

    return Eigen::Vector2d(x, y);
}

// 计算雅可比矩阵
Eigen::Matrix<double, 2 ,3> computeJacobian(const Eigen::Vector3d& state){
    Eigen::Matrix<double, 2, 3> J;

    double x = state[0];
    double y = state[1];
    double z = state[2];

    // 对于x和y的偏导数
    J(0, 0) = 2 * x;    // u对x的偏导
    J(0, 1) = 2 * y;    // u对y的偏导
    J(0, 2) = 1;        // u对z的偏导

    J(1, 0) = std::cos(x);     // v对x的偏导
    J(1, 1) = -std::sin(y);    // v对y的偏导
    J(1, 2) = std::exp(z);     // v对z的偏导

    return J;
}


int main(int argc, char *argv[])
{
    // 示例状态
    state << 2, 3, 0;         // x = 2, y = 3, theta = 0

    // 计算观测
    Eigen::Vector2d z = observationModel(state);
    std::cout << "观测Observation: " << z.transpose() << std::endl;

    // 计算雅可比矩阵
    Eigen::Matrix<double, 2, 3> J = computeJacobian(state);
    std::cout << "Jaconbian Matrix :\n" << J <<std::endl;
    
    return 0;
}

二.里程计

编写一个运动模型的实现,如里程计模型,给定控制输入预测下一个状态

#include <iostream>
#include <Eigen/Dense>

/*
编写一个运动模型的实现,如里程计模型,给定控制输入预测下一个状态
*/

// 状态向量[x, y, theta]
Eigen::Vector3d state;

// 运动模型
Eigen::Vector3d motionModel(const Eigen::Vector3d& state, double v, double omega, double dt){
    double x = state[0];
    double y = state[1];
    double theta = state[2];

    // 更新状态
    double newX = x + v * std::cos(theta) * dt;
    double newY = y + v * std::sin(theta) * dt;
    double newTheta = theta + omega * dt;

    // 返回新的状态
    return Eigen::Vector3d(newX, newY, newTheta);
}

int main(){
    // 初始状态
    state << 0, 0, 0;

    // 控制输入:线速度和角速度
    double v = 1.0;        // 1.0m/s
    double omega = 0.1;    // 0.1rad/s
    double dt = 1.0;       // 时间间隔 1s

    // 计算新的状态
    Eigen::Vector3d newState = motionModel(state, v, omega, dt);
    std::cout << "新的状态: " << newState.transpose() << std::endl;

    return 0;
}

三.卡尔曼滤波

实现一个二维卡尔曼滤波器,使用二维状态向量,假设我们要跟踪物体在平面上的位置和速度。状态向量 [x, y, vx, vy].包括位置(x, y),速度(vx, vy)

#include <iostream>
#include <Eigen/Dense>


/*
实现一个二维卡尔曼滤波器,使用二维状态向量,假设我们要跟踪物体在平面上的位置和速度。状态向量 [x, y, vx, vy].包括位置(x, y),速度(vx, vy)
*/


class kamlanFilter{
public:
    kamlanFilter(){
        // 初始化状态向量[x, y, v_x, v_y]
        state = Eigen::Vector4d(0, 0, 0, 0);

        // 初始化协方差矩阵
        covariance = Eigen::Matrix4d::Identity();

        // 状态转移矩阵
        F = Eigen::Matrix4d::Identity();
        F(0, 2) = 1;   //x = x + v_x * dt
        F(1, 3) = 1;   //y = y + v_y * dt

        // 观测矩阵
        H = Eigen::Matrix<double, 2, 4>::Zero();
        H(0, 0) = 1;    // 观测x
        H(1, 1) = 1;    // 观测y

        // 状态噪声协方差
        Q = Eigen::Matrix4d::Identity() * 0.1;
        // 观测噪声协方差
        R = Eigen::Matrix2d::Identity() * 1;
    }

    void predict(){
        // 状态预测
        state = F * state;
        // 协方差预测
        covariance = F * covariance * F.transpose() + Q;
    }

    void update(const Eigen::Vector2d& measurement){
        // 计算创新
        Eigen::Vector2d y = measurement - H * state;
        // 计算协方差
        Eigen::Matrix2d S = H * covariance * H.transpose() + R;
        // 计算卡尔曼增益
        Eigen::Matrix<double, 4, 2> K = covariance * H.transpose() * S.inverse();
        // 更新状态
        state += K * y;
        // 更新协方差
        covariance = (Eigen::Matrix4d::Identity() - K * H) * covariance;
    }

    Eigen::Vector4d getState() const{
        return state;
    }


private:
    Eigen::Vector4d state;              // 状态向量
    Eigen::Matrix4d covariance;         // 协方差矩阵
    Eigen::Matrix4d F;                  // 状态转移矩阵
    Eigen::Matrix<double, 2, 4> H;      // 观测矩阵
    Eigen::Matrix4d Q;                  // 状态噪声协方差
    Eigen::Matrix2d R;                  // 观测噪声协方差
};

int main(){
    kamlanFilter kf;

    // 模拟数据
    Eigen::Vector2d measurements;
    for(int i = 0; i < 10; i++){
        // 模拟测量量(假设真实值是线性运动,添加噪声)
        measurements(0) = i + ((double) rand() / RAND_MAX) * 0.5;   // 真实x添加噪声
        measurements(1) = i + ((double) rand() / RAND_MAX) * 0.5;   // 真实y添加噪声
        // 预测
        kf.predict();
        // 更新
        kf.update(measurements);

        std::cout << "当前状态估计: " << kf.getState().transpose() << std::endl;

    }

    return 0;
}

四.扩展卡尔曼滤波

要在现有的二维卡尔曼滤波器基础上实现扩展卡尔曼滤波(EKF),你需要以下几个步骤:

非线性状态转移模型:定义一个非线性状态转移函数。
非线性观测模型:定义一个非线性观测函数。
计算雅可比矩阵:在预测和更新步骤中,计算状态转移和观测的雅可比矩阵。
下面是修改后的代码示例,添加了扩展卡尔曼滤波的实现:

#include <iostream>
#include <Eigen/Dense>


/*
要在现有的二维卡尔曼滤波器基础上实现扩展卡尔曼滤波(EKF),你需要以下几个步骤:

非线性状态转移模型:定义一个非线性状态转移函数。
非线性观测模型:定义一个非线性观测函数。
计算雅可比矩阵:在预测和更新步骤中,计算状态转移和观测的雅可比矩阵。
下面是修改后的代码示例,添加了扩展卡尔曼滤波的实现:
*/


class ExtendedKalmanFilter {
public:
    ExtendedKalmanFilter() {
        // 初始化状态向量 [x, y, v_x, v_y]
        state = Eigen::Vector4d(0, 0, 0, 0);

        // 初始化协方差矩阵
        covariance = Eigen::Matrix4d::Identity();

        // 状态噪声协方差
        Q = Eigen::Matrix4d::Identity() * 0.1;
        // 观测噪声协方差
        R = Eigen::Matrix2d::Identity() * 1;
    }

    void predict(double dt) {
        // 使用非线性状态转移模型
        Eigen::Vector4d newState;
        newState(0) = state(0) + state(2) * dt; // x = x + v_x * dt
        newState(1) = state(1) + state(3) * dt; // y = y + v_y * dt
        newState(2) = state(2); // 速度保持不变
        newState(3) = state(3); // 速度保持不变

        // 更新状态
        state = newState;

        // 计算雅可比矩阵 F
        F = Eigen::Matrix4d::Identity();
        F(0, 2) = dt; // ∂x/∂v_x
        F(1, 3) = dt; // ∂y/∂v_y

        // 协方差预测
        covariance = F * covariance * F.transpose() + Q;
    }

    void update(const Eigen::Vector2d& measurement) {
        // 使用非线性观测模型
        Eigen::Vector2d z_pred;
        z_pred(0) = state(0); // 观测 x
        z_pred(1) = state(1); // 观测 y

        // 计算创新
        Eigen::Vector2d y = measurement - z_pred;

        // 计算雅可比矩阵 H
        H = Eigen::Matrix<double, 2, 4>::Zero();
        H(0, 0) = 1; // ∂z/∂x
        H(1, 1) = 1; // ∂z/∂y

        // 计算协方差
        Eigen::Matrix2d S = H * covariance * H.transpose() + R;
        // 计算卡尔曼增益
        Eigen::Matrix<double, 4, 2> K = covariance * H.transpose() * S.inverse();
        // 更新状态
        state += K * y;
        // 更新协方差
        covariance = (Eigen::Matrix4d::Identity() - K * H) * covariance;
    }

    Eigen::Vector4d getState() const {
        return state;
    }

private:
    Eigen::Vector4d state;              // 状态向量
    Eigen::Matrix4d covariance;         // 协方差矩阵
    Eigen::Matrix4d F;                  // 状态转移雅可比矩阵
    Eigen::Matrix<double, 2, 4> H;      // 观测雅可比矩阵
    Eigen::Matrix4d Q;                  // 状态噪声协方差
    Eigen::Matrix2d R;                  // 观测噪声协方差
};

int main() {
    ExtendedKalmanFilter ekf;

    // 模拟数据
    Eigen::Vector2d measurements;
    double dt = 1.0; // 时间步长
    for (int i = 0; i < 10; i++) {
        // 模拟测量量(假设真实值是线性运动,添加噪声)
        measurements(0) = i + ((double)rand() / RAND_MAX) * 0.5; // 真实 x 加噪声
        measurements(1) = i + ((double)rand() / RAND_MAX) * 0.5; // 真实 y 加噪声
        // 预测
        ekf.predict(dt);
        // 更新
        ekf.update(measurements);

        std::cout << "当前状态估计: " << ekf.getState().transpose() << std::endl;
    }

    return 0;
}

五.g2o

使用g2o优化曲线

#include <iostream>
#include <g2o/core/g2o_core_api.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/core/optimization_algorithm_dogleg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <Eigen/Core>
#include <opencv2/core/core.hpp>
#include <cmath>
#include <chrono>

using namespace std;

// 曲线模型的顶点,模板参数:优化变量维度和数据类型
class CurveFittingVertex : public g2o::BaseVertex<3, Eigen::Vector3d> {
public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW //这是一个宏,用于在Eigen库中创建对齐的new操作符。这可以确保在分配内存时,对象的大小与编译器指定的对齐要求相匹配

  // 重置
  virtual void setToOriginImpl() override {    //用于将顶点的位置重置为原点
    _estimate << 0, 0, 0;
  }

  // 更新
  virtual void oplusImpl(const double *update) override {    //用于更新顶点的位置
    _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) {}

  // 计算曲线模型误差
  virtual void computeError() override {    //用于计算曲线模型的误差
    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 void linearizeOplus() override {      //用于计算雅可比矩阵
    const CurveFittingVertex *v = static_cast<const CurveFittingVertex *> (_vertices[0]);
    const Eigen::Vector3d abc = v->estimate();
    double y = exp(abc[0] * _x * _x + abc[1] * _x + abc[2]);
    _jacobianOplusXi[0] = -_x * _x * y;
    _jacobianOplusXi[1] = -_x * y;
    _jacobianOplusXi[2] = -y;
  }

  virtual bool read(istream &in) {}

  virtual bool write(ostream &out) const {}

public:
  double _x;  // x 值, y 值为 _measurement
};

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 = 200;                                 // 数据点
  double w_sigma = 1.0;                        // 噪声Sigma值
  double inv_sigma = 1.0 / w_sigma;
  cv::RNG rng;                                 // OpenCV随机数产生器

  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 * w_sigma));
  }

  // 构建图优化,先设定g2o
  typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>> BlockSolverType;  // 每个误差项优化变量维度为3,误差值维度为1
  typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型

  // 梯度下降方法,可以从GN, LM, DogLeg 中选
  auto solver = new g2o::OptimizationAlgorithmGaussNewton(
    g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
  g2o::SparseOptimizer optimizer;     // 图模型
  optimizer.setAlgorithm(solver);   // 设置求解器
  optimizer.setVerbose(true);       // 打开调试输出

  // 往图中增加顶点
  CurveFittingVertex *v = new CurveFittingVertex();
  v->setEstimate(Eigen::Vector3d(ae, be, ce));
  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(10);
  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;
}

六. 给定一组3D点,计算它投影到图像上的2D点坐标

要将一组3D点投影到图像上的2D点坐标,通常需要使用相机的内参和外参进行投影。以下是实现的基本步骤:

基本步骤:

1.定义相机内参K

2.定义相机外参

包括旋转矩阵R和平移向量t,将3D点从世界坐标系转换到相机坐标系

3.投影公式

给定一个3D点P =(X,Y,Z)^T,首先将其转换到相机坐标系,Pcamera=R*P+t,然后使用内参矩阵投影到2D平面:[u, v, 1] = K*Pcamera,最后计算2D坐标u' = u / z, v' = v / z。

#include <iostream>
#include <Eigen/Dense>
#include <vector>

using namespace Eigen;

// 定义3D点投影到2D的函数
std::vector<Vector2d> projectPoints(const std::vector<Vector3d>& points3D, const Matrix3d& K, const Matrix3d& R, const Vector3d& t) {
    std::vector<Vector2d> points2D;

    for (const auto& point3D : points3D) {
        // 将3D点转换到相机坐标系
        Vector3d pointCamera = R * point3D + t;

        // 进行投影
        Vector3d point2DHomogeneous = K * pointCamera;

        // 将齐次坐标转换为2D坐标
        double z = point2DHomogeneous(2);
        if (z != 0) {
            Vector2d point2D(point2DHomogeneous(0) / z, point2DHomogeneous(1) / z);
            points2D.push_back(point2D);
        }
    }

    return points2D;
}

int main() {
    // 相机内参
    Matrix3d K;
    K << 800, 0, 320,   // fx, 0, cx
         0, 800, 240,   // 0, fy, cy
         0, 0, 1;       // 0, 0, 1

    // 相机外参 (旋转和平移)
    Matrix3d R = Matrix3d::Identity(); // 假设无旋转
    Vector3d t(0, 0, 0); // 假设无平移

    // 定义3D点
    std::vector<Vector3d> points3D = {
        {1, 1, 5},
        {2, 2, 5},
        {3, 3, 5}
    };

    // 投影到2D
    auto points2D = projectPoints(points3D, K, R, t);

    // 输出结果
    for (const auto& point : points2D) {
        std::cout << "Projected 2D Point: (" << point(0) << ", " << point(1) << ")\n";
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值