structPoseGraphData { /* struct InternalSubmapData { std::shared_ptr<const Submap> submap; SubmapState state = SubmapState::kNoConstraintSearch; // IDs of the nodes that were inserted into this map together with // constraints for them. They are not to be matched again when this submap // becomes 'kFinished'. std::set<NodeId> node_ids; }; */
// Submaps get assigned an ID and state as soon as they are seen, even // before they take part in the background computations. MapById<SubmapId, InternalSubmapData> submap_data;
// Global submap poses currently used for displaying data. MapById<SubmapId, optimization::SubmapSpec2D> global_submap_poses_2d; MapById<SubmapId, optimization::SubmapSpec3D> global_submap_poses_3d;
// Data that are currently being shown. MapById<NodeId, TrajectoryNode> trajectory_nodes;
// Global landmark poses with all observations. std::map<std::string /* landmark ID */, PoseGraphInterface::LandmarkNode> landmark_nodes; // How our various trajectories are related. TrajectoryConnectivityState trajectory_connectivity_state; int num_trajectory_nodes = 0; std::map<int, InternalTrajectoryState> trajectories_state;
// Set of all initial trajectory poses. std::map<int, PoseGraph::InitialTrajectoryPose> initial_trajectory_poses;
structInternalSubmapData { std::shared_ptr<const Submap> submap; SubmapState state = SubmapState::kNoConstraintSearch; // IDs of the nodes that were inserted into this map together with // constraints for them. They are not to be matched again when this submap // becomes 'kFinished'. std::set<NodeId> node_ids; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
structConstraint { structPose { transform::Rigid3d zbar_ij; double translation_weight; double rotation_weight; }; SubmapId submap_id; // 'i' in the paper. NodeId node_id; // 'j' in the paper. // Pose of the node 'j' relative to submap 'i'. Pose pose; /* Differentiates between intra-submap (where node 'j' was inserted into submap 'i') and inter-submap constraints (where node 'j' was not inserted into submap 'i').*/ enumTag { INTRA_SUBMAP, INTER_SUBMAP } tag; };
// Rays begin at 'origin'. 'returns' are the points where obstructions were // detected. 'misses' are points in the direction of rays for which no return // was detected, and were inserted at a configured distance // It is assumed that between the 'origin' and 'misses' is free space structRangeData { Eigen::Vector3f origin; PointCloud returns; PointCloud misses; };
using PointCloud = std::vector<RangefinderPoint>; // Stores 3D position of a point observed by a rangefinder sensor. structRangefinderPoint { Eigen::Vector3f position; };
structTrajectoryNode { structData { common::Time time; // Transform to approximately gravity align the tracking frame as // determined by local SLAM. Eigen::Quaterniond gravity_alignment; // Used for loop closure in 2D: voxel filtered returns in the // 'gravity_alignment' frame. sensor::PointCloud filtered_gravity_aligned_point_cloud; // 省略 3D.
// The node pose in the local SLAM frame. transform::Rigid3d local_pose; }; common::Time time()const{ return constant_data->time; } // This must be a shared_ptr. If the data is used for visualization while the // node is being trimmed, it must survive until all use finishes. std::shared_ptr<const Data> constant_data; // The node pose in the global SLAM frame. transform::Rigid3d global_pose; };
// An individual submap, which has a 'local_pose' in the local map frame, // keeps track of how many range data were inserted into it // and sets 'insertion_finished' when the map no longer changes // and is ready for loop closing classSubmap { public: Submap(const transform::Rigid3d& local_submap_pose) : local_pose_(local_submap_pose) {}
private: // Pose of this submap in the local map frame const transform::Rigid3d local_pose_; // Number of RangeData inserted. int num_range_data_ = 0; bool insertion_finished_ = false; };
sensor::TimedPointCloudOriginData RangeDataCollator::AddRangeData( const std::string& sensor_id, const sensor::TimedPointCloudData& timed_point_cloud_data) { // 检测该点云数据 sensor_id 是否在期望的sensor_ids里面 CHECK_NE(expected_sensor_ids_.count(sensor_id), 0); // TODO(gaschler): These two cases can probably be one. // 此传感器类型数据已有 if (id_to_pending_data_.count(sensor_id) != 0) { current_start_ = current_end_; // When we have two messages of the same sensor, move forward the older of // the two (do not send out current). /* 如果当前融合时间段内融合结果中已包含同一ID的传感器数据,则应采用最新的点云数据进行替换 但是结束的时间戳仍然采用原来的时间刻度,开始进行数据截取合并CropAndMerge()并返回 */ // std::map<std::string, sensor::TimedPointCloudData> id_to_pending_data_; current_end_ = id_to_pending_data_.at(sensor_id).time; auto result = CropAndMerge(); id_to_pending_data_.emplace(sensor_id, timed_point_cloud_data); return result; } id_to_pending_data_.emplace(sensor_id, timed_point_cloud_data); // 若现在收到的数据类型未全,即期望收到种类未全,直接退出,无需融合 if (expected_sensor_ids_.size() != id_to_pending_data_.size()) { return {}; } current_start_ = current_end_; // We have messages from all sensors, move forward to oldest. common::Time oldest_timestamp = common::Time::max(); // 找传感器数据中最早的时间戳 for (constauto& pair : id_to_pending_data_) { oldest_timestamp = std::min(oldest_timestamp, pair.second.time); } /* current_end_是下次融合的开始时间,是本次融合的最后时间刻度 但其实是此次融合所有传感器中最早的时间戳*/ current_end_ = oldest_timestamp; returnCropAndMerge(); }
在把一帧的scan插入到子图之前,scan 相对子图的位姿是由 ceres scan matcher 优化获得的。由于这是一个实时的局部优化,需要一个好的初始位姿估计。前面的real time CSM就是把位姿估计器传来的预测值更新为一个好的初值,如果没有real time CSM,就还用位姿估计器传来的预测值作初值。Ceres Scan Matcher以初值作为先验,并找到最佳的点,该点就是通过scan match获得的在子地图中的位置,实现方式是 interpolating the submap and sub-pixel aligning the scan. 前端的两个scan matcher其实都是 scan to map 问题,让当前观测和已知环境最匹配
scan matcher 负责在地图中找到一个scan pose,使得在该位姿下所有 hit 点在 local map中的占用概率之和最大,于是转化为一个最小二乘问题:
实时相关性scan match 以预测值为中心,在搜索窗口内枚举出所有候选位姿,并生成每个位姿对应的点云,也就是说按照 把当前观测 投影到栅格地图 m (t 时刻的激光数据与 t-1 时刻的地图匹配, scan to map),然后分别计算每个点云中hit点在栅格地图的CellIndex,并计算所有hit点的占据概率之和,概率最大值对应的位姿就是我们需要的。代表着在这个位姿下,激光点得映射到占据栅格中的可能性最大,当前观测与已知环境最为一致,前后帧匹配成功。 scan match非常重要,如果结果位姿很差,建图就会失败,后端怎么调整也没用
if (polygon->points.size()==1 && obstacle->radius > 0) // Circle { obstacles_.push_back(ObstaclePtr(newCircularObstacle(polygon->points[0].x, polygon->points[0].y, obstacle->radius))); } elseif (polygon->points.size()==1) // Point { obstacles_.push_back(ObstaclePtr(newPointObstacle(polygon->points[0].x, polygon->points[0].y))); } elseif (polygon->points.size()==2) // Line { obstacles_.push_back(ObstaclePtr(newLineObstacle(polygon->points[0].x, polygon->points[0].y, polygon->points[1].x, polygon->points[1].y ))); } elseif (polygon->points.size()>2) // Real polygon { PolygonObstacle* polyobst = new PolygonObstacle; for (std::size_t j=0; j<polygon->points.size(); ++j) { polyobst->pushBackVertex(polygon->points[j].x, polygon->points[j].y); } polyobst->finalizePolygon(); obstacles_.push_back(ObstaclePtr(polyobst)); }
// Set velocity, if obstacle is moving if(!obstacles_.empty()) obstacles_.back()->setCentroidVelocity(obstacles->obstacles[i].velocities, obstacles->obstacles[i].orientation); } }
teb_markers (visualization_msgs/Marker) teb_local_planner通过具有不同名称空间的标记来提供规划场景的其他信息。 Namespaces PointObstacles and PolyObstacles: visualize all point and polygon obstacles that are currently considered during optimization. Namespace TebContainer: Visualize all found and optimized trajectories that rest in alternative topologies (only if parallel planning is enabled). Some more information is published such as the optimization footprint model
dt_ref: 0.3 局部路径规划的解析度; Temporal resolution of the planned trajectory (usually it is set to the magnitude of the 1/control_rate)。 TEB通过状态搜索树寻找最优路径,而dt_ref则是最优路径上的两个相邻姿态(即位置、速度、航向信息,可通过TEB可视化在rivz中看到)的默认距离。 此距离不固定,规划器自动根据速度大小调整这一距离,速度越大,相邻距离自然越大,较小的值理论上可提供更高精度。 从autoResize函数可以看到,当相邻时间差距离(TimeDiff(i) )和dt_ref的差超过正负dt_hysteresis时,规划器将改变这一距离。 增大到0.4,teb_pose个数大大减少,车速明显加快,多数接近最大速度
oscillation_recovery: 默认true. Try to detect and resolve oscillations between multiple solutions in the same equivalence class (robot frequently switches between left/right/forward/backwards
[ WARN] Clearing costmap to unstuck robot(3.00 m). [ WARN] TebLocalPlannerROS: trajectory is not feasible. Resetting planner... [ WARN] Rotate recovery behavior started. # 开始振荡 RotateRecovery::runBehavior() [ INFO] TebLocalPlannerROS: oscillation recovery disabled/expired. [ WARN] TebLocalPlannerROS: possible oscillation(of the robot or its local plan) detected. Activating recovery strategy(prefer current turning direction during optimization).
obstacle_association_force_inclusion_factor: 10.0 the obstacles that will be taken into account are those closer than min_obstacle_dist * factor, if legacy is false
obstacle_association_cutoff_factor: 40.0 the obstacles that are further than min_obstacle_dist * factor will not be taken into account, if legacy is false
simple_exploration 默认false. 如果为true,不同的 trajectories are explored using a simple left-right approach (pass each obstacle on the left or right side) for path generation, otherwise sample possible roadmaps randomly in a specified region between start and goal.
ros::Time last_error = ros::Time::now(); std::string tf_error; //确保机器人global_frame_和robot_base_frame_之间的有效转换, 否则一直停留在这里阻塞 // 这里已经写死为 0.1,有需要可以改变 while (ros::ok() && !tf_.waitForTransform(global_frame_, robot_base_frame_, ros::Time(), ros::Duration(0.1), ros::Duration(0.01), &tf_error)) { ros::spinOnce(); // 如果5秒内未成功tf转换,报警 if (last_error + ros::Duration(5.0) < ros::Time::now()) { ROS_WARN("Timed out waiting for transform from %s to %s to become available before running costmap, tf error: %s", robot_base_frame_.c_str(), global_frame_.c_str(), tf_error.c_str()); last_error = ros::Time::now(); } // The error string will accumulate and errors will typically be the same, so the last // will do for the warning above. Reset the string here to avoid accumulation. tf_error.clear(); } //检测是否需要“窗口滚动”,从参数服务器获取以下参数 bool rolling_window, track_unknown_space, always_send_full_costmap; private_nh.param("rolling_window", rolling_window, false); private_nh.param("track_unknown_space", track_unknown_space, false); private_nh.param("always_send_full_costmap", always_send_full_costmap, false);
有时出现了下面的报错 这就是因为上面的0.1太小了
LayeredCostmap类的对象
layered_costmap_ = new LayeredCostmap(global_frame_, rolling_window, track_unknown_space);