当前位置:   article > 正文

从零开始学自动驾驶(1)——软件框架篇_从零开始自动驾驶

从零开始自动驾驶

 # 在此首先感谢任佬的无私分享,有兴趣的同学可以去任佬的知乎进行学习

从零开始做自动驾驶定位

注:遇到不清不楚的地方一定要立马解决,总想着以后解决,以后不懂的越来越多就会焦虑想放弃,希望这句话能时刻提醒自己

 

对应任佬代码tag4.0,后续会对代码不断的改进,任佬说一套好的代码要从最简单的开始,所以我准备一步一步进行优化,也是对不怎么熟练的C++学习。

CMakeLists部分


首先学习CMakeLists,这部分包含了整体框架的结构,是学习工程很好的一个切入点

  1. cmake_minimum_required(VERSION 2.8.3)
  2. project(lidar_localization)
  3. #Release:发布版本进行优化 Debug:调试版本不进行优化
  4. SET(CMAKE_BUILD_TYPE "Release")
  5. SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
  6. #使用C++11
  7. #为当前路径和下层路径的目标增加编译器命令行选项
  8. add_compile_options(-std=c++11)
  9. add_definitions(-std=c++11)
  10. #发现依赖包
  11. find_package(catkin REQUIRED COMPONENTS
  12. roscpp
  13. rospy
  14. std_msgs
  15. pcl_ros
  16. geometry_msgs
  17. tf
  18. eigen_conversions
  19. )
  20. #将所有的依赖进行打包,修改后也随着变化
  21. set(ALL_TARGET_LIBRARIES "")
  22. #将文件放入cmake文件下,方便管理,在cmake文件中在去找对应的包
  23. include(cmake/glog.cmake)
  24. include(cmake/PCL.cmake)
  25. include(cmake/eigen.cmake)
  26. include(cmake/geographic.cmake)
  27. include_directories(include ${catkin_INCLUDE_DIRS})
  28. include(cmake/global_defination.cmake)
  29. catkin_package()
  30. #简化代码
  31. #整合所有的cpp文件 到 ALL_SRCS
  32. file(GLOB_RECURSE ALL_SRCS "*.cpp")
  33. #整合所有的src/*_node.cpp文件 到 NODE_SRCS
  34. file(GLOB_RECURSE NODE_SRCS "src/*_node.cpp")
  35. #整合所有的third_party/*.cpp文件 到 THIRD_PARTY_SRCS
  36. file(GLOB_RECURSE THIRD_PARTY_SRCS "third_party/*.cpp")
  37. #在 ALL_SRCS 剔除 NODE_SRCS THIRD_PARTY_SRCS 所包含的内容
  38. list(REMOVE_ITEM ALL_SRCS ${NODE_SRCS})
  39. list(REMOVE_ITEM ALL_SRCS ${THIRD_PARTY_SRCS})
  40. #最终需要编译的就是两个运行节点
  41. #加入上面打包好的依赖,很方便使用
  42. add_executable(test_frame_node src/test_frame_node.cpp ${ALL_SRCS})
  43. target_link_libraries(test_frame_node ${catkin_LIBRARIES} ${ALL_TARGET_LIBRARIES})
  44. add_executable(front_end_node src/front_end_node.cpp ${ALL_SRCS})
  45. target_link_libraries(front_end_node ${catkin_LIBRARIES} ${ALL_TARGET_LIBRARIES})

根据CMakeLists.txt的说明,主要运行节点为front_end_node.cpp,下面我们一起看看

front_end_node主要流程


  1. /*
  2. * @Description:
  3. * @Author: Ren Qian
  4. * @Date: 2020-02-05 02:56:27
  5. */
  6. #include <ros/ros.h>
  7. #include <pcl/common/transforms.h>
  8. #include "glog/logging.h"
  9. #include "lidar_localization/global_defination/global_defination.h"
  10. #include "lidar_localization/subscriber/cloud_subscriber.hpp"
  11. #include "lidar_localization/subscriber/imu_subscriber.hpp"
  12. #include "lidar_localization/subscriber/gnss_subscriber.hpp"
  13. #include "lidar_localization/tf_listener/tf_listener.hpp"
  14. #include "lidar_localization/publisher/cloud_publisher.hpp"
  15. #include "lidar_localization/publisher/odometry_publisher.hpp"
  16. #include "lidar_localization/front_end/front_end.hpp"
  17. using namespace lidar_localization;
  18. int main(int argc, char *argv[]) {
  19. //初始化日志函数 接受一个char*类型的参数也就是argv[0]
  20. google::InitGoogleLogging(argv[0]);
  21. //FLAGS_log_dir设置日志输出目录
  22. FLAGS_log_dir = WORK_SPACE_PATH + "/Log";
  23. //是否将所有日志输出到文件 这里选择是
  24. FLAGS_alsologtostderr = 1;
  25. //初始化ros节点 与 句柄
  26. ros::init(argc, argv, "front_end_node");
  27. ros::NodeHandle nh;
  28. //接收点云、imu、gnss、雷达与imu之间的标定信息话题
  29. //make_shared<CloudSubscriber>: make_shared使用过程就是调用CloudSubscriber中的模板函数推断出创建类型
  30. //shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制 计数为0是自动释放内存
  31. //std::make_shared 一次性为int对象和用于引用计数的数据都分配了内存,而new操作符只是为int分配了内存。
  32. std::shared_ptr<CloudSubscriber> cloud_sub_ptr = std::make_shared<CloudSubscriber>(nh, "/kitti/velo/pointcloud", 100000);
  33. std::shared_ptr<IMUSubscriber> imu_sub_ptr = std::make_shared<IMUSubscriber>(nh, "/kitti/oxts/imu", 1000000);
  34. std::shared_ptr<GNSSSubscriber> gnss_sub_ptr = std::make_shared<GNSSSubscriber>(nh, "/kitti/oxts/gps/fix", 1000000);
  35. std::shared_ptr<TFListener> lidar_to_imu_ptr = std::make_shared<TFListener>(nh, "velo_link", "imu_link");
  36. //发布当前扫描、局部地图、全局地图、激光里程计、guss里程计话题
  37. std::shared_ptr<CloudPublisher> cloud_pub_ptr = std::make_shared<CloudPublisher>(nh, "current_scan", 100, "/map");
  38. std::shared_ptr<CloudPublisher> local_map_pub_ptr = std::make_shared<CloudPublisher>(nh, "local_map", 100, "/map");
  39. std::shared_ptr<CloudPublisher> global_map_pub_ptr = std::make_shared<CloudPublisher>(nh, "global_map", 100, "/map");
  40. std::shared_ptr<OdometryPublisher> laser_odom_pub_ptr = std::make_shared<OdometryPublisher>(nh, "laser_odom", "map", "lidar", 100);
  41. std::shared_ptr<OdometryPublisher> gnss_pub_ptr = std::make_shared<OdometryPublisher>(nh, "gnss", "map", "lidar", 100);
  42. std::shared_ptr<FrontEnd> front_end_ptr = std::make_shared<FrontEnd>();
  43. //定义用来保存点云 imu gnss 当前时刻的信息容器
  44. //CloudData为保存点云的类
  45. std::deque<CloudData> cloud_data_buff;
  46. std::deque<IMUData> imu_data_buff;
  47. std::deque<GNSSData> gnss_data_buff;
  48. //初始化lidar与imu坐标系之间的转化关系
  49. Eigen::Matrix4f lidar_to_imu = Eigen::Matrix4f::Identity();
  50. bool transform_received = false;
  51. bool gnss_origin_position_inited = false;
  52. bool front_end_pose_inited = false;
  53. //定义类指针 用来存储局部地图 全局地图 当前帧扫描点云
  54. CloudData::CLOUD_PTR local_map_ptr(new CloudData::CLOUD());
  55. CloudData::CLOUD_PTR global_map_ptr(new CloudData::CLOUD());
  56. CloudData::CLOUD_PTR current_scan_ptr(new CloudData::CLOUD());
  57. double run_time = 0.0;
  58. double init_time = 0.0;
  59. bool time_inited = false;
  60. bool has_global_map_published = false;
  61. //设置循环频率100hz
  62. ros::Rate rate(100);
  63. while (ros::ok()) {
  64. ros::spinOnce();
  65. //把新点云、imu、gnss数据保存到cloud_data_buff imu_data_buff gnss_data_buff
  66. cloud_sub_ptr->ParseData(cloud_data_buff);
  67. imu_sub_ptr->ParseData(imu_data_buff);
  68. gnss_sub_ptr->ParseData(gnss_data_buff);
  69. //导入lidar与imu位姿关系
  70. if (!transform_received) {
  71. //以旋转矩阵的形式表示出
  72. if (lidar_to_imu_ptr->LookupData(lidar_to_imu)) {
  73. transform_received = true;
  74. }
  75. } else {
  76. while (cloud_data_buff.size() > 0 && imu_data_buff.size() > 0 && gnss_data_buff.size() > 0) {
  77. //提取第一个数据
  78. CloudData cloud_data = cloud_data_buff.front();
  79. IMUData imu_data = imu_data_buff.front();
  80. GNSSData gnss_data = gnss_data_buff.front();
  81. //如果时间没有初始化,则设置第一个点云数据时间为初始时间
  82. //已经初始了则计算点云与初始时间的差值
  83. if (!time_inited) {
  84. time_inited = true;
  85. init_time = cloud_data.time;
  86. } else {
  87. run_time = cloud_data.time - init_time;
  88. }
  89. //保证点云数据与imu、gnss数据时间同步
  90. double d_time = cloud_data.time - imu_data.time;
  91. if (d_time < -0.05) {
  92. cloud_data_buff.pop_front();
  93. } else if (d_time > 0.05) {
  94. imu_data_buff.pop_front();
  95. gnss_data_buff.pop_front();
  96. } else {
  97. cloud_data_buff.pop_front();
  98. imu_data_buff.pop_front();
  99. gnss_data_buff.pop_front();
  100. Eigen::Matrix4f odometry_matrix = Eigen::Matrix4f::Identity();
  101. //初始化gnss数据
  102. if (!gnss_origin_position_inited) {
  103. gnss_data.InitOriginPosition();
  104. gnss_origin_position_inited = true;
  105. }
  106. //更新gnss数据 转化到imu坐标系下,并发布
  107. gnss_data.UpdateXYZ();
  108. odometry_matrix(0,3) = gnss_data.local_E;
  109. odometry_matrix(1,3) = gnss_data.local_N;
  110. odometry_matrix(2,3) = gnss_data.local_U;
  111. odometry_matrix.block<3,3>(0,0) = imu_data.GetOrientationMatrix();
  112. odometry_matrix *= lidar_to_imu;
  113. gnss_pub_ptr->Publish(odometry_matrix);
  114. //如果位姿没有初始化,使用gnss第一帧位姿进行初始化
  115. if (!front_end_pose_inited) {
  116. front_end_pose_inited = true;
  117. front_end_ptr->SetInitPose(odometry_matrix);
  118. }
  119. //用里程计去预测位姿
  120. front_end_ptr->SetPredictPose(odometry_matrix);
  121. //更新点云信息并发布
  122. Eigen::Matrix4f laser_matrix = front_end_ptr->Update(cloud_data);
  123. laser_odom_pub_ptr->Publish(laser_matrix);
  124. front_end_ptr->GetCurrentScan(current_scan_ptr);
  125. cloud_pub_ptr->Publish(current_scan_ptr);
  126. //发布局部地图
  127. if (front_end_ptr->GetNewLocalMap(local_map_ptr))
  128. local_map_pub_ptr->Publish(local_map_ptr);
  129. }
  130. //时间超过460秒,则发布一次最新的全局地图
  131. if (run_time > 460.0 && !has_global_map_published) {
  132. if (front_end_ptr->GetNewGlobalMap(global_map_ptr)) {
  133. global_map_pub_ptr->Publish(global_map_ptr);
  134. has_global_map_published = true;
  135. }
  136. }
  137. }
  138. }
  139. rate.sleep();
  140. }
  141. return 0;
  142. }

 各个部分都模块化是很容易阅读和扩展的一种方式,下面以点云数据为例

点云类型的定义模块



  1. namespace lidar_localization {
  2. class CloudData {
  3. public:
  4. using POINT = pcl::PointXYZ;
  5. //多个点组成点云容器
  6. using CLOUD = pcl::PointCloud<POINT>;
  7. //指向点云容器的指针
  8. using CLOUD_PTR = CLOUD::Ptr;
  9. public:
  10. //cloud_ptr(new CLOUD()) 等价于 cloud_ptr = new CLOUD 给cloud_ptr 分配内存
  11. //cloud_ptr后面就是指向点云容器的指针 cloud_ptr是后面用到的
  12. CloudData()
  13. :cloud_ptr(new CLOUD()) {
  14. }
  15. public:
  16. double time = 0.0;
  17. CLOUD_PTR cloud_ptr;
  18. };
  19. }

 点云话题发布者模块


  1. namespace lidar_localization {
  2. CloudPublisher::CloudPublisher(ros::NodeHandle& nh,
  3. std::string topic_name,
  4. size_t buff_size,
  5. std::string frame_id)
  6. //用nh_代替nh,frame_id_代替frame_id
  7. :nh_(nh), frame_id_(frame_id) {
  8. //发布者格式
  9. publisher_ = nh_.advertise<sensor_msgs::PointCloud2>(topic_name, buff_size);
  10. }
  11. void CloudPublisher::Publish(CloudData::CLOUD_PTR cloud_ptr_input) {
  12. sensor_msgs::PointCloud2Ptr cloud_ptr_output(new sensor_msgs::PointCloud2());
  13. //转化成ros格式进行发布
  14. pcl::toROSMsg(*cloud_ptr_input, *cloud_ptr_output);
  15. //提取时间辍,ros::Time::now()随当前时间变化
  16. cloud_ptr_output->header.stamp = ros::Time::now();
  17. cloud_ptr_output->header.frame_id = frame_id_;
  18. publisher_.publish(*cloud_ptr_output);
  19. }
  20. } // namespace data_output

点云话题订阅者模块



  1. namespace lidar_localization {
  2. //订阅者接受数据函数
  3. CloudSubscriber::CloudSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size)
  4. :nh_(nh) {
  5. subscriber_ = nh_.subscribe(topic_name, buff_size, &CloudSubscriber::msg_callback, this);
  6. }
  7. //回调函数接受点云数据,并把点云从ros格式转化为pcl格式,然后存储在new_cloud_data_中
  8. void CloudSubscriber::msg_callback(const sensor_msgs::PointCloud2::ConstPtr& cloud_msg_ptr) {
  9. //cloud_data类 包含时间信息和点云容器
  10. CloudData cloud_data;
  11. cloud_data.time = cloud_msg_ptr->header.stamp.toSec();
  12. pcl::fromROSMsg(*cloud_msg_ptr, *(cloud_data.cloud_ptr));
  13. new_cloud_data_.push_back(cloud_data);
  14. }
  15. //用于数据的传输,把新来的点云数据保存到当前点云数据中
  16. //cloud_data_buff由很多CloudData类组成的容器
  17. void CloudSubscriber::ParseData(std::deque<CloudData>& cloud_data_buff) {
  18. if (new_cloud_data_.size() > 0) {
  19. //新来的点云被插入在尾部
  20. cloud_data_buff.insert(cloud_data_buff.end(), new_cloud_data_.begin(), new_cloud_data_.end());
  21. //清空接受新点云的内存
  22. new_cloud_data_.clear();
  23. }
  24. }
  25. } // namespace data_input

 GNSS定义模块


主要作用就是把经纬高坐标系转化为东北天ENU坐标系

  1. namespace lidar_localization {
  2. class GNSSData {
  3. public:
  4. double time = 0.0;
  5. //经度 维度 高度
  6. double longitude = 0.0;
  7. double latitude = 0.0;
  8. double altitude = 0.0;
  9. //RTK获得的经纬高坐标要转换为东北天坐标,才能用于局部的导航和定位
  10. //东北天(ENU)坐标系
  11. //U轴垂直于过原点的参 考椭球面,指向天顶;N与U轴相互垂直,N轴指向为参考椭球短半轴;E 轴完成左手系。
  12. double local_E = 0.0;
  13. double local_N = 0.0;
  14. double local_U = 0.0;
  15. int status = 0;
  16. int service = 0;
  17. private:
  18. static GeographicLib::LocalCartesian geo_converter;
  19. static bool origin_position_inited;
  20. public:
  21. void InitOriginPosition();
  22. void UpdateXYZ();
  23. };
  24. }

 GNSS坐标系转化实现模块


  1. //静态成员变量必须在类外初始化
  2. bool lidar_localization::GNSSData::origin_position_inited = false;
  3. //经纬度原点初始化
  4. GeographicLib::LocalCartesian lidar_localization::GNSSData::geo_converter;
  5. namespace lidar_localization {
  6. void GNSSData::InitOriginPosition() {
  7. //经纬度原点初始化
  8. //Reset 函数的作用是重置原点,LocalCartesian构造函数是默认在(0,0,0)也就是地心
  9. geo_converter.Reset(latitude, longitude, altitude);
  10. origin_position_inited = true;
  11. }
  12. void GNSSData::UpdateXYZ() {
  13. if (!origin_position_inited) {
  14. LOG(WARNING) << "GeoConverter has not set origin position";
  15. }
  16. //经纬度转ENU
  17. //Forward (lat, lon, alt, x, y, z)函数就是把经纬高转换为ENU,前三个传入,后三个传出
  18. geo_converter.Forward(latitude, longitude, altitude, local_E, local_N, local_U);
  19. }
  20. }

 imu定义模块

  1. namespace lidar_localization {
  2. class IMUData {
  3. public:
  4. //线速度
  5. struct LinearAcceleration {
  6. double x = 0.0;
  7. double y = 0.0;
  8. double z = 0.0;
  9. };
  10. //角速度
  11. struct AngularVelocity {
  12. double x = 0.0;
  13. double y = 0.0;
  14. double z = 0.0;
  15. };
  16. //角度
  17. struct Orientation {
  18. double x = 0.0;
  19. double y = 0.0;
  20. double z = 0.0;
  21. double w = 0.0;
  22. };
  23. double time = 0.0;
  24. LinearAcceleration linear_acceleration;
  25. AngularVelocity angular_velocity;
  26. Orientation orientation;
  27. public:
  28. // 把四元数转换成旋转矩阵送出去
  29. Eigen::Matrix3f GetOrientationMatrix() {
  30. Eigen::Quaterniond q(orientation.w, orientation.x, orientation.y, orientation.z);
  31. Eigen::Matrix3f matrix = q.matrix().cast<float>();
  32. return matrix;
  33. }
  34. };
  35. }

tf坐标变换姿态变换模块


  1. namespace lidar_localization {
  2. TFListener::TFListener(ros::NodeHandle& nh, std::string base_frame_id, std::string child_frame_id)
  3. :nh_(nh), base_frame_id_(base_frame_id), child_frame_id_(child_frame_id) {
  4. }
  5. bool TFListener::LookupData(Eigen::Matrix4f& transform_matrix) {
  6. try {
  7. tf::StampedTransform transform;
  8. //ros::Time(0):查找最近两个时间坐标,计算坐标关系
  9. //child_frame_id_相对于base_frame_id_的坐标关系
  10. listener_.lookupTransform(base_frame_id_, child_frame_id_, ros::Time(0), transform);
  11. //姿态关系转化成4x4旋转矩阵
  12. TransformToMatrix(transform, transform_matrix);
  13. return true;
  14. } catch (tf::TransformException &ex) {
  15. return false;
  16. }
  17. }
  18. bool TFListener::TransformToMatrix(const tf::StampedTransform& transform, Eigen::Matrix4f& transform_matrix) {
  19. //定义平移变量
  20. Eigen::Translation3f tl_btol(transform.getOrigin().getX(), transform.getOrigin().getY(), transform.getOrigin().getZ());
  21. double roll, pitch, yaw;
  22. //定义旋转变量
  23. tf::Matrix3x3(transform.getRotation()).getEulerYPR(yaw, pitch, roll);
  24. Eigen::AngleAxisf rot_x_btol(roll, Eigen::Vector3f::UnitX());
  25. Eigen::AngleAxisf rot_y_btol(pitch, Eigen::Vector3f::UnitY());
  26. Eigen::AngleAxisf rot_z_btol(yaw, Eigen::Vector3f::UnitZ());
  27. // 此矩阵为 child_frame_id 到 base_frame_id 的转换矩阵 得到4x4旋转矩阵
  28. transform_matrix = (tl_btol * rot_z_btol * rot_y_btol * rot_x_btol).matrix();
  29. return true;
  30. }
  31. }

 

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

闽ICP备14008679号