激光SLAM直接线性方法里程计运动模型及标定

2023-11-08

原创作者:W_Tortoise ,

原创作者文章:https://blog.csdn.net/learning_tortosie/article/details/107763626

 

1 里程计运动模型

1.1 两轮差分底盘的运动模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2 三轮全向底盘的运动学模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 航迹推算(Dead Reckoning)

在这里插入图片描述

2 里程计标定

2.1 线性最小二乘的基本原理

在这里插入图片描述
在这里插入图片描述

2.2 最小二乘的直线拟合

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 最小二乘在里程计标定中的应用

在这里插入图片描述
在这里插入图片描述

3 实现一个直接线性方法的里程计标定模块

3.1 安装

mkdir -p ~/calib_odom_ws/src
cd ~/calib_odom_ws/src
git clone https://github.com/KOTOKORURU/odometry_calibration
cd ..
catkin_make
  • 1
  • 2
  • 3
  • 4
  • 5

错误1:

odom_calib/src/odometry_calibration/src/main.cpp:5:40: fatal error: nav_core/recovery_behavior.h: 没有那个文件或目录

解决办法:打开odom_calib/src/odometry_calibration/src/main.cpp,注释#nav_core/recovery_behavior.h

错误2:

odom_calib/src/odometry_calibration/src/main.cpp:28:25: fatal error: csm/csm_all.h: 没有那个文件或目录

解决办法:安装ros-kinetic-csm

sudo apt install ros-kinetic-csm
  • 1

3.2 运行

3.2.1 启动launch文件

cd ~/calib_odom_ws/
source /devel/setup.bash
roslaunch calib_odom odomCalib.launch
  • 1
  • 2
  • 3

注意:保证没有任何ROS节点在运行,roscore也要关闭。

3.2.2 打开rviz

1.打开rviz

rosrun rviz rviz
  • 1

2.配置rviz

fix_frame选择为odom。在Add选项卡中增加三条Path消息,订阅的topic分别为:

  • odom_path_pub_
  • scan_path_pub_
  • calib_path_pub_

分别选择不同的颜色,用来区分路径。

可以将此rviz的配置保存为odom_calib.rviz文件,然后在odomCalib.launch文件中添加:

<node pkg="rviz" type="rviz" name="rviz" args="-d $(find calib_odom)/rviz_cfg/odom_calib.rviz" />
  • 1

这样,在启动launch文件时,就自动打开了配置好的rviz。

3.2.3 播放bag

首先,解压odom.bag.tar.gz

cd ~/calib_odom_ws/src/odometry_calibration/bag
tar zxvf odom.bag.tar.gz
  • 1
  • 2

然后,播放bag:


rosbag play –clock odom.bag
  • 1
  • 2

如果一切正常,则能看到运行矫正程序的终端会打印数据,并且rviz中可以看到两条路径。

3.2.4 矫正里程计

当打印的数据到达一定的数量后,则可以开始矫正。这里,需要给calib_flag话题发布一个数据。

rostopic pub /calib_flag std_msgs/Empty "{}"
  • 1

3.2.5 结果

程序矫正完毕会输出对应的矫正矩阵,并且会在rviz中显示出第三条路径,即calib_path。可以观察里程计路径odom_path和矫正路径calib_path区别来判断此次矫正的效果。
在这里插入图片描述
如下图所示,激光雷达的pose好像有点问题。。
在这里插入图片描述

3.3 解析

3.3.1 构建超定方程组Ax=b

/*
 * 输入:里程计和激光数据
 * 构建最小二乘需要的超定方程组
 * Ax = b
*/
typedef Eigen::Matrix<double, 1, 9> Vector9d_T;
typedef Eigen::Matrix<double, 9, 1> Vector9d;

bool OdomCalib::Add_Data(Eigen::Vector3d Odom, Eigen::Vector3d scan)
{
    if(now_len < INT_MAX)
    {
		Eigen::Matrix<double, 3, 9> A_tmp;
		Eigen::Matrix<double, 3, 1> b_tmp;
		A_tmp.setZero();
		b_tmp.setZero();
	        
        b_tmp = scan;

		for(int i = 0; i < 3; i++){
			Vector9d_T tmp;
			tmp.setZero();
			tmp.block<1, 3>(0, i * 3) = Odom.transpose();
			A_tmp.block<1, 9>(i, 0) = tmp;
		}

		A.block<3, 9>(now_len * 3, 0) =  A_tmp;
		b.block<3, 1>(now_len * 3, 0) =  b_tmp;

        now_len++;
        return true;
    }
    else
        return false;
}

/*

* 输入:里程计和激光数据

* 构建最小二乘需要的超定方程组

* Ax = b

*/

typedef Eigen::Matrix<double, 1, 9> Vector9d_T;

typedef Eigen::Matrix<double, 9, 1> Vector9d;

bool OdomCalib::Add_Data(Eigen::Vector3d Odom, Eigen::Vector3d scan)

{

    if(now_len < INT_MAX)

    {

        Eigen::Matrix<double, 3, 9> A_tmp;

        Eigen::Matrix<double, 3, 1> b_tmp;

        A_tmp.setZero(); b_tmp.setZero();

       b_tmp = scan;

       for(int i = 0; i < 3; i++)

     {

         Vector9d_T tmp;

          tmp.setZero();

          tmp.block<1, 3>(0, i * 3) = Odom.transpose();

          A_tmp.block<1, 9>(i, 0) = tmp; }

         A.block<3, 9>(now_len * 3, 0) = A_tmp;

         b.block<3, 1>(now_len * 3, 0) = b_tmp;

        now_len++;

        return true;

    }

     else return false;

}

3.3.2 求解超定方程组

/*
 * 求解线性最小二乘Ax=b(A^TAx^* = A^Tb)
 * 返回得到的矫正矩阵
*/
Eigen::Matrix3d OdomCalib::Solve()
{
    Eigen::Matrix3d correct_matrix;
    Vector9d x;
    Eigen::MatrixXd A_tmp;
    Eigen::VectorXd b_tmp;
    
    A_tmp = A.transpose() * A;
    b_tmp = A.transpose() * b;
    x = A_tmp.ldlt().solve(b_tmp);  // Cholesky decomposition
    
    correct_matrix << x(0), x(1), x(2),
                      x(3), x(4), x(5),
                      x(6), x(7), x(8);
    
    return correct_matrix;
}

/*

* 求解线性最小二乘Ax=b(A^TAx^* = A^Tb)

* 返回得到的矫正矩阵

*/

Eigen::Matrix3d OdomCalib::Solve()

{

     Eigen::Matrix3d correct_matrix;

     Vector9d x; Eigen::MatrixXd A_tmp;

     Eigen::VectorXd b_tmp;

    A_tmp = A.transpose() * A;

    b_tmp = A.transpose() * b;

    x = A_tmp.ldlt().solve(b_tmp); // Cholesky decomposition

   correct_matrix << x(0), x(1), x(2),

                                  x(3), x(4), x(5),

                                  x(6), x(7), x(8);

   return correct_matrix;

}

3.3.3 计算两个位姿之间的位姿差

Eigen::Vector3d cal_delta_distance(Eigen::Vector3d odom_pose)
{
    static Eigen::Vector3d now_pos, last_pos;
    Eigen::Vector3d d_pos;  // return value
    now_pos = odom_pose;

    Eigen::Matrix3d R_last;
    R_last << cos(now_pos(2)), -sin(now_pos(2)), 0,
              sin(now_pos(2)), cos(now_pos(2)),  0,
              0,                             0,  1;
    d_pos = R_last.transpose() * (now_pos - last_pos);
    // d_pos = R_last.inverse() * (now_pos - last_pos); // 根据航迹推算公式,应该是逆啊
    
    last_pos = now_pos;

    return d_pos;
}

Eigen::Vector3d cal_delta_distence(Eigen::Vector3d odom_pose)

{

         static Eigen::Vector3d now_pos, last_pos;

         Eigen::Vector3d d_pos; // return value

         now_pos = odom_pose;

         Eigen::Matrix3d R_last;

         R_last << cos(now_pos(2)), -sin(now_pos(2)), 0,

                           sin(now_pos(2)), cos(now_pos(2)), 0,

                            0,                          0,                          1;

        d_pos = R_last.transpose() * (now_pos - last_pos);

       // d_pos = R_last.inverse() * (now_pos - last_pos); // 根据航迹推算公式,应该是逆啊

      last_pos = now_pos;

      return d_pos;

}

3.3.4 获取里程计和激光雷达位姿

输出bag文件的信息:
在这里插入图片描述
其中,/odom话题为里程计的pose,/sick_scan为2D激光雷达的数据,同时还发布了TF。激光雷达的pose是通过PI-ICP算法计算的,而里程计是直接从bag发布的。

获取里程计pose:

bool Scan2::getOdomPose(Eigen::Vector3d& pose, const ros::Time& t)
{
    // Get the robot's pose
    tf::Stamped<tf::Pose> ident(tf::Transform(tf::createQuaternionFromRPY(0,0,0),
                                               tf::Vector3(0,0,0)), t, base_frame_);
    tf::Stamped<tf::Transform> odom_pose;

    try
    {
        tf_.transformPose(odom_frame_, ident, odom_pose);
    }
    catch(tf::TransformException e)
    {
        ROS_WARN("Failed to compute odom pose, skipping scan (%s)", e.what());
        return false;
    }

    double yaw = tf::getYaw(odom_pose.getRotation());

    pose << odom_pose.getOrigin().x(),
            odom_pose.getOrigin().y(),
            yaw;
    //pub_msg(pose, path_odom, odom_path_pub_);
    return true;
}

bool Scan2::getOdomPose(Eigen::Vector3d& pose, const ros::Time& t)

{

// Get the robot's pose

       tf::Stamped<tf::Pose> ident(tf::Transform(tf::createQuaternionFromRPY(0,0,0),

                                                                         tf::Vector3(0,0,0)), t, base_frame_);

      tf::Stamped<tf::Transform> odom_pose;

     try

    {

            tf_.transformPose(odom_frame_, ident, odom_pose);

    }

     catch(tf::TransformException e)

     {

            ROS_WARN("Failed to compute odom pose, skipping scan (%s)", e.what());

            return false;

       }

      double yaw = tf::getYaw(odom_pose.getRotation());

      pose << odom_pose.getOrigin().x(),

                   odom_pose.getOrigin().y(),

                   yaw;

      //pub_msg(pose, path_odom, odom_path_pub_);

     return true;

}

 

3.3.5 小结

理论上,相邻的两个时刻,激光雷达和里程计的位姿变换是相同的,而实际的估计值和真实值呈线性关系,因此此可以构建基于直接线性方法的最小二乘问题。

目前此项目,只是演示如何使用激光雷达纠正里程计的误差,属于后处理,并不适用于实际应用。

我觉得,真正意义上得里程计标定可以参考:https://github.com/MegviiRobot/OdomLaserCalibraTool 。

4 参考

  1. 激光SLAM理论与实践
  2. https://github.com/KOTOKORURU/odometry_calibration
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

激光SLAM直接线性方法里程计运动模型及标定 的相关文章

随机推荐

  • C++的智能指针unique_ptr、shared_ptr和weak_ptr

    C 的智能指针是一种特殊的指针类型 它能够自动管理内存资源 避免常见的内存泄漏和多次释放等问题 C 11引入了三种主要的智能指针 unique ptr shared ptr和weak ptr 目录 unique ptr shared ptr
  • 双线性插值算法的详细总结

    原文出处 http blog csdn net xjz18298268521 article details 51220576 最近在做视频拼接的项目 里面用到了图像的单应性矩阵变换 在最后的图像重映射 由于目标图像的坐标是非整数的 所以需
  • pcie设备之驱动加载udev事件详解

    打卡打卡 udev 自内核2 6之后取代devfs udev配置 usr lib udev rules d etc udev rules d 如何触发udev事件 kobject uevent函数 pcie scan扫描函数 初始化pcie
  • python在linux系统下的编辑编译运行

    PYTHON在linux系统下的编辑编译 新建一个脚本文件 编写程序 运行程序 若安装了如spyder这样的编辑器 若是很简单的代码 新建一个脚本文件 gedit xxx py 新建py格式文件 编写程序 运行程序 在当前目录下 输入pyt
  • 【论文学习】FD-MonbileNet: IMPROVED MOBILENET WITH A FAST DOWNSAMPLING STRATEGY

    原文链接 https arxiv org abs 1802 03750 作者介绍了一种高效且在有限运算量限制上十分准确的网络 Fast Downsampling MobileNet FD MobileNet 其中心思想是在MobileNet
  • 中国钢铁产业产量分析与市场需求状况研究报告2022版

    中国钢铁产业产量分析与市场需求状况研究报告2022版 修订日期 2021年12月 搜索鸿晟信合研究院查看官网更多内容 第一章 产业转移的内涵及模式概述 1 1 产业转移的概念界定 1 1 1 产业转移的定义 1 1 2 产业转移的分类 1
  • mysql 连续打卡_MySQL查询连续打卡信息?

    最近多次看到用SQL查询连续打卡信息问题 自己也实践一波 抛开问题本身 也是对MySQL窗口函数和自定义变量用法的一种练习 01 建表 所用数据库为MySQL8 0 简单而不失一般性 建立一个仅有记录id 用户id 日期和打卡标记共4个字段
  • Python用户消费行为分析实例

    本文借鉴于知乎用户秦路的专栏https zhuanlan zhihu com p 27910430 这里只是自己理解基础上加以扩充和整理修改 丰富细节 由于手头用户消费数据的缺失我们这次采用专栏的数据进行实战 原数据在此 链接 https
  • vue判断input框不能为空_vue判断input输入内容全是空格的方法

    moduleinfo card count count phone 1 count 1 search count count phone 7 count 7 card des 支持文本 图片 视频 网站安全检测等多格式识别服务 提供色情 涉
  • 吃透MIPI接口,你必须了解它这三种PHY规范的区别

    MIPI接口及其物理层特性 MIPI 移动行业处理器接口 是专为移动设备 如智能手机 平板电脑 笔记本电脑和混合设备 设计的行业规范的标准定义 MIPI标准定义了三个通用的唯一物理 PHY 层 即MIPID PHY C PHY和M PHY
  • Stars in Your Window 【POJ - 2482】【线段树扫描线】

    题目链接 最开始的时候做成了贪心 离线求二维前缀和 然后树状数组维护二维偏序 这样的想法是存在BUG的 因为我是将每个点当成左下角 右下角 左上角 右上角来分别计算最大贡献的 但这样的做法却不是最贪心的 因为有可能该点并不作为矩形的四个顶角
  • [ESP][驱动]ST7701S RGB屏幕驱动

    ST7701SForESP ST7701S ESP系列驱动 基于ESP IDF5 0 ESP32S3编写 本库只负责SPI的配置 SPI设置屏幕信息两方面 由于RGB库和图形库的配置无法解耦 具体使用的图形库需要自行配置添加 本示例默认绑定
  • 全局异常处理Seata事务失效解决方案

    全局异常处理Seata事务失效解决方案 最近的项目用到了seata来管理全局事务 在进行测试的时候 发现当service A 调用Service B时 如果ServiceA报错 ServiceB能回滚 但是如果ServiceB报错 Serv
  • 区块链基础和底层技术

    大家好 这里是链客区块链技术问答社区 链客 有问必答 区块链基础 区块链的维基百科定义 区块链是一个基于比特币协议的不需要许可的分布式数据库 它维护了一个持续增长的不可篡改的数据记录列表 即使对于该数据库节点的运营者们也是如此 简而言之 区
  • 机器视觉图像分析领域,单目测量和双目测量有什么区别和用途?

    单目测量和双目测量在许多应用场景中都有广泛的应用 以下是一些典型的应用场景 单目测量应用场景 1 无人机定位与导航 单目摄像头可以用于无人机的视觉定位与导航 通过捕捉地面特征点 实现无人机的姿态估计和位置定位 2 机器人视觉导航 在轻量级的
  • CART回归树 GBDT XGB LGB

    CART回归树 GBDT XGB LGB 1 决策树 ID3 C4 5 CART 决策树算法原理 上 CART树 首先我们看看决策树算法的优点 1 简单直观 生成的决策树很直观 2 基本不需要预处理 不需要提前归一化 处理缺失值 3 使用决
  • Windows中使用Docker安装Redis

    1 拉取Redis 以管理员身份运行CMD 执行如下命令拉取Redis docker pull redis 2 在D盘新建目录D Net Program Net Docker Redis 在D盘新建D Net Program Net Doc
  • DataSphereStudio创建工作流时报错No FileSystem for scheme: hdfs问题

    DataSphereStudio创建工作流时报错No FileSystem for scheme hdfs问题 最近在用微众银行开发的dss工具 但是安装完之后 创建工作流报错No FileSystem for scheme hdfs 如果
  • java nio socket,Java----NioSocket的简单使用

    一 自己理解的概念 nioSocket 即new io socket 是一种同步非阻塞的I O 其使用buffer来缓存数据和channel来传输数据 使用select来分拣消息 其使用的ServerSocketChannel和Socket
  • 激光SLAM直接线性方法里程计运动模型及标定

    原创作者 W Tortoise 原创作者文章 https blog csdn net learning tortosie article details 107763626 1 里程计运动模型 1 1 两轮差分底盘的运动模型 1 2 三轮全