• 关于地图map的部分

1. 地图的定义:定义全局变量map;

static pcl::PointCloud<pcl::PointXYZI> map;

2. 在main函数中,创建与地图有关的发布者对象ndt_map_pub,发布的消息名称为:/ndt_map;

  ndt_map_pub = nh.advertise<sensor_msgs::PointCloud2>("/ndt_map", 1000);

3. 在main函数中,创建一个订阅者对象output_sub,订阅话题config/ndt_mapping_output,输该输入是一个地图map,并通过回调函数output_callback进行操作;

  ros::Subscriber output_sub = nh.subscribe("config/ndt_mapping_output", 10, output_callback);

4. 在output_callback函数中,主要目的是对输入的地图进行滤波处理,体素滤波完后,再利用前面创建的发布者对象ndt_map_pub将地图发布出去,消息名称为:/ndt_map。(tips: 因为节点句柄为 nh,因此该消息的命名空间就是:/话题名);

pcl::toROSMsg(*map_filtered, *map_msg_ptr);
// 发布之前先进行从pcl数据格式到ros消息类型的转换
ndt_map_pub.publish(*map_msg_ptr);
  • 处理每一帧点云的points_callback函数

1. 在主函数中进行定义,创建订阅者对象points_sub,用于接收每一帧点云数据points_raw,并通过函数points_callback对点云进行处理;

ros::Subscriber points_sub = nh.subscribe("points_raw", 100000, points_callback);

看一下该回调函数(此函数是ndt_mapping节点的最主要部分,代码量也很多,450~850一共400多行),此函数的输入是points_raw,即input:

static void points_callback(const sensor_msgs::PointCloud2::ConstPtr& input)

2. 首先对点云进行过远点和过近点的滤除,得到新的点云scan_ptr;

// 对输入每一帧点云的操作
static void points_callback(const sensor_msgs::PointCloud2::ConstPtr& input)
{double r;pcl::PointXYZI p;pcl::PointCloud<pcl::PointXYZI> tmp, scan;// 用于接收从雷达消息过来的pcl数据格式的转换pcl::PointCloud<pcl::PointXYZI>::Ptr filtered_scan_ptr(new pcl::PointCloud<pcl::PointXYZI>());pcl::PointCloud<pcl::PointXYZI>::Ptr transformed_scan_ptr(new pcl::PointCloud<pcl::PointXYZI>());tf::Quaternion q;Eigen::Matrix4f t_localizer(Eigen::Matrix4f::Identity());Eigen::Matrix4f t_base_link(Eigen::Matrix4f::Identity());static tf::TransformBroadcaster br;tf::Transform transform;current_scan_time = input->header.stamp;pcl::fromROSMsg(*input, tmp);for (pcl::PointCloud<pcl::PointXYZI>::const_iterator item = tmp.begin(); item != tmp.end(); item++){p.x = (double)item->x;p.y = (double)item->y;p.z = (double)item->z;p.intensity = (double)item->intensity;r = sqrt(pow(p.x, 2.0) + pow(p.y, 2.0));// 滤除掉距离过近或过远的点,存到新的点云scan中if (min_scan_range < r && r < max_scan_range){scan.push_back(p);}}// 点云scan转换为智能指针scan_ptrpcl::PointCloud<pcl::PointXYZI>::Ptr scan_ptr(new pcl::PointCloud<pcl::PointXYZI>(scan));

3. 检查地图是否完成了初始化,如果没有,就利用初始的变换(零变换)对第一帧点云进行变换,变换后的点云为transformed_scan_ptr,再将transformed_scan_ptr加入到地图map中,完成第一帧对地图的初始化;

// 检查地图是否进行初始化,也即是否为空if (initial_scan_loaded == 0){pcl::transformPointCloud(*scan_ptr, *transformed_scan_ptr, tf_btol);// 如果map为空,则将新的点云scan_ptr作为输入点云,tf_btol作为变换(全局变量),变换后的点云为transformed_scan_ptrmap += *transformed_scan_ptr;// 再将transformed_scan_ptr加入到地图map中,完成第一帧对地图的初始化initial_scan_loaded = 1;}

4. 随后再对点云进行体素滤波处理,滤波完成后的点云为filtered_scan_ptr;

// 运用体素滤波对每一帧点云进行滤波,滤波完成后的点云为filtered_scan_ptrpcl::VoxelGrid<pcl::PointXYZI> voxel_grid_filter;voxel_grid_filter.setLeafSize(voxel_leaf_size, voxel_leaf_size, voxel_leaf_size);voxel_grid_filter.setInputCloud(scan_ptr);voxel_grid_filter.filter(*filtered_scan_ptr);

5. 配置点云配准的参数;

  pcl::PointCloud<pcl::PointXYZI>::Ptr map_ptr(new pcl::PointCloud<pcl::PointXYZI>(map));// 进行点云配准的方法与参数:迭代步长、分辨率、次数以及输入的源点云filtered_scan_ptrif (_method_type == MethodType::PCL_GENERIC){ndt.setTransformationEpsilon(trans_eps);ndt.setStepSize(step_size);ndt.setResolution(ndt_res);ndt.setMaximumIterations(max_iter);ndt.setInputSource(filtered_scan_ptr);}

6. 用于配准的输入点云的变化:如下图所示;

输入的一帧点云经过上述变换后,才成为ndt配准的点云;

7. 用于迭代的初始位姿计算,以没有odom没有imu的情况为例:

迭代的初始位姿 = 上一时刻的位姿 + 变化量

变化量diff采用匀速运动模型进行计算:

变化量diff = 当前位姿current_pose - 上一时刻位姿previous_pose

 // 用于迭代的初始位姿 = 上一时刻的位姿 + 变化量// guess_pose为静态的全局变量,只在此文件内起作用// 关于几个pose:guess_pose, guess_pose_imu, guess_pose_odom, guess_pose_imu_odom// 分别为没有任何设备下的迭代初始位姿和只有imu、只有里程计和有imu和里程计的迭代初始位姿guess_pose.x = previous_pose.x + diff_x;// diff_x = current_pose.x - previous_pose.x; 匀速运动模型// previous_pose为通过配准得到的位姿guess_pose.y = previous_pose.y + diff_y;guess_pose.z = previous_pose.z + diff_z;guess_pose.roll = previous_pose.roll;guess_pose.pitch = previous_pose.pitch;guess_pose.yaw = previous_pose.yaw + diff_yaw;

将用于迭代的位姿映射到ndt算法中:

  pose guess_pose_for_ndt;if (_use_imu == true && _use_odom == true)guess_pose_for_ndt = guess_pose_imu_odom;else if (_use_imu == true && _use_odom == false)guess_pose_for_ndt = guess_pose_imu;else if (_use_imu == false && _use_odom == true)guess_pose_for_ndt = guess_pose_odom;elseguess_pose_for_ndt = guess_pose;// 将没有任何先验信息的位姿估计赋值给guess_pose_for_ndt

8. 用于坐标变换的矩阵:

变换矩阵4*4 = 平移向量 * 旋转向量 * 缩放向量

其中旋转向量通过欧拉角来定义,公式如下:

Eigen::AngleAxisf init_rotation_x(guess_pose_for_ndt.roll, Eigen::Vector3f::UnitX());Eigen::AngleAxisf init_rotation_y(guess_pose_for_ndt.pitch, Eigen::Vector3f::UnitY());Eigen::AngleAxisf init_rotation_z(guess_pose_for_ndt.yaw, Eigen::Vector3f::UnitZ());Eigen::Translation3f init_translation(guess_pose_for_ndt.x, guess_pose_for_ndt.y, guess_pose_for_ndt.z);// 构建迭代初始位姿的坐标变换矩阵4*4: 平移向量*旋转向量*缩放向量Eigen::Matrix4f init_guess =(init_translation * init_rotation_z * init_rotation_y * init_rotation_x).matrix() * tf_btol;

至此,用于ndt_mapping算法的所有准备工作就绪

9. 进行点云配准:

pcl::PointCloud<pcl::PointXYZI>::Ptr output_cloud(new pcl::PointCloud<pcl::PointXYZI>);if (_method_type == MethodType::PCL_GENERIC){ndt.align(*output_cloud, init_guess);fitness_score = ndt.getFitnessScore();t_localizer = ndt.getFinalTransformation();has_converged = ndt.hasConverged();final_num_iteration = ndt.getFinalNumIteration();transformation_probability = ndt.getTransformationProbability();}

其中,输入点云经过点云配准,由init_guess变换为矩阵output_cloud,这一过程的变换矩阵存储于t_localizer中,t_localizer表示从map到雷达定位器的坐标转换;

t_localizer = anh_ndt.getFinalTransformation();// 保存坐标变换的矩阵,变换为map到雷达坐标系

为了计算小车底盘到map的变换,需要引入车底盘到雷达定位器的坐标变换:

  t_base_link = t_localizer * tf_ltob;// t_base_link为map到base_link的坐标变换// tf_ltob为base_link到雷达坐标系的变换

10. 地图拼接

得到了坐标变换后,将输入点云通过该变换,变换到map中,实现地图的拼接;

pcl::transformPointCloud(*scan_ptr, *transformed_scan_ptr, t_localizer);// transformed_scan_ptr为scan_ptr经过t_localizer变换后的点云// 这一步主要是将点云变换后,拼接到map中

11. 更新,主要是用于更新下一次的计算初值;

// 更新雷达定位器的位姿xyz,定位localizer_pose.x = t_localizer(0, 3);localizer_pose.y = t_localizer(1, 3);localizer_pose.z = t_localizer(2, 3);mat_l.getRPY(localizer_pose.roll, localizer_pose.pitch, localizer_pose.yaw, 1);// 更新NDT位姿,NDT建图位姿ndt_pose.x = t_base_link(0, 3);ndt_pose.y = t_base_link(1, 3);ndt_pose.z = t_base_link(2, 3);mat_b.getRPY(ndt_pose.roll, ndt_pose.pitch, ndt_pose.yaw, 1);// 将NDT建图位姿映射为当前位姿current_pose.x = ndt_pose.x;current_pose.y = ndt_pose.y;current_pose.z = ndt_pose.z;current_pose.roll = ndt_pose.roll;current_pose.pitch = ndt_pose.pitch;current_pose.yaw = ndt_pose.yaw;diff_x = current_pose.x - previous_pose.x;diff_y = current_pose.y - previous_pose.y;diff_z = current_pose.z - previous_pose.z;diff_yaw = calcDiffForRadian(current_pose.yaw, previous_pose.yaw);diff = sqrt(diff_x * diff_x + diff_y * diff_y + diff_z * diff_z);current_velocity_x = diff_x / secs;current_velocity_y = diff_y / secs;current_velocity_z = diff_z / secs;// 更新操作,将当前位姿存储为上一时刻位姿,用于下一次计算的点云配准迭代过程初始化previous_pose.x = current_pose.x;previous_pose.y = current_pose.y;previous_pose.z = current_pose.z;previous_pose.roll = current_pose.roll;previous_pose.pitch = current_pose.pitch;previous_pose.yaw = current_pose.yaw;

12. 计算配准偏移量shift: 当前时刻和上一时刻之间的偏移距离,目的是:

  • 距离过近时,不往地图上添加,一方面频繁的添加使得计算量偏大,而地图更新不明显;
  • 此外,当小车静止不动时,不往map中添加点云;
// 计算added_pos和current_pos之间的偏移,也即当前时刻和上一时刻之间的偏移距离,欧式距离double shift = sqrt(pow(current_pose.x - added_pose.x, 2.0) + pow(current_pose.y - added_pose.y, 2.0));if (shift >= min_add_scan_shift)// 默认值:static double min_add_scan_shift = 1.0;// 距离过近时,不往地图上添加,一方面频繁的添加使得计算量偏大,而地图更新不明显;// 此外,当小车静止不动时,不往map中添加点云{map += *transformed_scan_ptr;// 当前点云帧与map进行拼接// 更新操作,用于下一时刻计算距离偏移量added_pose.x = current_pose.x;added_pose.y = current_pose.y;added_pose.z = current_pose.z;added_pose.roll = current_pose.roll;added_pose.pitch = current_pose.pitch;added_pose.yaw = current_pose.yaw;

13. 发布消息:

  // 数据类型转换为消息类型,为发布消息做准备sensor_msgs::PointCloud2::Ptr map_msg_ptr(new sensor_msgs::PointCloud2);pcl::toROSMsg(*map_ptr, *map_msg_ptr);ndt_map_pub.publish(*map_msg_ptr);// 发布地图消息current_pose_pub.publish(current_pose_msg);// 发布当前位姿

Autoware:ndt_mapping节点相关推荐

  1. autoware下ndt_mapping节点解读

    利用激光雷达进行建图,首先需要得到稠密点云,然后进行体素滤波进行过滤得到包含特征的点云数据.接着利用每一帧扫描的点云地图进行ndt配准逐帧拼接,最后能够得到激光雷达扫描路径下的整体点云地图. ndt_ ...

  2. 【Autoware】Ubuntu 18.04 ssdcaffe安装与Autoware 检测节点运行

    Ubuntu 18.04 + 1.14 Autoware USE CPU&GPU 安装caffe 系统依赖项 获取ssd版caffe 编译前工作 Makefile.config Makefil ...

  3. 【Autoware】Autoware安装教程

    如果嫌弃c站,可移步博客园:只要主题不崩 问题不大: https://www.cnblogs.com/kin-zhang/p/16984139.html 前提:大家需要换源[软件源和pip源]:git ...

  4. 【导航业务框架】开源无人驾驶项目autoware解读

    系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录 前言 一.Autoware的整体框架和模块 1.Autoware介绍 2. ...

  5. 如何让罗技29方向盘像视频中的那样转动起来?

    ​​​​​​​[vlog]Autoware Carla G29 自动驾驶仿真_哔哩哔哩_bilibili 话接上文,在我之前一篇博客中已经讲解了如何给罗技29方向盘装上力反馈,也就是在拨动方向盘的时候 ...

  6. 2021秋招学习笔记

    PS:csdn上有很多图片加载不出来,有PDF版在我的资源.(如果没有1积分可以评论我,直接发给你邮箱) 文章目录 Java基础篇学习(7/3-7/4) 数据类型 泛型.反射.注解.序列化(加实例) ...

  7. 【Autoware规控】mpc_follower模型预测控制节点

    文章目录 1. 技术原理 2. 代码实现 1. 技术原理 MPC,即Model Predictive Control(模型预测控制),是一种基于动态模型的控制算法.MPC算法通过建立系统的数学模型,根 ...

  8. autoware lidar_localizer包下的ndt_matching节点的学习

    autoware lidar_localizer包下的ndt_matching节点的学习 激光雷达定位 自身定位对于自动驾驶汽车来说非常重要,常见的机器人定位方法有视觉slam方法(即时定位与地图构建 ...

  9. 【Autoware规控】Lattice规划节点

    文章目录 1. Lattice规划介绍 2. 相关代码 1. Lattice规划介绍 Lattice Planner 是一种基于栅格地图的规划算法,通过搜索和优化实现路径规划的目的.Lattice P ...

最新文章

  1. 2019年上半年收集到的人工智能LSTM干货文章
  2. 定义一个计算字符串有效长度的_一个正方形的小抽屉柜,根据设计草图计算出所需四片木板的长度...
  3. 日志规范之slf4j整合JDK14以及Simple的使用
  4. orcale中case when和group by同时使用会报无效标识符
  5. 面试官十大常问面试问题总结
  6. ORM框架使用优缺点
  7. 层次分析法软件操作步骤(yaahp)
  8. QT自定义控件-经纬度输入框
  9. 优秀流程图和逻辑图画法的分析和借鉴
  10. 在vscode中使用iconfont阿里字体图标
  11. c语言hypot函数,hypot()函数以及C ++中的示例
  12. 32 《奇特的一生》 -豆瓣评分8.5
  13. 从程序中学习UKF-SLAM(二)
  14. 测试网络SNMP连接的几个方法(我平时调试SNMP程序时用到的几个解决方案)
  15. L1-020 帅到没朋友(c++包含测试点)
  16. sublimelinter_开发人员使用SublimeLinter指南
  17. 【译】 WebP 支持:超出你想象
  18. 数据结构之(二叉)堆
  19. Java实验报告(一)
  20. 两款不错的网络电视录制软件

热门文章

  1. cas607-34-1|5-硝基喹啉|5-Nitroquinoline淡黄色晶体
  2. 鸢尾花数据集的线性多分类
  3. 网络攻防技术(郑大信安个人总结版)
  4. 史元春老师组20-21年论文笔记
  5. mac删除分区并合并分区
  6. 80后年薪多少,才能摆脱中年危机?
  7. 魔兽mdx文件导出为Ogre Mesh的小进展
  8. Opencv Python 综合练习1---读取银行卡卡号
  9. C++怎么操作EXCEL
  10. kernel panic分析