当前位置:   article > 正文

slam定位练习笔记(一)_odom message

odom message

实现上一篇学习笔记里面的想法。

首先总结lida_localization里面test_frame_work的整体工作。

1.订阅了三个传感器数据。其中cloud_data提供当前帧的点云,gnss提供t向量,imu提供R矩阵,然后由t和R的数据整合生成transform的4×4矩阵。

2.将订阅到的点云转换为PCL格式,然后使用PCL库函数处理,其中主要是使用transform矩阵进行位置变换,然后在将点云转换为ROS格式,然后再发布出去。

3.利用前面得到的transform矩阵,发布odometry。

主要是这个流程,现在有之前跟这李太白大佬学习二维slam建图的数据lesson1.bag。看了一下包的数据格式:

 发布的话题有/laser_scan和/odom所有想着实现和上面一样的效果,每一帧点云都在转换在里程计上面。

具体流程:

1.订阅/laser_scan,并将这个转换为PCL格式。

2.订阅/odom,并获得transform。

3.利用PCL库函数将PCL格式的点云带上transform变换,然后转换回ROS格式并发布,再发布odom。

首先要创建工作空间,确定相关的依赖。

  1. mkdir -p node1/src
  2. cd node1/src
  3. catkin_init_workspace

再确定相关的依赖,直接看lida_localization里面package.xml文件内容:

  1. <buildtool_depend>catkin</buildtool_depend>
  2. <build_depend>roscpp</build_depend>
  3. <build_depend>rospy</build_depend>
  4. <build_export_depend>roscpp</build_export_depend>
  5. <build_export_depend>rospy</build_export_depend>
  6. <exec_depend>roscpp</exec_depend>
  7. <exec_depend>rospy</exec_depend>
  8. <depend>sensor_msgs</depend>
  9. <depend>pcl_ros</depend>
  10. <depend>std_msgs</depend>
  11. <depend>geometry_msgs</depend>
  12. <depend>tf</depend>
  13. <depend>eigen_conversions</depend>

依赖包有roscpp、rospy、sensor_msgs、pcl_ros、std_msgs、geometry_msgs、tf、eigen_conversions、nav_msgs。直接照抄就可以了。

一、设置PCL点云数据格式

这里直接参考lidar_localization里面cloud的数据格式。

cloud_data.hpp

  1. #ifndef FRAME_EXERCISE_SENSOR_DATA_CLOUD_DATA_HPP_
  2. #define FRAME_EXERCISE_SENSOR_DATA_CLOUD_DATA_HPP_
  3. #include <pcl/point_types.h>
  4. #include <pcl/point_cloud.h>
  5. namespace frame_exercise
  6. {
  7. class CloudData
  8. {
  9. public:
  10. using POINT = pcl::PointXYZ;
  11. using CLOUD = pcl::PointCloud<POINT>;
  12. using CLOUD_PTR = CLOUD::Ptr;
  13. public:
  14. CloudData()
  15. :cloud_ptr(new CLOUD()) {
  16. }
  17. public:
  18. double time = 0.0;
  19. CLOUD_PTR cloud_ptr;
  20. };
  21. }

 注意这里同时也要设置好cmake文件里面关于pcl的设置

  1. find_package(PCL 1.7 REQUIRED)
  2. list(REMOVE_ITEM PCL_LIBRARIES "vtkproj4")
  3. include_directories(${PCL_INCLUDE_DIRS})
  4. list(APPEND ALL_TARGET_LIBRARIES ${PCL_LIBRARIES})

二、订阅bag包发送的/laser_scan

和lidar_localization里面的处理方式一样,写单独的类,并用构造函数来订阅topic。

这里有一点点需要变换,在lidar_localization里面

它订阅的是sensor_msgs::PointCloud2::ConstPtr数据类型,

而这里订阅的是sensor_msgs/LaserScan数据类型。所以需要修改。

cloud_subscriber.hpp

  1. #ifndef FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_
  2. #define FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_
  3. #include <deque>
  4. #include <ros/ros.h>
  5. #include <sensor_msgs/PointCloud2.h>
  6. #include <pcl/point_types.h>
  7. #include <pcl/point_cloud.h>
  8. #include <pcl_conversions/pcl_conversions.h>
  9. #include "frame_exercise/sensor_data/cloud_data.hpp"
  10. namespace frame_exercise
  11. {
  12. class CloudSubscriber {
  13. public:
  14. CloudSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size);
  15. CloudSubscriber() = default;
  16. void ParseData(std::deque<CloudData>& deque_cloud_data);
  17. private:
  18. void msg_callback(const sensor_msgs::LaserScan::ConstPtr& cloud_msg_ptr);
  19. private:
  20. ros::NodeHandle nh_;
  21. ros::Subscriber subscriber_;
  22. std::deque<CloudData> new_cloud_data_;
  23. };
  24. } // namespace frame_exercise

将格式改变后,突然发现不能借助pcl库的函数进行转换了,得自己来写。。。

这里参靠李太白大佬的文章,来写将laserscan转化为pointcloud2格式。

比较简单,将参数格式一改,查询一下相关的rosmsg show就好了

 将LaserScan的格式转换为PointCloud2的格式。

cloud_subscriber.cpp:

  1. #include "frame_exercise/subscriber/cloud_subscriber.hpp"
  2. namespace frame_exercise
  3. {
  4. CloudSubscriber::CloudSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size)
  5. :nh_(nh) {
  6. subscriber_ = nh_.subscribe(topic_name, buff_size, &CloudSubscriber::msg_callback, this);
  7. }
  8. void CloudSubscriber::msg_callback(const sensor_msgs::LaserScan::ConstPtr& scan_msg) {
  9. CloudData cloud_data;
  10. cloud_data->cloud_ptr->points.resize(scan_msg->ranges.size());
  11. cloud_data.time = scan_msg->header.stamp.toSec();
  12. // pcl::fromROSMsg(*cloud_msg_ptr, *(cloud_data.cloud_ptr));
  13. // 这里不能使用这个函数,得自己手写数据转换方式
  14. for(unsigned int i = 0; i < scan_msg->ranges.size(); ++i )
  15. {
  16. pcl::PointXYZ & point_tmp = cloud_data->cloud_ptr->points[i];
  17. float range = scan_msg->ranges[i];
  18. if (!std::isfinite(range))
  19. {
  20. // std::cout << " " << i << " " << scan_msg->ranges[i];
  21. point_tmp = invalid_point_;
  22. continue;
  23. }
  24. if (range > scan_msg->range_min && range < scan_msg->range_max)
  25. {
  26. // 获取第i个点对应的角度
  27. float angle = scan_msg->angle_min + i * scan_msg->angle_increment;
  28. // 获取第i个点在笛卡尔坐标系下的坐标
  29. point_tmp.x = range * cos(angle);
  30. point_tmp.y = range * sin(angle);
  31. point_tmp.z = 0.0;
  32. }
  33. else
  34. {
  35. // 无效点
  36. point_tmp = invalid_point_;
  37. }
  38. cloud_data->cloud_ptr->width = scan_msg->ranges.size();
  39. cloud_data->cloud_ptr->height = 1;
  40. cloud_data->cloud_ptr->is_dense = false; // contains nans
  41. cloud_data->cloud_ptr->header.seq = scan_msg->header->seq;
  42. cloud_data->cloud_ptr->header.frame_id = scan_msg->header->frame_id;
  43. }
  44. new_cloud_data_.push_back(cloud_data);
  45. }
  46. void CloudSubscriber::ParseData(std::deque<CloudData>& cloud_data_buff) {
  47. if (new_cloud_data_.size() > 0) {
  48. cloud_data_buff.insert(cloud_data_buff.end(), new_cloud_data_.begin(), new_cloud_data_.end());
  49. new_cloud_data_.clear();
  50. }
  51. }
  52. } // namespace frame_exercise

三、订阅bag包发送的/odom

查看Odometry的数据格式

 思考:在订阅odometry的时候要不要也像cloud_subscriber一样添加一个专门的clouddata的格式,应该不用,我只需要提取每一帧的odom中的transform就行了,不需要对他进行专门的数据处理。

主要用到了四元数与旋转矩阵之间的转换,这篇文章写的很好,参考这篇文章提到的函数。

odom_subscriber.hpp

  1. #ifndef FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_
  2. #define FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_
  3. // 订阅到odom得到transform矩阵。
  4. #include <deque>
  5. #include <ros/ros.h>
  6. #include <nav_msgs/Odometry.h>
  7. #include <Eigen/Dense>
  8. namespace frame_exercise
  9. {
  10. class OdomSubscriber{
  11. public:
  12. OdomSubscriber(ros::NodeHandle& nh_, std::string topic_name, std::size_t buff_size);
  13. std::deque<Eigen::Matrix4f> odom_transform;
  14. private:
  15. void msg_callback(const nav_msgs::Odometry::ConstPtr& odom_msg);
  16. private:
  17. ros::NodeHandle nh_;
  18. ros::Subscriber subscriber_;
  19. };
  20. } // namespace frame_exercise

特地写了一个用于存放odomtransform的队列。

OdomSubscriber.cpp

  1. #include "frame_exercise/subscriber/odom_subscriber.hpp"
  2. namespace frame_exercise
  3. {
  4. OdomSubscriber::OdomSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size):
  5. nh_(nh){
  6. subscriber_ = nh_.subscriber(topic_name, buff_size, &OdomSubscriber::msg_callback, this);
  7. }
  8. void OdomSubscriber::msg_callback(const nav_msgs::Odometry::ConstPtr& odom_msg)
  9. {
  10. Eigen::Maxtrix4f transform;
  11. // t向量
  12. transform(0, 3) = odom_msg.pose.pose.position.x;
  13. transform(1, 3) = odom_msg.pose.pose.position.y;
  14. transform(2, 3) = odom_msg.pose.pose.position.z;
  15. // R旋转矩阵
  16. Eigen::Quaternionf q;
  17. q.x() = odom_msg.pose.pose.orientation.x;
  18. q.y() = odom_msg.pose.pose.orientation.y;
  19. q.z() = odom_msg.pose.pose.orientation.z;
  20. transform.block<3,3>(0,0) = q.matrix();
  21. odom_transfrom.push_back(transform);
  22. }
  23. } // namespace frame_exercise

感觉应该没一个函数都要测试一下能不能正常使用,达到我想要的效果,不能这样一股闹的全部写完再测试。

四、发布环节

主要应用到函数:

pcl::transformPointCloud(*cloud_data.cloud_ptr, *cloud_data.cloud_ptr, odometry_matrix);

现在的问题是odometry_matrix这个已经存在了,但不知道怎么提取出来。

还是采用了lidar_localization的框架,在里面单独使用一个队列,然后在callback函数里面处理这个队列。

里面遇到了很多坑,都是我对于pcl没有特别理解的后果。。。

a.回调函数里面关于引用的处理要带上*。

b.关于头文件相互包含的问题。(这个错误卡了我一个下午,最后发现是宏定义出了问题,我当时直接复制了cloud_subscriber.hpp的宏定义!!!)

直接将发布环节写在frame_exercise.cpp里面了:

  1. #include "ros/ros.h"
  2. #include "pcl/common/transforms.h"
  3. #include <pcl_ros/point_cloud.h>
  4. #include <sensor_msgs/PointCloud2.h>
  5. #include <nav_msgs/Odometry.h>
  6. #include "frame_exercise/subscriber/cloud_subscriber.hpp"
  7. #include "frame_exercise/subscriber/odom_subscriber.hpp"
  8. using namespace frame_exercise;
  9. int main(int argc, char** argv)
  10. {
  11. ros::init(argc, argv, "test_frame");
  12. ros::NodeHandle nh;
  13. ros::Publisher pub_odom;
  14. ros::Publisher pub_cloud;
  15. // pub_odom = nh.advertise<nav_msgs::Odometry>("/odom1", 10);
  16. pub_cloud = nh.advertise<sensor_msgs::PointCloud2>("/cloud1", 10);
  17. std::shared_ptr<CloudSubscriber> cloud_sub_ptr = std::make_shared<CloudSubscriber>(nh, "/laser_scan", 1000);
  18. std::shared_ptr<OdomSubscriber> odom_sub_ptr = std::make_shared<OdomSubscriber>(nh, "/odom", 1000);
  19. std::deque<CloudData> cloud_data_buff;
  20. std::deque<Eigen::Matrix4f> transform_buff;
  21. ros::Rate rate(100);
  22. while(ros::ok())
  23. {
  24. ros::spinOnce();
  25. cloud_sub_ptr->ParseData(cloud_data_buff);
  26. odom_sub_ptr->ParseData(transform_buff);
  27. while(cloud_data_buff.size() > 0 && transform_buff.size())
  28. {
  29. CloudData cloud_data = cloud_data_buff.front();
  30. Eigen::Matrix4f odometry_matrix = transform_buff.front();
  31. cloud_data_buff.pop_front();
  32. transform_buff.pop_front();
  33. pcl::transformPointCloud(*cloud_data.cloud_ptr, *cloud_data.cloud_ptr, odometry_matrix);
  34. // 如果想要发布odom的话,还需要定义一个nav_msgs::Odometry对象,太复杂了,先不写了。
  35. pub_cloud.publish(cloud_data.cloud_ptr);
  36. }
  37. rate.sleep();
  38. }
  39. return 0;
  40. }

最开始编译的时候 会报这个错误,想了很久也不知道是哪里的问题,最后是看了这篇文章解决。

 然后就可以成功编译。

但显示明明生成了可执行文件,但还是rosrun的时候报错说没有。

 我在devel的lib中没有发现,但在build里面的frame_exercise文件里面找到了。导致rosrun的时候找不到可执行文件,解决这个问题主要看了这篇文章

然后发现了很多bug,暂时解决不了。先列举一下发现的bug:

1.四元数是有w的。

2.发布需要变成rosmsg格式的,要改进。

3.pcl::transformPointCloud(*cloud_data.cloud_ptr, *cloud_data.cloud_ptr, odometry_matrix);运行到这里就会报错,不知道是哪里的问题。

先挖个坑,以后在解决这些问题。

实践出真知

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/454392
推荐阅读
相关标签
  

闽ICP备14008679号