ROS2入门到精通—— 2-6 ROS2实战:可调节纯跟踪算法(局部规划)

在这里插入图片描述

1 Regulated Pure Pursuit

纯追踪算法变体:调节纯追踪算法

将自适应纯追踪(Adaptive Pure Pursuit)算法的特性与围绕线性速度的规则相结合,重点关注消费类、工业和服务型机器人的需求。我们还实现了几种常识性的安全机制,如碰撞检测

这个Regulated Pure Pursuit控制器实现了主动碰撞检测。使用一个参数来设置在当前速度命令上可能发生潜在碰撞之前的最大允许时间。利用当前的线性和角速度,我们在将来的这段时间内进行投影,并检查是否存在碰撞。

直观地看,机器人和预瞄点之间进行碰撞检查是合理的,然而,如果你在狭窄空间中操纵,只搜索一定时间范围内是有意义的,这样可以给系统一些余地来解决问题。特别是在狭窄空间中,我们希望确保为当前正在执行的动作进行合理范围的碰撞检查(例如,以0.1米/秒移动时,向前查找到预瞄点10米甚至100秒的未来是没有意义的);这有助于在高速度/角速度下进一步查看,在受限制的环境中缓慢移动,就不会因为靠近障碍物的合理运动被过度报告碰撞。如果将最大允许时间设置为一个较大的数字,它将在预瞄点之前进行碰撞检查,但不会超过该点。我们将这种碰撞检测弧线可视化在lookahead_arc话题上

调节纯追踪算法还利用了纯追踪算法的常见变体。实现了自适应纯追踪算法的主要特点,即基于速度缩放的前瞻点距离。这有助于使控制器在更大范围的潜在线性速度下更加稳定。我们设置了前瞻增益(或前瞻时间)的参数,并对最小和最大值进行了阈值化。

最后一项改进是在接近目标时减速,最佳前瞻距离为X,我们可以计算实际前瞻点距离与X之间的差值,从而得到前瞻点误差。在操作过程中,这种误差的变化应该异常地小,并且不会被触发。然而,在路径结束时,没有点距离机器人有一个前瞻距离,因此它会使用路径上的最后一个点。因此,随着机器人接近目标,其误差会增大,并且机器人的速度会按比例减小,直到达到最小阈值。这也受到运动学速度限制的跟踪,以确保可驾驶性。

这项工作实施的主要改进是基于一些成本函数对线性速度进行调节。这些函数的选择旨在消除Pure Pursuit算法中长期存在的不良行为。普通的Pure Pursuit算法在特别高曲率(或曲率极快变化)的环境中存在超调和较差的处理能力的问题。这会导致机器人超出路径,并有可能与环境发生碰撞。Regulated Pure Pursuit算法中的这些成本函数也是基于服务、商业和工业用例中移动机器人的常见需求和需求而选择的;

通过曲率缩放可以形成直观的行为,使机器人在进行急转弯时减速,并在靠近潜在碰撞物时减速,以防止小的变化导致碰撞障碍物。这在部分可观测的环境中非常有用(比如经常在过道/走廊间转弯),可以在进入未知动态环境之前减速,更保守地假设有可能立即需要停车的障碍物。

成本函数根据机器人与障碍物的接近程度和路径的曲率对其速度进行惩罚。这有助于在狭窄空间中接近物体时减缓机器人的速度,并通过曲率缩减线性速度有助于稳定控制器在更大范围的预瞄点距离上。这还有一个额外的好处,即消除了对预瞄点/范围的敏感调整,因为机器人将更好地跟踪路径。仍然需要进行调整,但通过微小调整可以更容易地获得合理的行为。

通过曲率缩减线性速度的一个意外的第三个好处是,当使用不与机器人姿势方向对齐的全向规划器时,机器人将自然旋转到粗糙的路径航向。由于曲率会非常高,线性速度下降,角速度接管旋转到航向的任务。虽然不完美,但确实大大减少了在跟随路径之前需要旋转到接近路径航向的需求,并扩大了规划技术的广泛范围。否则,纯追踪控制器甚至在相对狭窄的空间中都无法从中恢复。

通过将距离和曲率调节的线性速度与时间缩放的碰撞检测器结合起来,我们可以看到几乎完美的组合,使得调节后的Pure Pursuit算法能够处理路径上的高起始偏差,并且在狭窄空间中无需超调即可避开碰撞

Tip: 允许的最大碰撞时间由观测点作为阈值进行限制,这样做是为了确保碰撞检测不会明显超出路径,这可能会在受限环境中引发问题

举例来说,如果有一条直线路径指向一堵墙,然后向左转,若该参数设置过高,那么它将在实际机器人意图运动点之后检测到碰撞。因此,如果机器人移动速度较快,选择更远的观测点不仅是Pure Pursuit算法行为稳定性的问题,同时也赋予机器人更强大的预测性碰撞检测能力。最大允许时间参数仍然适用于慢速指令

2 Regulated Pure Pursuit相关参数

参数名描述
desired_linear_vel期望线速度
lookahead_dist预瞄距离
min_lookahead_dist当使用速度缩放预瞄距离的最小预瞄距离
max_lookahead_dist当使用速度缩放预瞄距离的最大预瞄距离
lookahead_time通过投影速度找到速度缩放预瞄距离的时间,称为预瞄距离系数
rotate_to_heading_angular_vel如果使用旋转到航向,则使用的角速度
transform_toleranceTF变换容差
use_velocity_scaled_lookahead_dist是否使用速度缩放的前瞻距离或常量前瞻距离
min_approach_linear_velocity靠近目标时应用的最小速度阈值
approach_velocity_scaling_dist从变换路径末端开始应用速度缩放的累积距离。默认为代价地图正向范围减去一个代价地图单元长度
use_collision_detection是否启用碰撞检测
max_allowed_time_to_collision_up_to_carrot在启用碰撞检测时,检查碰撞的最大时间。限制为所选前瞻距离的最大距离
use_regulated_linear_velocity_scaling是否使用曲率的调节功能
use_cost_regulated_linear_velocity_scaling是否使用障碍物接近的调节功能
cost_scaling_dist触发线性速度缩放的障碍物最小距离。如果启用了use_cost_regulated_linear_velocity_scaling,则设置的值应小于或等于代价地图膨胀层中设置的膨胀半径,因为膨胀用于计算距离障碍物的距离
cost_scaling_gain乘法增益,应<= 1.0,用于在障碍物处于cost_scaling_dist内时进一步缩放速度。较低的值会更快地降低速度
inflation_cost_scaling_factor本地代价地图膨胀层中设置的cost_scaling_factor的值。为了使用膨胀单元值准确计算距离障碍物,该值应完全相同
regulated_linear_scaling_min_radius触发调节功能的转弯半径。请记住,转弯越急,半径越小
regulated_linear_scaling_min_speed调节功能可以发送的最小速度,以确保即使在成本高且曲率高的空间中仍然可以实现进程
use_fixed_curvature_lookahead启用曲率检测的固定前瞻。对于具有长前瞻的系统非常有用
curvature_lookahead_dist用于确定速度调节目的的曲率的前瞻距离。仅当启用use_fixed_curvature_lookahead时使用
use_rotate_to_heading是否在使用全向规划器时启用旋转到粗略航向和目标方向。建议对除了阿克曼类型之外的所有机器人类型都启用
rotate_to_heading_min_angle路径方向与起始机器人方向之间的差异,触发原地旋转的最小角度,如果启用use_rotate_to_heading
max_angular_accel在旋转到航向时允许的最大角加速度
max_robot_pose_search_dist沿路径绑定机器人最近姿态的最大集成距离。默认情况下,它设置为最大代价地图范围,因此除非本地代价地图中有循环,否则不应手动设置

参数配置:

controller_server:ros__parameters:use_sim_time: Truecontroller_frequency: 20.0min_x_velocity_threshold: 0.001min_y_velocity_threshold: 0.5min_theta_velocity_threshold: 0.001progress_checker_plugin: "progress_checker"goal_checker_plugin: "goal_checker"controller_plugins: ["FollowPath", "Testing"]progress_checker:plugin: "nav2_controller::SimpleProgressChecker"required_movement_radius: 0.5movement_time_allowance: 10.0goal_checker:plugin: "nav2_controller::SimpleGoalChecker"xy_goal_tolerance: 0.25yaw_goal_tolerance: 0.25stateful: TrueFollowPath:plugin: "nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController"desired_linear_vel: 0.25max_linear_accel: 1.5max_linear_decel: 1.5lookahead_dist: 0.40min_lookahead_dist: 0.3max_lookahead_dist: 0.6lookahead_time: 2.0rotate_to_heading_angular_vel: 1.8transform_tolerance: 0.1use_velocity_scaled_lookahead_dist: falsemin_approach_linear_velocity: 0.05use_approach_linear_velocity_scaling: truemax_allowed_time_to_collision: 1.0use_regulated_linear_velocity_scaling: trueuse_cost_regulated_linear_velocity_scaling: falseregulated_linear_scaling_min_radius: 0.9regulated_linear_scaling_min_speed: 0.25use_rotate_to_heading: trueallow_reversing: falserotate_to_heading_min_angle: 0.785max_angular_accel: 3.2

3 源码解析

regulated_pure_pursuit_controller.hpp

#ifndef NAV2_REGULATED_PURE_PURSUIT_CONTROLLER__REGULATED_PURE_PURSUIT_CONTROLLER_HPP_
#define NAV2_REGULATED_PURE_PURSUIT_CONTROLLER__REGULATED_PURE_PURSUIT_CONTROLLER_HPP_#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <mutex>#include "nav2_costmap_2d/footprint_collision_checker.hpp"
#include "nav2_core/controller.hpp"
#include "rclcpp/rclcpp.hpp"
#include "pluginlib/class_loader.hpp"
#include "pluginlib/class_list_macros.hpp"
#include "nav2_util/odometry_utils.hpp"
#include "nav2_util/geometry_utils.hpp"
#include "geometry_msgs/msg/pose2_d.hpp"namespace nav2_regulated_pure_pursuit_controller
{// 自适应纯跟踪插件
class RegulatedPurePursuitController : public nav2_core::Controller
{
public:RegulatedPurePursuitController() = default;~RegulatedPurePursuitController() override = default;// 配置void configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,std::string name, std::shared_ptr<tf2_ros::Buffer> tf,std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros) override;void cleanup() override;void activate() override;void deactivate() override;/*** @brief 在给定当前姿态和速度的情况下,使用可能的调试信息计算最佳命令* @param pose      当前机器人姿态* @param velocity  当前机器人速度* @param goal_checker   发送给此任务的目标检查器,以防在计算命令时有用* @return          最佳控制命令*/geometry_msgs::msg::TwistStamped computeVelocityCommands(const geometry_msgs::msg::PoseStamped & pose,const geometry_msgs::msg::Twist & velocity,nav2_core::GoalChecker * /*goal_checker*/) override;// 设置全局路径void setPlan(const nav_msgs::msg::Path & path) override;/*** @brief 限制机器人的最大线速度* @param speed_limit 以绝对值(m/s)表示或以最大机器人速度的百分比表示* @param percentage 如果为真,则以百分比设置速度限制或者在错误情况下为绝对值*/void setSpeedLimit(const double & speed_limit, const bool & percentage) override;protected:/*** @brief 将全局路径变换到机器人坐标下* 如果出现以下任何一种情况,则没有资格被选为前瞻点:在local_costmap之外(无法保证避免碰撞)* @param pose 姿势转换* @return 新帧中的路径*/nav_msgs::msg::Path transformGlobalPlan(const geometry_msgs::msg::PoseStamped & pose);// 转换pose到另一个frame// in_pose: 输入帧// out_pose: 输出帧bool transformPose(const std::string frame,const geometry_msgs::msg::PoseStamped & in_pose,geometry_msgs::msg::PoseStamped & out_pose) const;// 根据当前速度获取预瞄距离// 传入线速度等比例获取前视距离double getLookAheadDistance(const geometry_msgs::msg::Twist &);// 可视化预瞄点// 输入参数:输入carrot点std::unique_ptr<geometry_msgs::msg::PointStamped> createCarrotMsg(const geometry_msgs::msg::PoseStamped & carrot_pose);// 机器人是否应该旋转到粗糙路径航向bool shouldRotateToPath(const geometry_msgs::msg::PoseStamped & carrot_pose, double & angle_to_path);// 机器人是否应该旋转到最终目标方向bool shouldRotateToGoalHeading(const geometry_msgs::msg::PoseStamped & carrot_pose);// 创建平滑且运动学平滑的旋转命令void rotateToHeading(double & linear_vel, double & angular_vel,const double & angle_to_path, const geometry_msgs::msg::Twist & curr_speed);/*** @brief Whether collision is imminent* @param robot_pose Pose of robot* @param carrot_pose Pose of carrot* @param linear_vel linear velocity to forward project* @param angular_vel angular velocity to forward project* @param carrot_dist Distance to the carrot for PP* @return Whether collision is imminent*/// 碰撞是否很急bool isCollisionImminent(const geometry_msgs::msg::PoseStamped &,const double &, const double &,const double &);// 在投影姿态下检查碰撞情况bool inCollision(const double & x,const double & y,const double & theta);/*** @brief Cost at a point* @param x Pose of pose x* @param y Pose of pose y* @return Cost of pose in costmap*/// 一个点的代价double costAtPose(const double & x, const double & y);// 控制机器人距离终点的时的线速度缩放,通过剩余距离来线性插值线速度的大小double approachVelocityScalingFactor(const nav_msgs::msg::Path & path) const;void applyApproachVelocityScaling(const nav_msgs::msg::Path & path,double & linear_vel) const;/*** @brief apply regulation constraints to the system* @param linear_vel robot command linear velocity input* @param lookahead_dist optimal lookahead distance* @param curvature curvature of path* @param speed Speed of robot* @param pose_cost cost at this pose*/// 应用一些调节约束到系统上void applyConstraints(const double & curvature, const geometry_msgs::msg::Twist & speed,const double & pose_cost, const nav_msgs::msg::Path & path,double & linear_vel, double & sign);/*** @brief Find the intersection a circle and a line segment.* This assumes the circle is centered at the origin.* If no intersection is found, a floating point error will occur.* @param p1 first endpoint of line segment* @param p2 second endpoint of line segment* @param r radius of circle* @return point of intersection*/// 找到线和圆的交点static geometry_msgs::msg::Point circleSegmentIntersection(const geometry_msgs::msg::Point & p1,const geometry_msgs::msg::Point & p2,double r);/*** @brief Get lookahead point* @param lookahead_dist Optimal lookahead distance* @param path Current global path* @return Lookahead point*/// 获得预瞄点// param: 前视距离、当前全局路径geometry_msgs::msg::PoseStamped getLookAheadPoint(const double &, const nav_msgs::msg::Path &);// 检查cusp位置,返回机器人与尖端的距离double findVelocitySignChange(const nav_msgs::msg::Path & transformed_plan);// 从中心获取成本图的最大范围(以米为单位),返回从中心到成本图边缘的最大距离double getCostmapMaxExtent() const;// 检测到参数更改时执行回调rcl_interfaces::msg::SetParametersResultdynamicParametersCallback(std::vector<rclcpp::Parameter> parameters);rclcpp_lifecycle::LifecycleNode::WeakPtr node_;std::shared_ptr<tf2_ros::Buffer> tf_;std::string plugin_name_;std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros_;nav2_costmap_2d::Costmap2D * costmap_;rclcpp::Logger logger_ {rclcpp::get_logger("RegulatedPurePursuitController")};rclcpp::Clock::SharedPtr clock_;double desired_linear_vel_, base_desired_linear_vel_;double lookahead_dist_;double rotate_to_heading_angular_vel_;double max_lookahead_dist_;double min_lookahead_dist_;double lookahead_time_;bool use_velocity_scaled_lookahead_dist_;tf2::Duration transform_tolerance_;double min_approach_linear_velocity_;double approach_velocity_scaling_dist_;double control_duration_;double max_allowed_time_to_collision_up_to_carrot_;bool use_collision_detection_;bool use_regulated_linear_velocity_scaling_;bool use_cost_regulated_linear_velocity_scaling_;double cost_scaling_dist_;double cost_scaling_gain_;double inflation_cost_scaling_factor_;double regulated_linear_scaling_min_radius_;double regulated_linear_scaling_min_speed_;bool use_rotate_to_heading_;double max_angular_accel_;double rotate_to_heading_min_angle_;double goal_dist_tol_;bool allow_reversing_;double max_robot_pose_search_dist_;bool use_interpolation_;nav_msgs::msg::Path global_plan_;std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<nav_msgs::msg::Path>> global_path_pub_;std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<geometry_msgs::msg::PointStamped>>carrot_pub_;std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<nav_msgs::msg::Path>> carrot_arc_pub_;std::unique_ptr<nav2_costmap_2d::FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>collision_checker_;// Dynamic parameters handlerstd::mutex mutex_;rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr dyn_params_handler_;
};}  // namespace nav2_regulated_pure_pursuit_controller#endif  // NAV2_REGULATED_PURE_PURSUIT_CONTROLLER__REGULATED_PURE_PURSUIT_CONTROLLER_HPP_

regulated_pure_pursuit_controller.cpp

#include <algorithm>
#include <string>
#include <limits>
#include <memory>
#include <vector>
#include <utility>#include "nav2_regulated_pure_pursuit_controller/regulated_pure_pursuit_controller.hpp"
#include "nav2_core/exceptions.hpp"
#include "nav2_util/node_utils.hpp"
#include "nav2_util/geometry_utils.hpp"
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"using std::hypot;
using std::min;
using std::max;
using std::abs;
using nav2_util::declare_parameter_if_not_declared;
using nav2_util::geometry_utils::euclidean_distance;
using namespace nav2_costmap_2d;  // NOLINT
using rcl_interfaces::msg::ParameterType;namespace nav2_regulated_pure_pursuit_controller
{void RegulatedPurePursuitController::configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,std::string name, std::shared_ptr<tf2_ros::Buffer> tf,std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
{// 首先对配置参数加互斥锁auto node = parent.lock();node_ = parent;if (!node) {throw nav2_core::PlannerException("Unable to lock node!");}costmap_ros_ = costmap_ros;costmap_ = costmap_ros_->getCostmap();tf_ = tf;plugin_name_ = name;logger_ = node->get_logger();clock_ = node->get_clock();double transform_tolerance = 0.1;double control_frequency = 20.0;goal_dist_tol_ = 0.15;  // reasonable default before first update// 初始化参数declare_parameter_if_not_declared(node, plugin_name_ + ".desired_linear_vel", rclcpp::ParameterValue(0.25));declare_parameter_if_not_declared(node, plugin_name_ + ".lookahead_dist", rclcpp::ParameterValue(0.5));declare_parameter_if_not_declared(node, plugin_name_ + ".min_lookahead_dist", rclcpp::ParameterValue(0.3));declare_parameter_if_not_declared(node, plugin_name_ + ".max_lookahead_dist", rclcpp::ParameterValue(0.7));declare_parameter_if_not_declared(node, plugin_name_ + ".lookahead_time", rclcpp::ParameterValue(1.5));declare_parameter_if_not_declared(node, plugin_name_ + ".rotate_to_heading_angular_vel", rclcpp::ParameterValue(1.8));declare_parameter_if_not_declared(node, plugin_name_ + ".transform_tolerance", rclcpp::ParameterValue(0.1));declare_parameter_if_not_declared(node, plugin_name_ + ".use_velocity_scaled_lookahead_dist",rclcpp::ParameterValue(false));declare_parameter_if_not_declared(node, plugin_name_ + ".min_approach_linear_velocity", rclcpp::ParameterValue(0.05));declare_parameter_if_not_declared(node, plugin_name_ + ".approach_velocity_scaling_dist",rclcpp::ParameterValue(0.6));declare_parameter_if_not_declared(node, plugin_name_ + ".max_allowed_time_to_collision_up_to_carrot",rclcpp::ParameterValue(1.0));declare_parameter_if_not_declared(node, plugin_name_ + ".use_collision_detection",rclcpp::ParameterValue(true));declare_parameter_if_not_declared(node, plugin_name_ + ".use_regulated_linear_velocity_scaling", rclcpp::ParameterValue(true));declare_parameter_if_not_declared(node, plugin_name_ + ".use_cost_regulated_linear_velocity_scaling",rclcpp::ParameterValue(true));declare_parameter_if_not_declared(node, plugin_name_ + ".cost_scaling_dist", rclcpp::ParameterValue(0.6));declare_parameter_if_not_declared(node, plugin_name_ + ".cost_scaling_gain", rclcpp::ParameterValue(1.0));declare_parameter_if_not_declared(node, plugin_name_ + ".inflation_cost_scaling_factor", rclcpp::ParameterValue(3.0));declare_parameter_if_not_declared(node, plugin_name_ + ".regulated_linear_scaling_min_radius", rclcpp::ParameterValue(0.90));declare_parameter_if_not_declared(node, plugin_name_ + ".regulated_linear_scaling_min_speed", rclcpp::ParameterValue(0.25));declare_parameter_if_not_declared(node, plugin_name_ + ".use_rotate_to_heading", rclcpp::ParameterValue(true));declare_parameter_if_not_declared(node, plugin_name_ + ".rotate_to_heading_min_angle", rclcpp::ParameterValue(0.785));declare_parameter_if_not_declared(node, plugin_name_ + ".max_angular_accel", rclcpp::ParameterValue(3.2));declare_parameter_if_not_declared(node, plugin_name_ + ".allow_reversing", rclcpp::ParameterValue(false));declare_parameter_if_not_declared(node, plugin_name_ + ".max_robot_pose_search_dist",rclcpp::ParameterValue(getCostmapMaxExtent()));declare_parameter_if_not_declared(node, plugin_name_ + ".use_interpolation",rclcpp::ParameterValue(true));node->get_parameter(plugin_name_ + ".desired_linear_vel", desired_linear_vel_);base_desired_linear_vel_ = desired_linear_vel_;node->get_parameter(plugin_name_ + ".lookahead_dist", lookahead_dist_);node->get_parameter(plugin_name_ + ".min_lookahead_dist", min_lookahead_dist_);node->get_parameter(plugin_name_ + ".max_lookahead_dist", max_lookahead_dist_);node->get_parameter(plugin_name_ + ".lookahead_time", lookahead_time_);node->get_parameter(plugin_name_ + ".rotate_to_heading_angular_vel",rotate_to_heading_angular_vel_);node->get_parameter(plugin_name_ + ".transform_tolerance", transform_tolerance);node->get_parameter(plugin_name_ + ".use_velocity_scaled_lookahead_dist",use_velocity_scaled_lookahead_dist_);node->get_parameter(plugin_name_ + ".min_approach_linear_velocity",min_approach_linear_velocity_);node->get_parameter(plugin_name_ + ".approach_velocity_scaling_dist",approach_velocity_scaling_dist_);// approach_velocity_scaling_dist大于正向成本图范围导致永久性放缓if (approach_velocity_scaling_dist_ > costmap_->getSizeInMetersX() / 2.0) {RCLCPP_WARN(logger_, "approach_velocity_scaling_dist is larger than forward costmap extent, ""leading to permanent slowdown");}node->get_parameter(plugin_name_ + ".max_allowed_time_to_collision_up_to_carrot",max_allowed_time_to_collision_up_to_carrot_);node->get_parameter(plugin_name_ + ".use_collision_detection",use_collision_detection_);node->get_parameter(plugin_name_ + ".use_regulated_linear_velocity_scaling",use_regulated_linear_velocity_scaling_);node->get_parameter(plugin_name_ + ".use_cost_regulated_linear_velocity_scaling",use_cost_regulated_linear_velocity_scaling_);node->get_parameter(plugin_name_ + ".cost_scaling_dist", cost_scaling_dist_);node->get_parameter(plugin_name_ + ".cost_scaling_gain", cost_scaling_gain_);node->get_parameter(plugin_name_ + ".inflation_cost_scaling_factor",inflation_cost_scaling_factor_);node->get_parameter(plugin_name_ + ".regulated_linear_scaling_min_radius",regulated_linear_scaling_min_radius_);node->get_parameter(plugin_name_ + ".regulated_linear_scaling_min_speed",regulated_linear_scaling_min_speed_);node->get_parameter(plugin_name_ + ".use_rotate_to_heading", use_rotate_to_heading_);node->get_parameter(plugin_name_ + ".rotate_to_heading_min_angle", rotate_to_heading_min_angle_);node->get_parameter(plugin_name_ + ".max_angular_accel", max_angular_accel_);node->get_parameter(plugin_name_ + ".allow_reversing", allow_reversing_);node->get_parameter("controller_frequency", control_frequency);node->get_parameter(plugin_name_ + ".max_robot_pose_search_dist",max_robot_pose_search_dist_);node->get_parameter(plugin_name_ + ".use_interpolation",use_interpolation_);transform_tolerance_ = tf2::durationFromSec(transform_tolerance);// 控制周期control_duration_ = 1.0 / control_frequency;// ationcost_scalingfactor设置不正确,应大于0if (inflation_cost_scaling_factor_ <= 0.0) {RCLCPP_WARN(logger_, "The value inflation_cost_scaling_factor is incorrectly set, ""it should be >0. Disabling cost regulated linear velocity scaling.");use_cost_regulated_linear_velocity_scaling_ = false;}// 仅当满足以下条件时,才可能反向行驶 use_rotate_to_heading is falseif (use_rotate_to_heading_ && allow_reversing_) {RCLCPP_WARN(logger_, "Disabling reversing. Both use_rotate_to_heading and allow_reversing ""parameter cannot be set to true. By default setting use_rotate_to_heading true");allow_reversing_ = false;}// 发布全局路径、预瞄点、前视碰撞检测距离global_path_pub_ = node->create_publisher<nav_msgs::msg::Path>("received_global_plan", 1);carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>("lookahead_point", 1);carrot_arc_pub_ = node->create_publisher<nav_msgs::msg::Path>("lookahead_collision_arc", 1);// 初始化碰撞检查和设置代价地图collision_checker_ = std::make_unique<nav2_costmap_2d::FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>(costmap_);collision_checker_->setCostmap(costmap_);
}
// 清楚资源
void RegulatedPurePursuitController::cleanup()
{RCLCPP_INFO(logger_,"Cleaning up controller: %s of type"" regulated_pure_pursuit_controller::RegulatedPurePursuitController",plugin_name_.c_str());global_path_pub_.reset();carrot_pub_.reset();carrot_arc_pub_.reset();
}
// 激活
void RegulatedPurePursuitController::activate()
{RCLCPP_INFO(logger_,"Activating controller: %s of type ""regulated_pure_pursuit_controller::RegulatedPurePursuitController",plugin_name_.c_str());global_path_pub_->on_activate();carrot_pub_->on_activate();carrot_arc_pub_->on_activate();// Add callback for dynamic parametersauto node = node_.lock();dyn_params_handler_ = node->add_on_set_parameters_callback(std::bind(&RegulatedPurePursuitController::dynamicParametersCallback,this, std::placeholders::_1));
}
// 取消激活
void RegulatedPurePursuitController::deactivate()
{RCLCPP_INFO(logger_,"Deactivating controller: %s of type ""regulated_pure_pursuit_controller::RegulatedPurePursuitController",plugin_name_.c_str());global_path_pub_->on_deactivate();carrot_pub_->on_deactivate();carrot_arc_pub_->on_deactivate();dyn_params_handler_.reset();
}
// 可视化预瞄点
std::unique_ptr<geometry_msgs::msg::PointStamped> RegulatedPurePursuitController::createCarrotMsg(const geometry_msgs::msg::PoseStamped & carrot_pose)
{auto carrot_msg = std::make_unique<geometry_msgs::msg::PointStamped>();carrot_msg->header = carrot_pose.header;carrot_msg->point.x = carrot_pose.pose.position.x;carrot_msg->point.y = carrot_pose.pose.position.y;carrot_msg->point.z = 0.01;  // publish right over map to stand outreturn carrot_msg;
}// 传入线速度等比例获取前视距离
double RegulatedPurePursuitController::getLookAheadDistance(const geometry_msgs::msg::Twist & speed)
{// 如果使用速度缩放的前视距离,寻找以及限幅距离,使用静态前视距离double lookahead_dist = lookahead_dist_;if (use_velocity_scaled_lookahead_dist_) {lookahead_dist = fabs(speed.linear.x) * lookahead_time_;lookahead_dist = std::clamp(lookahead_dist, min_lookahead_dist_, max_lookahead_dist_);}// 若不使用速度缩放的前视距离,返回设置的前视距离return lookahead_dist;
}
// 计算速度命令
geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocityCommands(const geometry_msgs::msg::PoseStamped & pose,const geometry_msgs::msg::Twist & speed,nav2_core::GoalChecker * goal_checker)
{std::lock_guard<std::mutex> lock_reinit(mutex_);nav2_costmap_2d::Costmap2D * costmap = costmap_ros_->getCostmap();std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));// 更新当前目标检查器的状态geometry_msgs::msg::Pose pose_tolerance;geometry_msgs::msg::Twist vel_tolerance;if (!goal_checker->getTolerances(pose_tolerance, vel_tolerance)) {RCLCPP_WARN(logger_, "Unable to retrieve goal checker's tolerances!");} else {goal_dist_tol_ = pose_tolerance.position.x;}// Transform path to robot base frame// 转换路径到机器人坐标系auto transformed_plan = transformGlobalPlan(pose);// Find look ahead distance and point on path and publish// 找到预瞄距离double lookahead_dist = getLookAheadDistance(speed);// Check for reverse driving// 是否允许倒车if (allow_reversing_) {// Cusp checkdouble dist_to_cusp = findVelocitySignChange(transformed_plan);// 如果前视距离比cusp更远,则使用cusp距离if (dist_to_cusp < lookahead_dist) {lookahead_dist = dist_to_cusp;}}// 找到预瞄点auto carrot_pose = getLookAheadPoint(lookahead_dist, transformed_plan);// 发布预瞄点carrot_pub_->publish(createCarrotMsg(carrot_pose));double linear_vel, angular_vel;// 在机器人坐标系下找到前视距离的平方// 这是圆的弦长const double carrot_dist2 =(carrot_pose.pose.position.x * carrot_pose.pose.position.x) +(carrot_pose.pose.position.y * carrot_pose.pose.position.y);// Find curvature of circle (k = 1 / R)double curvature = 0.0;// 计算曲率if (carrot_dist2 > 0.001) {curvature = 2.0 * carrot_pose.pose.position.y / carrot_dist2;}// Setting the velocity direction// 设置速度方向double sign = 1.0;// 如果允许倒车,将速度方向置反if (allow_reversing_) {sign = carrot_pose.pose.position.x >= 0.0 ? 1.0 : -1.0;}// 设置期望的线速度linear_vel = desired_linear_vel_;// 确保遵守基本的约束double angle_to_heading;// 约束检查if (shouldRotateToGoalHeading(carrot_pose)) {double angle_to_goal = tf2::getYaw(transformed_plan.poses.back().pose.orientation);rotateToHeading(linear_vel, angular_vel, angle_to_goal, speed);} else if (shouldRotateToPath(carrot_pose, angle_to_heading)) {rotateToHeading(linear_vel, angular_vel, angle_to_heading, speed);} else {applyConstraints(curvature, speed,costAtPose(pose.pose.position.x, pose.pose.position.y), transformed_plan,linear_vel, sign);// 在约束线速度后将曲率应用于角速度angular_vel = linear_vel * curvature;}// 此速度航向的碰撞检测const double & carrot_dist = hypot(carrot_pose.pose.position.x, carrot_pose.pose.position.y);// 使用碰撞检测,然后判断碰撞检测是否很急if (use_collision_detection_ && isCollisionImminent(pose, linear_vel, angular_vel, carrot_dist)) {throw nav2_core::PlannerException("RegulatedPurePursuitController detected collision ahead!");}// populate and return message// 填充并返回cmd_velgeometry_msgs::msg::TwistStamped cmd_vel;cmd_vel.header = pose.header;cmd_vel.twist.linear.x = linear_vel;cmd_vel.twist.angular.z = angular_vel;return cmd_vel;
}
// 将小车旋转至粗糙的轨迹
bool RegulatedPurePursuitController::shouldRotateToPath(const geometry_msgs::msg::PoseStamped & carrot_pose, double & angle_to_path)
{// 我们是否应该将机器人旋转到粗糙的路径航向angle_to_path = atan2(carrot_pose.pose.position.y, carrot_pose.pose.position.x);return use_rotate_to_heading_ && fabs(angle_to_path) > rotate_to_heading_min_angle_;
}
// 是否将小车旋转至目标航向
bool RegulatedPurePursuitController::shouldRotateToGoalHeading(const geometry_msgs::msg::PoseStamped & carrot_pose)
{// 我们是否应该将机器人旋转到目标航向double dist_to_goal = std::hypot(carrot_pose.pose.position.x, carrot_pose.pose.position.y);return use_rotate_to_heading_ && dist_to_goal < goal_dist_tol_;
}
// 使用可能的最大角速度/加速度旋转到位
void RegulatedPurePursuitController::rotateToHeading(double & linear_vel, double & angular_vel,const double & angle_to_path, const geometry_msgs::msg::Twist & curr_speed)
{// Rotate in place using max angular velocity / acceleration possiblelinear_vel = 0.0;const double sign = angle_to_path > 0.0 ? 1.0 : -1.0;angular_vel = sign * rotate_to_heading_angular_vel_;const double & dt = control_duration_;const double min_feasible_angular_speed = curr_speed.angular.z - max_angular_accel_ * dt;const double max_feasible_angular_speed = curr_speed.angular.z + max_angular_accel_ * dt;angular_vel = std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
}
// 线段和圆交点
geometry_msgs::msg::Point RegulatedPurePursuitController::circleSegmentIntersection(const geometry_msgs::msg::Point & p1,const geometry_msgs::msg::Point & p2,double r)
{double x1 = p1.x;double x2 = p2.x;double y1 = p1.y;double y2 = p2.y;double dx = x2 - x1;double dy = y2 - y1;double dr2 = dx * dx + dy * dy;double D = x1 * y2 - x2 * y1;// Augmentation to only return point within segmentdouble d1 = x1 * x1 + y1 * y1;double d2 = x2 * x2 + y2 * y2;double dd = d2 - d1;geometry_msgs::msg::Point p;double sqrt_term = std::sqrt(r * r * dr2 - D * D);p.x = (D * dy + std::copysign(1.0, dd) * dx * sqrt_term) / dr2;p.y = (-D * dx + std::copysign(1.0, dd) * dy * sqrt_term) / dr2;return p;
}
// 通过容器的迭代器找到大于前视距离的迭代器goal_pose_it,判断是否为轨迹最后跟踪点,如果是则返回改点,
// 如不是,则通过circleSegmentIntersection()找到机器人对应前视距离在规划轨迹上的相交点(实际上的插值作用)
geometry_msgs::msg::PoseStamped RegulatedPurePursuitController::getLookAheadPoint(const double & lookahead_dist,const nav_msgs::msg::Path & transformed_plan)
{// 找到第一个距离大于前视距离的姿势auto goal_pose_it = std::find_if(transformed_plan.poses.begin(), transformed_plan.poses.end(), [&](const auto & ps) {return hypot(ps.pose.position.x, ps.pose.position.y) >= lookahead_dist;});// 如果无姿势还不够远,请采取最后一个姿势if (goal_pose_it == transformed_plan.poses.end()) {goal_pose_it = std::prev(transformed_plan.poses.end());} else if (use_interpolation_ && goal_pose_it != transformed_plan.poses.begin()) {// Find the point on the line segment between the two poses// that is exactly the lookahead distance away from the robot pose (the origin)// This can be found with a closed form for the intersection of a segment and a circle// Because of the way we did the std::find_if, prev_pose is guaranteed to be inside the circle,// and goal_pose is guaranteed to be outside the circle.auto prev_pose_it = std::prev(goal_pose_it);auto point = circleSegmentIntersection(prev_pose_it->pose.position,goal_pose_it->pose.position, lookahead_dist);geometry_msgs::msg::PoseStamped pose;pose.header.frame_id = prev_pose_it->header.frame_id;pose.header.stamp = goal_pose_it->header.stamp;pose.pose.position = point;return pose;}return *goal_pose_it;
}
// 最后再做一次碰撞检测,则完成了一个控制周期的响应,通过获取代价地图分辨率和线速度,进行线速度前进方向上的避障检测,检测距离为前视距离的平方
// 碰撞是否紧急
bool RegulatedPurePursuitController::isCollisionImminent(const geometry_msgs::msg::PoseStamped & robot_pose,const double & linear_vel, const double & angular_vel,const double & carrot_dist)
{// Note(stevemacenski): This may be a bit unusual, but the robot_pose is in// odom frame and the carrot_pose is in robot base frame.// check current point is OK// 检查当前点是否OKif (inCollision(robot_pose.pose.position.x, robot_pose.pose.position.y,tf2::getYaw(robot_pose.pose.orientation))){return true;}// visualization messages// 可视化消息nav_msgs::msg::Path arc_pts_msg;arc_pts_msg.header.frame_id = costmap_ros_->getGlobalFrameID();arc_pts_msg.header.stamp = robot_pose.header.stamp;geometry_msgs::msg::PoseStamped pose_msg;pose_msg.header.frame_id = arc_pts_msg.header.frame_id;pose_msg.header.stamp = arc_pts_msg.header.stamp;// 投影时间double projection_time = 0.0;if (fabs(linear_vel) < 0.01 && fabs(angular_vel) > 0.01) {// 旋转以朝向目标或路径前进// 方程式找到了最大值所需的角距离// 将机器人半径的一部分移动到另一个成本图单元格// theta_min = 2.0 * sin ((res/2) / r_max)// 通过等腰三角形r_max-r_max分辨率除以角速度,我们得到一个时间步长double max_radius = costmap_ros_->getLayeredCostmap()->getCircumscribedRadius();projection_time =2.0 * sin((costmap_->getResolution() / 2) / max_radius) / fabs(angular_vel);} else {// 正常的路径跟踪projection_time = costmap_->getResolution() / fabs(linear_vel);}const geometry_msgs::msg::Point & robot_xy = robot_pose.pose.position;geometry_msgs::msg::Pose2D curr_pose;curr_pose.x = robot_pose.pose.position.x;curr_pose.y = robot_pose.pose.position.y;curr_pose.theta = tf2::getYaw(robot_pose.pose.orientation);// only forward simulate within time requested// 仅仅前向模拟在时间要求之内int i = 1;while (i * projection_time < max_allowed_time_to_collision_up_to_carrot_) {i++;// apply velocity at curr_pose over distancecurr_pose.x += projection_time * (linear_vel * cos(curr_pose.theta));curr_pose.y += projection_time * (linear_vel * sin(curr_pose.theta));curr_pose.theta += projection_time * angular_vel;// check if past carrot pose, where no longer a thoughtfully valid commandif (hypot(curr_pose.x - robot_xy.x, curr_pose.y - robot_xy.y) > carrot_dist) {break;}// store it for visualizationpose_msg.pose.position.x = curr_pose.x;pose_msg.pose.position.y = curr_pose.y;pose_msg.pose.position.z = 0.01;arc_pts_msg.poses.push_back(pose_msg);// 检查碰撞在投影位姿上if (inCollision(curr_pose.x, curr_pose.y, curr_pose.theta)) {carrot_arc_pub_->publish(arc_pts_msg);return true;}}carrot_arc_pub_->publish(arc_pts_msg);return false;
}
// 在投影姿态下检查碰撞情况
bool RegulatedPurePursuitController::inCollision(const double & x,const double & y,const double & theta)
{unsigned int mx, my;// is false// 成本图的尺寸太小,无法成功检查// 碰撞尽可能远。自行承担风险,减慢机器人速度,或增加您的成本图大小if (!costmap_->worldToMap(x, y, mx, my)) {RCLCPP_WARN_THROTTLE(logger_, *(clock_), 30000,"The dimensions of the costmap is too small to successfully check for ""collisions as far ahead as requested. Proceed at your own risk, slow the robot, or ""increase your costmap size.");return false;}double footprint_cost = collision_checker_->footprintCostAtPose(x, y, theta, costmap_ros_->getRobotFootprint());if (footprint_cost == static_cast<double>(NO_INFORMATION) &&costmap_ros_->getLayeredCostmap()->isTrackingUnknown()){return false;}// 如果被占用或未知,不要穿越未知空间return footprint_cost >= static_cast<double>(LETHAL_OBSTACLE);
}
// pose的代价
double RegulatedPurePursuitController::costAtPose(const double & x, const double & y)
{unsigned int mx, my;if (!costmap_->worldToMap(x, y, mx, my)) {RCLCPP_FATAL(// 成本图的尺寸太小,无法完全包括机器人的足迹,因此机器人无法继续前进logger_,"The dimensions of the costmap is too small to fully include your robot's footprint, ""thusly the robot cannot proceed further");throw nav2_core::PlannerException("RegulatedPurePursuitController: Dimensions of the costmap are too small ""to encapsulate the robot footprint at current speeds!");}unsigned char cost = costmap_->getCost(mx, my);return static_cast<double>(cost);
}double RegulatedPurePursuitController::approachVelocityScalingFactor(const nav_msgs::msg::Path & transformed_path
) const
{// Waiting to apply the threshold based on integrated distance ensures we don't// erroneously apply approach scaling on curvy paths that are contained in a large local costmap.// 等待基于积分距离应用阈值可确保我们不会错误地将方法缩放应用于包含在大型局部成本图中的弯曲路径double remaining_distance = nav2_util::geometry_utils::calculate_path_length(transformed_path);if (remaining_distance < approach_velocity_scaling_dist_) {auto & last = transformed_path.poses.back();// Here we will use a regular euclidean distance from the robot frame (origin)// to get smooth scaling, regardless of path density.// 在这里,我们将使用与机器人框架(原点)的规则欧几里德距离进行平滑缩放,而不管路径密度如何double distance_to_last_pose = std::hypot(last.pose.position.x, last.pose.position.y);return distance_to_last_pose / approach_velocity_scaling_dist_;} else {return 1.0;}
}
// 靠近速度缩放
void RegulatedPurePursuitController::applyApproachVelocityScaling(const nav_msgs::msg::Path & path,double & linear_vel
) const
{double approach_vel = linear_vel;double velocity_scaling = approachVelocityScalingFactor(path);double unbounded_vel = approach_vel * velocity_scaling;if (unbounded_vel < min_approach_linear_velocity_) {approach_vel = min_approach_linear_velocity_;} else {approach_vel *= velocity_scaling;}// Use the lowest velocity between approach and other constraints, if all overlappinglinear_vel = std::min(linear_vel, approach_vel);
}
// 约束检查函数,同时根据曲率生成对应的线速度
void RegulatedPurePursuitController::applyConstraints(const double & curvature, const geometry_msgs::msg::Twist & /*curr_speed*/,const double & pose_cost, const nav_msgs::msg::Path & path, double & linear_vel, double & sign)
{double curvature_vel = linear_vel;double cost_vel = linear_vel;// limit the linear velocity by curvature// 根据曲率限制线速度const double radius = fabs(1.0 / curvature);const double & min_rad = regulated_linear_scaling_min_radius_;// 使用可调节线速度缩放if (use_regulated_linear_velocity_scaling_ && radius < min_rad) {curvature_vel *= 1.0 - (fabs(radius - min_rad) / min_rad);}// limit the linear velocity by proximity to obstacles// 通过接近障碍物来限制线速度if (use_cost_regulated_linear_velocity_scaling_ &&pose_cost != static_cast<double>(NO_INFORMATION) &&pose_cost != static_cast<double>(FREE_SPACE)){const double inscribed_radius = costmap_ros_->getLayeredCostmap()->getInscribedRadius();const double min_distance_to_obstacle = (-1.0 / inflation_cost_scaling_factor_) *std::log(pose_cost / (INSCRIBED_INFLATED_OBSTACLE - 1)) + inscribed_radius;if (min_distance_to_obstacle < cost_scaling_dist_) {cost_vel *= cost_scaling_gain_ * min_distance_to_obstacle / cost_scaling_dist_;}}// Use the lowest of the 2 constraint heuristics, but above the minimum translational speed// 使用2个约束启发式算法中的最低值,但高于最小平移速度linear_vel = std::min(cost_vel, curvature_vel);linear_vel = std::max(linear_vel, regulated_linear_scaling_min_speed_);// 约束检查函数,同时根据曲率生成对应的线速度applyApproachVelocityScaling(path, linear_vel);// 限制线速度为合理linear_vel = std::clamp(fabs(linear_vel), 0.0, desired_linear_vel_);linear_vel = sign * linear_vel;
}
// 设置全局路径
void RegulatedPurePursuitController::setPlan(const nav_msgs::msg::Path & path)
{global_plan_ = path;
}
// 设置速度限制
void RegulatedPurePursuitController::setSpeedLimit(const double & speed_limit,const bool & percentage)
{if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {// 恢复默认值desired_linear_vel_ = base_desired_linear_vel_;} else {if (percentage) {// 速度限制以机器人最大速度的百分比表示desired_linear_vel_ = base_desired_linear_vel_ * speed_limit / 100.0;} else {// Speed limit is expressed in absolute valuedesired_linear_vel_ = speed_limit;}}
}
// 将全局路径变换到机器人坐标下
// max_robot_pose_search_dist往前搜的最大距离
nav_msgs::msg::Path RegulatedPurePursuitController::transformGlobalPlan(const geometry_msgs::msg::PoseStamped & pose)
{if (global_plan_.poses.empty()) {throw nav2_core::PlannerException("Received plan with zero length");}// let's get the pose of the robot in the frame of the plan// 将机器人坐标变换到规划坐标系(全局坐标系)geometry_msgs::msg::PoseStamped robot_pose;if (!transformPose(global_plan_.header.frame_id, pose, robot_pose)) {throw nav2_core::PlannerException("Unable to transform robot pose into global plan's frame");}// We'll discard points on the plan that are outside the local costmapdouble max_costmap_extent = getCostmapMaxExtent();// 找到规划轨迹中第一个大于搜索距离的元素(轨迹点)auto closest_pose_upper_bound =nav2_util::geometry_utils::first_after_integrated_distance(global_plan_.poses.begin(), global_plan_.poses.end(), max_robot_pose_search_dist_);// 找到轨迹上距离机器人最近轨迹点auto transformation_begin =nav2_util::geometry_utils::min_by(global_plan_.poses.begin(), closest_pose_upper_bound,[&robot_pose](const geometry_msgs::msg::PoseStamped & ps) {return euclidean_distance(robot_pose, ps);});// Find points up to max_transform_dist so we only transform them.// 丢弃超出局部代价地图的元素auto transformation_end = std::find_if(transformation_begin, global_plan_.poses.end(),[&](const auto & pose) {return euclidean_distance(pose, robot_pose) > max_costmap_extent;});// Lambda to transform a PoseStamped from global frame to local// Lambda表达式声明全局坐标变换局部坐标(机器人坐标系)的函数auto transformGlobalPoseToLocal = [&](const auto & global_plan_pose) {geometry_msgs::msg::PoseStamped stamped_pose, transformed_pose;stamped_pose.header.frame_id = global_plan_.header.frame_id;stamped_pose.header.stamp = robot_pose.header.stamp;stamped_pose.pose = global_plan_pose.pose;transformPose(costmap_ros_->getBaseFrameID(), stamped_pose, transformed_pose);transformed_pose.pose.position.z = 0.0;return transformed_pose;};// Transform the near part of the global plan into the robot's frame of reference.// 将轨迹从全局坐标系变换到机器人坐标系nav_msgs::msg::Path transformed_plan;std::transform(transformation_begin, transformation_end,std::back_inserter(transformed_plan.poses),transformGlobalPoseToLocal);transformed_plan.header.frame_id = costmap_ros_->getBaseFrameID();transformed_plan.header.stamp = robot_pose.header.stamp;// Remove the portion of the global plan that we've already passed so we don't// process it on the next iteration (this is called path pruning)// 移除已经经过的路径,将不会再下次处理(路径剪支)global_plan_.poses.erase(begin(global_plan_.poses), transformation_begin);// 发布转换过后的全局路径global_path_pub_->publish(transformed_plan);if (transformed_plan.poses.empty()) {throw nav2_core::PlannerException("Resulting plan has 0 poses in it.");}return transformed_plan;
}
// 判断机器人轨迹是否掉头,防止机器人超调走捷径,通过向量法OA,AB相乘是否小于0来判断,如果小于O,机器人的前视距离不能大于轨迹上的掉头点
double RegulatedPurePursuitController::findVelocitySignChange(const nav_msgs::msg::Path & transformed_plan)
{// 迭代变换后的全局路径以确定尖点的位置for (unsigned int pose_id = 1; pose_id < transformed_plan.poses.size() - 1; ++pose_id) {// We have two vectors for the dot product OA and AB. Determining the vectors.double oa_x = transformed_plan.poses[pose_id].pose.position.x -transformed_plan.poses[pose_id - 1].pose.position.x;double oa_y = transformed_plan.poses[pose_id].pose.position.y -transformed_plan.poses[pose_id - 1].pose.position.y;double ab_x = transformed_plan.poses[pose_id + 1].pose.position.x -transformed_plan.poses[pose_id].pose.position.x;double ab_y = transformed_plan.poses[pose_id + 1].pose.position.y -transformed_plan.poses[pose_id].pose.position.y;/* Checking for the existance of cusp, in the path, using the dot productand determine it's distance from the robot. If there is no cusp in the path,then just determine the distance to the goal location. */if ( (oa_x * ab_x) + (oa_y * ab_y) < 0.0) {// returning the distance if there is a cusp// The transformed path is in the robots frame, so robot is at the originreturn hypot(transformed_plan.poses[pose_id].pose.position.x,transformed_plan.poses[pose_id].pose.position.y);}}return std::numeric_limits<double>::max();
}
// 转换in_pose到out_pose
bool RegulatedPurePursuitController::transformPose(const std::string frame,const geometry_msgs::msg::PoseStamped & in_pose,geometry_msgs::msg::PoseStamped & out_pose) const
{if (in_pose.header.frame_id == frame) {out_pose = in_pose;return true;}try {tf_->transform(in_pose, out_pose, frame, transform_tolerance_);out_pose.header.frame_id = frame;return true;} catch (tf2::TransformException & ex) {RCLCPP_ERROR(logger_, "Exception in transformPose: %s", ex.what());}return false;
}
// 获取成本图最大范围
double RegulatedPurePursuitController::getCostmapMaxExtent() const
{const double max_costmap_dim_meters = std::max(costmap_->getSizeInMetersX(), costmap_->getSizeInMetersY());return max_costmap_dim_meters / 2.0;
}// 动态参数回调
rcl_interfaces::msg::SetParametersResult
RegulatedPurePursuitController::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
{rcl_interfaces::msg::SetParametersResult result;std::lock_guard<std::mutex> lock_reinit(mutex_);for (auto parameter : parameters) {const auto & type = parameter.get_type();const auto & name = parameter.get_name();if (type == ParameterType::PARAMETER_DOUBLE) {if (name == plugin_name_ + ".inflation_cost_scaling_factor") {if (parameter.as_double() <= 0.0) {RCLCPP_WARN(logger_, "The value inflation_cost_scaling_factor is incorrectly set, ""it should be >0. Ignoring parameter update.");continue;}inflation_cost_scaling_factor_ = parameter.as_double();} else if (name == plugin_name_ + ".desired_linear_vel") {desired_linear_vel_ = parameter.as_double();base_desired_linear_vel_ = parameter.as_double();} else if (name == plugin_name_ + ".lookahead_dist") {lookahead_dist_ = parameter.as_double();} else if (name == plugin_name_ + ".max_lookahead_dist") {max_lookahead_dist_ = parameter.as_double();} else if (name == plugin_name_ + ".min_lookahead_dist") {min_lookahead_dist_ = parameter.as_double();} else if (name == plugin_name_ + ".lookahead_time") {lookahead_time_ = parameter.as_double();} else if (name == plugin_name_ + ".rotate_to_heading_angular_vel") {rotate_to_heading_angular_vel_ = parameter.as_double();} else if (name == plugin_name_ + ".min_approach_linear_velocity") {min_approach_linear_velocity_ = parameter.as_double();} else if (name == plugin_name_ + ".max_allowed_time_to_collision_up_to_carrot") {max_allowed_time_to_collision_up_to_carrot_ = parameter.as_double();} else if (name == plugin_name_ + ".cost_scaling_dist") {cost_scaling_dist_ = parameter.as_double();} else if (name == plugin_name_ + ".cost_scaling_gain") {cost_scaling_gain_ = parameter.as_double();} else if (name == plugin_name_ + ".regulated_linear_scaling_min_radius") {regulated_linear_scaling_min_radius_ = parameter.as_double();} else if (name == plugin_name_ + ".transform_tolerance") {double transform_tolerance = parameter.as_double();transform_tolerance_ = tf2::durationFromSec(transform_tolerance);} else if (name == plugin_name_ + ".regulated_linear_scaling_min_speed") {regulated_linear_scaling_min_speed_ = parameter.as_double();} else if (name == plugin_name_ + ".max_angular_accel") {max_angular_accel_ = parameter.as_double();} else if (name == plugin_name_ + ".rotate_to_heading_min_angle") {rotate_to_heading_min_angle_ = parameter.as_double();}} else if (type == ParameterType::PARAMETER_BOOL) {if (name == plugin_name_ + ".use_velocity_scaled_lookahead_dist") {use_velocity_scaled_lookahead_dist_ = parameter.as_bool();} else if (name == plugin_name_ + ".use_regulated_linear_velocity_scaling") {use_regulated_linear_velocity_scaling_ = parameter.as_bool();} else if (name == plugin_name_ + ".use_cost_regulated_linear_velocity_scaling") {use_cost_regulated_linear_velocity_scaling_ = parameter.as_bool();} else if (name == plugin_name_ + ".use_rotate_to_heading") {if (parameter.as_bool() && allow_reversing_) {RCLCPP_WARN(logger_, "Both use_rotate_to_heading and allow_reversing ""parameter cannot be set to true. Rejecting parameter update.");continue;}use_rotate_to_heading_ = parameter.as_bool();} else if (name == plugin_name_ + ".allow_reversing") {if (use_rotate_to_heading_ && parameter.as_bool()) {RCLCPP_WARN(logger_, "Both use_rotate_to_heading and allow_reversing ""parameter cannot be set to true. Rejecting parameter update.");continue;}allow_reversing_ = parameter.as_bool();}}}result.successful = true;return result;
}}  // namespace nav2_regulated_pure_pursuit_controller// Register this controller as a nav2_core plugin
PLUGINLIB_EXPORT_CLASS(nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController,nav2_core::Controller)

该算法部署到实车上,配对维诺图算法,效果还不错,走直道和过窄门效果不错

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/47344.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

业务终端动态分配IP-DHCP技术、DHCP中继技术

一、为什么需要DHCP? 1、许多设备(主机、无线WiFi终端等)需要动态地址的分配; 2、人工手工配置任务繁琐、容易出错,比如:IP地址冲突; 3、网络规模扩大、复杂度提高,网络配置越来越复杂,计算机的位置变化和数量超过可分配IP地址的数量,造成IP地址变法频繁以及IP地址…

Monaco 使用 DocumentHighlightProvider

Monaco 中有一个文字高亮的功能&#xff0c;就是选中一个单词&#xff0c;会高亮文字文档中所有出现该单词的位置&#xff0c;效果如下&#xff1a; Monaco 默认就有这个功能&#xff0c;可以根据具体需求进行定制。通过 registerDocumentHighlightProvider 进行注册 实现 pro…

【Java数据结构】初始线性表之一:链表

为什么要有链表 上一节我们描述了顺序表&#xff1a;【Java数据结构】初识线性表之一&#xff1a;顺序表-CSDN博客 并且进行了简单模拟实现。通过源码知道&#xff0c;ArrayList底层使用数组来存储元素。 由于其底层是一段连续空间&#xff0c;当在ArrayList任意位置插入或者…

代码随想录二刷复习(二分法)

二分法模板&#xff1a; 1&#xff1a;左闭右闭区间写法 第一种写法&#xff0c;我们定义 target 是在一个在左闭右闭的区间里&#xff0c;也就是[left, right] &#xff08;这个很重要非常重要&#xff09;。 区间的定义这就决定了二分法的代码应该如何写&#xff0c;因为定…

vue 给特定满足条件的表单数据添加背景颜色,组件的 row-class-name

1、:row-class-name"tableRowClassName" 可为表格每行根据后面的函数绑定class名 <!-- 列表框 --><div class"tableList"><el-table :data"teamModelListTable" style"width: 100%"selection-change"handleSele…

el-table表格操作列错行处理

解决方法&#xff1a; <style>::v-deep .el-table th.el-table__cell > .cell {white-space: nowrap !important;} </style>

不想填邀请码?Xinstall来帮你,一键安装无忧愁

在这个快节奏的时代&#xff0c;每一个点击都承载着用户的期待与耐心。然而&#xff0c;在下载App的过程中&#xff0c;繁琐的邀请码填写往往成为了用户体验的一大障碍。你是否也曾经因为不愿填写邀请码而放弃了一款心仪的App&#xff1f;今天&#xff0c;就让我们一起走进Xins…

镜像与容器

Docker Image (镜像) Docker 镜像概念 Docker iamge 本质上是一个 read-only 只读文件&#xff0c;这个文件包含了文件系统、源码、库文件、依赖、工具等一些运行 application 所必需的文件。 可以把 Docker image 理解成一个模板&#xff0c;可以通过这个模板实例化出来很多…

【Datawhale AI 夏令营】入门lightgbm及特征工程进行实践

文章目录 1. LightGBM简介2. 导入必要的库3. 加载数据集4. 数据可视化4.1 不同类型对应目标值的柱状图4.2 特定ID的目标值折线图 5. 特征工程5.1 合并训练集和测试集并进行排序5.2 历史平移5.3 窗口统计5.4 数据切分5.5 确定输入特征 6. 模型训练与评估7. 结果展示 1. LightGBM…

一文看懂JTAG基本知识

文章目录 1、JTAG是什么?1.1边界扫描2、JTAG如何起作用?2.1 PC控制JTAG2.2 并行端口2.3 JTAG TAP控制器2.4 计算JTAG链中元件个数2.5 获得JTAG链上芯片的ID3、边界扫描3.1、SAMPLE3.2、边界扫描寄存器3.3、JTAG还可以做什么?参考资料:1、JTAG是什么? JTAG是20世纪80年代开…

云监控(华为) | 实训学习day1(10)

云监控&#xff1a;确保服务器高效运行 在当今的数字化时代&#xff0c;服务器的稳定运行对于任何企业都至关重要。为了确保服务器的 CPU、内存和硬盘等资源的合理运行&#xff0c;云监控成为了一项不可或缺的技术。本文将详细介绍云监控的基本概念、所需软件、配置方法以及如何…

JDBC 技术 | Java连接MySQL数据库(四万字零基础保姆级超全详解)

文章目录 前言一. JDBC概述1. JDBC 概念2. JDBC 本质3. JDBC 的好处 二. JDBC 快速入门1. 编写Java 程序步骤2. 在IDEA 中的操作流程3. 正式编写 Java程序 三. JDBC API详解1. DriverManager 类2. Connection 接口2.1 获取执行SQL语句的对象 3 .ResultSet 类3.1 概述3.2 代码实…

Cadence23学习笔记(四)

这个人讲cadence也很不错&#xff1a; 73、创建Power NetClass[Cadence Allegro132讲视频教程字幕版]_哔哩哔哩_bilibili 上位机开发&#xff1a; MFC 最详细入门教程-CSDN博客 Board Geometry — Design_Outline 板框 Etch — Top 走线 Pin — Top 焊盘 …

探索APP开发中的主流版式设计与应用实践

在当今移动互联网高速发展的时代&#xff0c;APP已成为人们日常生活中不可或缺的一部分。无论是社交娱乐、购物支付还是工作学习&#xff0c;各类APP都以其独特的界面设计和用户体验赢得了用户的青睐。而APP开发的版式设计和页面规范&#xff0c;则是决定用户体验好坏的关键因素…

记录些MySQL题集(6)

MySQL 单表为什么不要超过 2000W 行&#xff1f; 数据持久化在磁盘中&#xff0c;磁盘的最小单元是扇区&#xff0c;一个扇区 0.5 KB&#xff0c;而由 8 个扇区可以构成一个文件系统块&#xff08;4K&#xff09;&#xff0c;以 InnoDB 存储引擎为例&#xff0c;一个数据页的大…

打卡第15天------二叉树

最近公司给我派活儿太多了,要干好多活儿,好多工作任务要处理,我都没时间刷题了。leetcode上的题目通过数量一直停留在原地不动,我真的很着急呀,我现在每天过的都有一种紧迫感,很着急,有一种与时间赛跑的感觉,真的时间过的太快了,没有任何人能够阻挡住时间的年轮向前推…

【异步爬虫:利用异步协程抓取一部电影】

利用异步协程抓取一部电影 我们把目光转向wbdy. 目前该案例还是可以用的. 我们想要抓取网上的视频资源就必须要了解我们的视频网站是如何工作的. 这里我用91看剧来做举例. 其他网站的原理是一样的. 1.视频网站是如何工作的 假设, 你现在想要做一个视频网站. 也有很多的UP猪…

【BUG】已解决:java.lang.IllegalStateException: Duplicate key

已解决&#xff1a;java.lang.IllegalStateException: Duplicate key 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市…

【数学建模】——多领域资源优化中的创新应用-六大经典问题解答

目录 题目1&#xff1a;截取条材 题目 1.1问题描述 1.2 数学模型 1.3 求解 1.4 解答 题目2&#xff1a;商店进货销售计划 题目 2.1 问题描述 2.2 数学模型 2.3 求解 2.4 解答 题目3&#xff1a;货船装载问题 题目 3.1问题重述 3.2 数学模型 3.3 求解 3.4 解…

超详细信息收集篇

1 域名信息收集 1.1 域名是什么 域名&#xff08;英语&#xff1a;Domain Name&#xff09;&#xff0c;又称网域&#xff0c;是由一串用点分隔的名字组成的 Internet 上某一台 计算机 或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地…