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_tolerance | TF变换容差 |
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 | 沿路径绑定机器人最近姿态的最大集成距离。默认情况下,它设置为最大代价地图范围,因此除非本地代价地图中有循环,否则不应手动设置 |
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::SetParametersResult
dynamicParametersCallback(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 handler
std::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设置不正确,应大于0
if (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 false
if (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 parameters
auto 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 out
return 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 check
double 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_vel
geometry_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 possible
linear_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 segment
double 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
// 检查当前点是否OK
if (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 distance
curr_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 command
if (hypot(curr_pose.x - robot_xy.x, curr_pose.y - robot_xy.y) > carrot_dist) {
break;
}
// store it for visualization
pose_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 overlapping
linear_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 value
desired_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 costmap
double 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 product
and 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 origin
return 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)
该算法部署到实车上,配对维诺图算法,效果还不错,走直道和过窄门效果不错