从零开始激光slam定位【1】- 激光里程计

2024-02-28 15:38

本文主要是介绍从零开始激光slam定位【1】- 激光里程计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在前面:本文是参考知乎任乾大佬的帖子完成,增加了自己的注释以及一些其他功能,如果有理解不到位的地方,请大家指正~

目录

  • 里程计流程
  • front_end_node.cpp的注释
  • 可视化
  • debug
  • 参考

里程计流程

1、如果当前帧是第一帧,那么将其看做关键帧,进行关键帧更新
2、关键帧更新都做了什么?更新local_map队列,更新local_map,更新ndt的target_cloud。
3、如果不是第一帧,那么将其与local_map进行ndt配准,配准的所需要的guess_pose是根据匀速运动模型进行计算的。
4、配准得到当前帧的pose,利用当前帧和上一阵的pose,结合匀速运动模型得到下一次配准的guess_pose;如果当前帧的pose相对于上一关键帧的pose距离较大,则更新关键帧。

front_end_node.cpp的注释

/** @Descripttion: 对任乾大佬的tag4.0增加map--->lidar的tf变换,并可视化最原始的变换后的点云* @vision: * @Author: suyunzzz* @Date: 2020-08-20 23:40:44* @LastEditors: Please set LastEditors* @LastEditTime: 2020-08-26 23:20:21*/
/** @Description: * @Author: Ren Qian* @Date: 2020-02-05 02:56:27*/#include <ros/ros.h>
#include <pcl/common/transforms.h>#include "glog/logging.h"#include "lidar_localization/global_defination/global_defination.h"
#include "lidar_localization/subscriber/cloud_subscriber.hpp"
#include "lidar_localization/subscriber/imu_subscriber.hpp"
#include "lidar_localization/subscriber/gnss_subscriber.hpp"
#include "lidar_localization/tf_listener/tf_listener.hpp"
#include "lidar_localization/publisher/cloud_publisher.hpp"
#include "lidar_localization/publisher/odometry_publisher.hpp"
#include "lidar_localization/front_end/front_end.hpp"           // 前端相关的头文件#include <tf/transform_broadcaster.h>
#include <tf/transform_datatypes.h>using namespace lidar_localization;int main(int argc, char *argv[]) {google::InitGoogleLogging(argv[0]);FLAGS_log_dir = WORK_SPACE_PATH + "/Log";FLAGS_alsologtostderr = 1;// 初始化节点ros::init(argc, argv, "front_end_node");ros::NodeHandle nh;std::shared_ptr<CloudSubscriber> cloud_sub_ptr = std::make_shared<CloudSubscriber>(nh, "/kitti/velo/pointcloud", 100000);std::shared_ptr<IMUSubscriber> imu_sub_ptr = std::make_shared<IMUSubscriber>(nh, "/kitti/oxts/imu", 1000000);std::shared_ptr<GNSSSubscriber> gnss_sub_ptr = std::make_shared<GNSSSubscriber>(nh, "/kitti/oxts/gps/fix", 1000000);// TF监听。lidar-->imustd::shared_ptr<TFListener> lidar_to_imu_ptr = std::make_shared<TFListener>(nh, "velo_link", "imu_link");   // 发布者,发布到frame_id="/map"std::shared_ptr<CloudPublisher> cloud_pub_ptr = std::make_shared<CloudPublisher>(nh, "current_scan", 100, "/map");  std::shared_ptr<CloudPublisher> raw_cloud_pub_ptr = std::make_shared<CloudPublisher>(nh,"raw_current_scan",100,"/map");std::shared_ptr<CloudPublisher> local_map_pub_ptr = std::make_shared<CloudPublisher>(nh, "local_map", 100, "/map");std::shared_ptr<CloudPublisher> global_map_pub_ptr = std::make_shared<CloudPublisher>(nh, "global_map", 100, "/map");// 发布odom信息,topic_name,base_frame_id,child_frame_idstd::shared_ptr<OdometryPublisher> laser_odom_pub_ptr = std::make_shared<OdometryPublisher>(nh, "laser_odom", "map", "lidar", 100);std::shared_ptr<OdometryPublisher> gnss_pub_ptr = std::make_shared<OdometryPublisher>(nh, "gnss", "map", "lidar", 100);// 创建一个前端对象std::shared_ptr<FrontEnd> front_end_ptr = std::make_shared<FrontEnd>();// 数据队列std::deque<CloudData> cloud_data_buff;std::deque<IMUData> imu_data_buff;std::deque<GNSSData> gnss_data_buff;Eigen::Matrix4f lidar_to_imu = Eigen::Matrix4f::Identity();bool transform_received = false;bool gnss_origin_position_inited = false;bool front_end_pose_inited = false;             // true 前端pose未初始化// 局部地图,全局地图,当前帧点云CloudData::CLOUD_PTR local_map_ptr(new CloudData::CLOUD());     CloudData::CLOUD_PTR global_map_ptr(new CloudData::CLOUD());CloudData::CLOUD_PTR current_scan_ptr(new CloudData::CLOUD());CloudData::CLOUD_PTR current_scan_ptr_raw(new CloudData::CLOUD());  // 当前帧变换后的结果,/****************************************************/// 以下为我新增的tf变换,map--->lidar// 发布tf变换 map--->lidar//static tf::TransformBroadcaster br;tf::TransformBroadcaster br_;          tf::Transform transform;tf::Quaternion q;struct pose{double x,y,z;double roll,pitch,yaw;};struct pose current_pose_;/***************************************************/double run_time = 0.0;double init_time = 0.0;bool time_inited = false;bool has_global_map_published = false; ros::Rate rate(100);while (ros::ok()) {ros::spinOnce();// 数据读入cloud_sub_ptr->ParseData(cloud_data_buff);imu_sub_ptr->ParseData(imu_data_buff);gnss_sub_ptr->ParseData(gnss_data_buff);// 如果没有得到变换if (!transform_received) {if (lidar_to_imu_ptr->LookupData(lidar_to_imu)) {transform_received = true;}} else {while (cloud_data_buff.size() > 0 && imu_data_buff.size() > 0 && gnss_data_buff.size() > 0) {CloudData cloud_data = cloud_data_buff.front();IMUData imu_data = imu_data_buff.front();GNSSData gnss_data = gnss_data_buff.front();if (!time_inited) {time_inited = true;init_time = cloud_data.time;} else {run_time = cloud_data.time - init_time;}double d_time = cloud_data.time - imu_data.time;if (d_time < -0.05) {       // cloud慢cloud_data_buff.pop_front();} else if (d_time > 0.05) {     // cloud快imu_data_buff.pop_front();gnss_data_buff.pop_front();} else {                                    // 开始cloud_data_buff.pop_front();            // 将使用的数据在队列中删除imu_data_buff.pop_front();gnss_data_buff.pop_front();Eigen::Matrix4f odometry_matrix = Eigen::Matrix4f::Identity();if (!gnss_origin_position_inited) {gnss_data.InitOriginPosition();gnss_origin_position_inited = true;}gnss_data.UpdateXYZ();odometry_matrix(0,3) = gnss_data.local_E;odometry_matrix(1,3) = gnss_data.local_N;odometry_matrix(2,3) = gnss_data.local_U;odometry_matrix.block<3,3>(0,0) = imu_data.GetOrientationMatrix();odometry_matrix *= lidar_to_imu;gnss_pub_ptr->Publish(odometry_matrix);             // gnss发布里程计信息if (!front_end_pose_inited) {       // 如果未初始化前端,将gnss的pose作为初始posefront_end_pose_inited = true;front_end_ptr->SetInitPose(odometry_matrix);        }front_end_ptr->SetPredictPose(odometry_matrix);                 // gnss的里程计作为预测(没有用到)Eigen::Matrix4f laser_matrix = front_end_ptr->Update(cloud_data);               // 更新当前帧,返回当前帧的poselaser_odom_pub_ptr->Publish(laser_matrix);                  // lidar发布里程计信息(当前帧的pose)/****************************************************/// 以下为我新增的tf变换,map--->lidar// 发布tf变换map--->lidar// current_pose_写入transformtransform.setOrigin(tf::Vector3(laser_matrix(0,3), laser_matrix(1,3), laser_matrix(2,3)));   // transform 设置平移tf::Matrix3x3 mat_b;mat_b.setValue(static_cast<double>(laser_matrix(0, 0)), static_cast<double>(laser_matrix(0, 1)),static_cast<double>(laser_matrix(0, 2)), static_cast<double>(laser_matrix(1, 0)),static_cast<double>(laser_matrix(1, 1)), static_cast<double>(laser_matrix(1, 2)),static_cast<double>(laser_matrix(2, 0)), static_cast<double>(laser_matrix(2, 1)),static_cast<double>(laser_matrix(2, 2)));                    mat_b.getRPY(current_pose_.roll, current_pose_.pitch, current_pose_.yaw, 1);//mat2rpy  旋转q.setRPY(current_pose_.roll, current_pose_.pitch, current_pose_.yaw); //q from rpytransform.setRotation(q);//trans from qbr_.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "map", "lidar"));  // map--->scan的transform/****************************************************/// 当前帧的点云front_end_ptr->GetCurrentScan(current_scan_ptr);            // 变换到global下的当前帧点云cloud_pub_ptr->Publish(current_scan_ptr);                   // 发布当前帧点云front_end_ptr->GetCurrentScanRaw(current_scan_ptr_raw);        // 发布变换后的原始点云,未下采样raw_cloud_pub_ptr->Publish(current_scan_ptr_raw);if (front_end_ptr->GetNewLocalMap(local_map_ptr))           // 发布局部地图,只有遇到关键帧才能更新局部地图local_map_pub_ptr->Publish(local_map_ptr);}if (run_time > 460.0 && !has_global_map_published) {            // 全局地图,只能发布一次,并且有时间限制if (front_end_ptr->GetNewGlobalMap(global_map_ptr)) {    //  每100个关键帧,才更新一次全局地图global_map_pub_ptr->Publish(global_map_ptr);has_global_map_published = true;}}}}rate.sleep();}return 0;
}

可视化

在这里插入图片描述黄色的代表gnss的里程计,红色的代表激光里程计,大的Axis的坐标系是lidar,小的坐标系是map。

debug

typename boost::detail::sp_dereference<T>::type boost::shared_ptr<T>::operator*() const [with T = pcl::PointCloud<pcl::PointXYZ>; typename boost::detail::sp_dereference<T>::type = pcl::PointCloud<pcl::PointXYZ>&]: Assertion `px != 0' failed

在这里插入图片描述
解决:
这个问题的原因是创建pcl点云指针的时候没有初始化,应该在构造函数中进行初始化。

参考

从零开始做自动驾驶定位(四): 前端里程计之初试

这篇关于从零开始激光slam定位【1】- 激光里程计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/755914

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

CSS Anchor Positioning重新定义锚点定位的时代来临(最新推荐)

《CSSAnchorPositioning重新定义锚点定位的时代来临(最新推荐)》CSSAnchorPositioning是一项仍在草案中的新特性,由Chrome125开始提供原生支持需... 目录 css Anchor Positioning:重新定义「锚定定位」的时代来了! 什么是 Anchor Pos

详解如何使用Python从零开始构建文本统计模型

《详解如何使用Python从零开始构建文本统计模型》在自然语言处理领域,词汇表构建是文本预处理的关键环节,本文通过Python代码实践,演示如何从原始文本中提取多尺度特征,并通过动态调整机制构建更精确... 目录一、项目背景与核心思想二、核心代码解析1. 数据加载与预处理2. 多尺度字符统计3. 统计结果可

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

生信代码入门:从零开始掌握生物信息学编程技能

少走弯路,高效分析;了解生信云,访问 【生信圆桌x生信专用云服务器】 : www.tebteb.cc 介绍 生物信息学是一个高度跨学科的领域,结合了生物学、计算机科学和统计学。随着高通量测序技术的发展,海量的生物数据需要通过编程来进行处理和分析。因此,掌握生信编程技能,成为每一个生物信息学研究者的必备能力。 生信代码入门,旨在帮助初学者从零开始学习生物信息学中的编程基础。通过学习常用

js定位navigator.geolocation

一、简介   html5为window.navigator提供了geolocation属性,用于获取基于浏览器的当前用户地理位置。   window.navigator.geolocation提供了3个方法分别是: void getCurrentPosition(onSuccess,onError,options);//获取用户当前位置int watchCurrentPosition(

BIRT--商业智能和报表工具,从零开始

1.简介 BIRT (Business Intelligence and Reporting Tools), 是为 Web 应用程序开发的基于 Eclipse 的开源报表系统,特别之处在于它是以 Java 和 JavaEE 为基础。BIRT 有两个主要组件:基于 Eclipse 的报表设计器,以及部署到应用服务器上的运行时组件。 2.下载 官网下载网址:http://download.ec

flume系列之:记录一次flume agent进程被异常oom kill -9的原因定位

flume系列之:记录一次flume agent进程被异常oom kill -9的原因定位 一、背景二、定位问题三、解决方法 一、背景 flume系列之:定位flume没有关闭某个时间点生成的tmp文件的原因,并制定解决方案在博主上面这篇文章的基础上,在机器内存、cpu资源、flume agent资源都足够的情况下,flume agent又出现了tmp文件无法关闭的情况 二、

一次生产环境大量CLOSE_WAIT导致服务无法访问的定位过程

1.症状 生产环境的一个服务突然无法访问,服务的交互过程如下所示: 所有的请求都是通过网关进入,之后分发到后端服务。 现在的情况是用户服务无法访问商旅服务,网关有大量java.net.SocketTimeoutException: Read timed out报错日志,商旅服务也不断有日志打印,大多是回调和定时任务日志,所以故障点在网关和商旅服务,大概率是商旅服务无法访问导致网关超时。 后