规划与控制之局部路径规划算法local_planner

思考问题:

1、当前目标点如何不断更新?按距离?如果存在动态障碍物占据全局路径该如何设置?

2、刹车距离如何预留?

输入全局规划器的轨迹(位置、朝向(曲线切线方向?))、车的当前位置(起点)、当前目标点和局部地图,输出车辆的线速度v和角速度w(控制器用其转换为车辆的油门和方向控制量,驱动车辆移动)。当前目标点需不断更新,引导车辆按全局规划的路径走向全局终点。局部地图一般是动态的,有些障碍物是不同移动的。局部路径优化的轨迹优化、避障算法与全局规划有相同之处,但由于直接输出给控制器使用,需要考虑车辆的运动模型;由于存在动态障碍物,还需预测障碍物的轨迹,增加时间约束。

ros navigation代码中,(父->子)nav_core/BaseLocalPlanner->(base_local_planner/TrajectoryPlannerROS、dwa_local_planner/DWAPlannerROS)

主要函数:

  • setPlan:将global_planner规划路径plan输入
  • computeVelocityCommands:根据plan计算角速度和线速度,输出给底盘控制器
  • isGoalReached:根据车辆当前位置和目标位置判断是否到达目标

一、局部路径规划算法

1、TrajectoryPlannerROS

代价函数:

1)方向角函数:轨迹终点方向角与当前目标点方向角(heading_scoring_);

2)距离评价函数:距离障碍物越远越好(occ_cost),贴合全局路径越近越好(path_dist、goal_dist);

base_local_planner/include/base_local_planner/trajectory_planner_ros.h
参考链接

  • setPlan:将global_planner规划路径plan输入,赋给global_plan_
  • computeVelocityCommands:根据plan计算角速度和线速度,输出给底盘控制器
    1)将全局位置和plan变换到局部地图中transformed_plan,获取当前速度robot_vel
    2)TrajectoryPlanner->updatePlan(transformed_plan):赋给global_plan_,轨迹最后一个点设为目标点,final_goal_x_/y_
    3)TrajectoryPlanner->findBestPath(global_pose, robot_vel, drive_cmds):得到cmd_vel(drive_cmds),由createTrajectories(global_pose, robot_vel,acc_lim)得到Trajectory并返回。更新path_map_、goal_map_用于计算cost中Path/Goal_dist,path_map_记录各cell与全局规划路径上的cell之间的距离,goal_map_记录各cell与目标cell之间的距离。
    4)TrajectoryPlanner->createTrajectories:Trajectory.cost需大于等0,cost计算公式如下,主要考虑occ_cost(障碍物)、path_dist(路径距离)、goal_dist(目标距离)和heading_diff(角度距离)等因素,返回cost最小的轨迹。holonomic_robot_表示全向机器人,一般为非全向,只有x和角速度、heading_scoring_表示是否为朝向打分,最大/小、线/角速度由当前速度加/减去最大加速度*sim_time_/sim_period_(dwa)。按最小/最大速度差/vx_samples_步长遍历所有前向速度和角速度(vx_samp、vy_samp和vtheta_samp),再遍历原地旋转轨迹,最后不行再生成逃逸轨迹。
    5)TrajectoryPlanner::generateTrajectory:根据当前位置(x,y,theta)、速度(vx,vy,vtheta),使用最大加速度(acc_x,acc_y,acc_thea)达到期望速度(vx_samp,vy_samp,vtheta_samp),达到期望速度后匀速运动,总时间时间为sim_time_,!heading_scoring_时步数为num_steps = int(sim_time_ / sim_granularity_ + 0.5);heading_scoring_时num_steps = int(max((vmag * sim_time_) / sim_granularity_, fabs(vtheta_samp) / angular_sim_granularity_) + 0.5)。按dt = sim_time_ / num_steps进行加减速采样。最后输出的cmd_vel就是期望速度(vx_samp,vy_samp,vtheta_samp)。
cpp 复制代码
  void TrajectoryPlanner::generateTrajectory(
      double x, double y, double theta,
      double vx, double vy, double vtheta,
      double vx_samp, double vy_samp, double vtheta_samp,
      double acc_x, double acc_y, double acc_theta,
      double impossible_cost,
      Trajectory& traj) {

    // make sure the configuration doesn't change mid run
    boost::mutex::scoped_lock l(configuration_mutex_);

    double x_i = x;
    double y_i = y;
    double theta_i = theta;

    double vx_i, vy_i, vtheta_i;

    vx_i = vx;
    vy_i = vy;
    vtheta_i = vtheta;

    //compute the magnitude of the velocities
    double vmag = hypot(vx_samp, vy_samp);

    //compute the number of steps we must take along this trajectory to be "safe"
    int num_steps;
    if(!heading_scoring_) {
      num_steps = int(max((vmag * sim_time_) / sim_granularity_, fabs(vtheta_samp) / angular_sim_granularity_) + 0.5);
    } else {
      num_steps = int(sim_time_ / sim_granularity_ + 0.5);
    }

    //we at least want to take one step... even if we won't move, we want to score our current position
    if(num_steps == 0) {
      num_steps = 1;
    }

    double dt = sim_time_ / num_steps;
    double time = 0.0;

    //create a potential trajectory
    traj.resetPoints();
    traj.xv_ = vx_samp;
    traj.yv_ = vy_samp;
    traj.thetav_ = vtheta_samp;
    traj.cost_ = -1.0;

    //initialize the costs for the trajectory
    double path_dist = 0.0;
    double goal_dist = 0.0;
    double occ_cost = 0.0;
    double heading_diff = 0.0;

    for(int i = 0; i < num_steps; ++i){
      //get map coordinates of a point
      unsigned int cell_x, cell_y;

      //we don't want a path that goes off the know map
      if(!costmap_.worldToMap(x_i, y_i, cell_x, cell_y)){
        traj.cost_ = -1.0;
        return;
      }

      //check the point on the trajectory for legality
      double footprint_cost = footprintCost(x_i, y_i, theta_i);

      //if the footprint hits an obstacle this trajectory is invalid
      if(footprint_cost < 0){
        traj.cost_ = -1.0;
        return;
        //TODO: Really look at getMaxSpeedToStopInTime... dues to discretization errors and high acceleration limits,
        //it can actually cause the robot to hit obstacles. There may be something to be done to fix, but I'll have to
        //come back to it when I have time. Right now, pulling it out as it'll just make the robot a bit more conservative,
        //but safe.
        /*
        double max_vel_x, max_vel_y, max_vel_th;
        //we want to compute the max allowable speeds to be able to stop
        //to be safe... we'll make sure we can stop some time before we actually hit
        getMaxSpeedToStopInTime(time - stop_time_buffer_ - dt, max_vel_x, max_vel_y, max_vel_th);

        //check if we can stop in time
        if(fabs(vx_samp) < max_vel_x && fabs(vy_samp) < max_vel_y && fabs(vtheta_samp) < max_vel_th){
          ROS_ERROR("v: (%.2f, %.2f, %.2f), m: (%.2f, %.2f, %.2f) t:%.2f, st: %.2f, dt: %.2f", vx_samp, vy_samp, vtheta_samp, max_vel_x, max_vel_y, max_vel_th, time, stop_time_buffer_, dt);
          //if we can stop... we'll just break out of the loop here.. no point in checking future points
          break;
        }
        else{
          traj.cost_ = -1.0;
          return;
        }
        */
      }

      occ_cost = std::max(std::max(occ_cost, footprint_cost), double(costmap_.getCost(cell_x, cell_y)));

      //do we want to follow blindly
      if (simple_attractor_) {
        goal_dist = (x_i - global_plan_[global_plan_.size() -1].pose.position.x) *
          (x_i - global_plan_[global_plan_.size() -1].pose.position.x) +
          (y_i - global_plan_[global_plan_.size() -1].pose.position.y) *
          (y_i - global_plan_[global_plan_.size() -1].pose.position.y);
      } else {

        bool update_path_and_goal_distances = true;

        // with heading scoring, we take into account heading diff, and also only score
        // path and goal distance for one point of the trajectory
        if (heading_scoring_) {
          if (time >= heading_scoring_timestep_ && time < heading_scoring_timestep_ + dt) {
            heading_diff = headingDiff(cell_x, cell_y, x_i, y_i, theta_i);
          } else {
            update_path_and_goal_distances = false;
          }
        }

        if (update_path_and_goal_distances) {
          //update path and goal distances
          path_dist = path_map_(cell_x, cell_y).target_dist;
          goal_dist = goal_map_(cell_x, cell_y).target_dist;

          //if a point on this trajectory has no clear path to goal it is invalid
          if(impossible_cost <= goal_dist || impossible_cost <= path_dist){
//            ROS_DEBUG("No path to goal with goal distance = %f, path_distance = %f and max cost = %f",
//                goal_dist, path_dist, impossible_cost);
            traj.cost_ = -2.0;
            return;
          }
        }
      }


      //the point is legal... add it to the trajectory
      traj.addPoint(x_i, y_i, theta_i);

      //calculate velocities
      vx_i = computeNewVelocity(vx_samp, vx_i, acc_x, dt);
      vy_i = computeNewVelocity(vy_samp, vy_i, acc_y, dt);
      vtheta_i = computeNewVelocity(vtheta_samp, vtheta_i, acc_theta, dt);

      //calculate positions
      x_i = computeNewXPosition(x_i, vx_i, vy_i, theta_i, dt);
      y_i = computeNewYPosition(y_i, vx_i, vy_i, theta_i, dt);
      theta_i = computeNewThetaPosition(theta_i, vtheta_i, dt);

      //increment time
      time += dt;
    } // end for i < numsteps

    //ROS_INFO("OccCost: %f, vx: %.2f, vy: %.2f, vtheta: %.2f", occ_cost, vx_samp, vy_samp, vtheta_samp);
    double cost = -1.0;
    if (!heading_scoring_) {
      cost = path_distance_bias_ * path_dist + goal_dist * goal_distance_bias_ + occdist_scale_ * occ_cost;
    } else {
      cost = occdist_scale_ * occ_cost + path_distance_bias_ * path_dist + 0.3 * heading_diff + goal_dist * goal_distance_bias_;
    }
    traj.cost_ = cost;
  }

      /**
       * @brief  Compute x position based on velocity
       * @param  xi The current x position
       * @param  vx The current x velocity
       * @param  vy The current y velocity
       * @param  theta The current orientation
       * @param  dt The timestep to take
       * @return The new x position 
       */
      inline double computeNewXPosition(double xi, double vx, double vy, double theta, double dt){
        return xi + (vx * cos(theta) + vy * cos(M_PI_2 + theta)) * dt;
      }

      /**
       * @brief  Compute y position based on velocity
       * @param  yi The current y position
       * @param  vx The current x velocity
       * @param  vy The current y velocity
       * @param  theta The current orientation
       * @param  dt The timestep to take
       * @return The new y position 
       */
      inline double computeNewYPosition(double yi, double vx, double vy, double theta, double dt){
        return yi + (vx * sin(theta) + vy * sin(M_PI_2 + theta)) * dt;
      }

      /**
       * @brief  Compute orientation based on velocity
       * @param  thetai The current orientation
       * @param  vth The current theta velocity
       * @param  dt The timestep to take
       * @return The new orientation
       */
      inline double computeNewThetaPosition(double thetai, double vth, double dt){
        return thetai + vth * dt;
      }

      //compute velocity based on acceleration
      /**
       * @brief  Compute velocity based on acceleration
       * @param vg The desired velocity, what we're accelerating up to 
       * @param vi The current velocity
       * @param a_max An acceleration limit
       * @param  dt The timestep to take
       * @return The new velocity
       */
      inline double computeNewVelocity(double vg, double vi, double a_max, double dt){
        if((vg - vi) >= 0) {
          return std::min(vg, vi + a_max * dt);
        }
        return std::max(vg, vi - a_max * dt);
      }
  • isGoalReached:根据车辆当前位置和目标位置判断是否到达目标,初始化完成后返回reached_goal_
    2、DWA(Dynamic Window Approach,动态窗口法)
    DWA
    参考链接1参考链接2
    采样不同v和w,根据运动学模型生成多条轨迹;再从中选取最好的1条轨迹,评价函数如下:
    1)方向角函数:轨迹终点方向角与当前目标点方向角;
    2)距离评价函数:距离障碍物越远越好,贴合全局路径越近越好;
    3)速度评价函数:容许范围内越大越好
    4)摆动打分
cpp 复制代码
base_local_planner::OscillationCostFunction oscillation_costs_(摆动打分)
base_local_planner::ObstacleCostFunction obstacle_costs_(避障打分)
base_local_planner::MapGridCostFunction path_costs_(路径跟随打分)
base_local_planner::MapGridCostFunction goal_costs_(指向目标打分)
base_local_planner::MapGridCostFunction goal_front_costs_(前向点指向目标打分)
base_local_planner::MapGridCostFunction alignment_costs_(对齐打分)

代码主要实现在base_local_planner::SimpleScoredSamplingPlanner类中,DWAPlanner初始化std::vector<base_local_planner::TrajectorySampleGenerator*> gen_list用于生成不同采样轨迹,std::vector<base_local_planner::TrajectoryCostFunction*> critics用于打分,包括以上6个打分规则,每项再用scale调节权重cost *= score_function_p->getScale()。以参数形式初始化SimpleScoredSamplingPlanner。

cpp 复制代码
bool DWAPlannerROS::dwaComputeVelocityCommands(geometry_msgs::PoseStamped &global_pose, geometry_msgs::Twist& cmd_vel) {
    // dynamic window sampling approach to get useful velocity commands
    if(! isInitialized()){
      ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
      return false;
    }

    geometry_msgs::PoseStamped robot_vel;
    odom_helper_.getRobotVel(robot_vel);

    /* For timing uncomment
    struct timeval start, end;
    double start_t, end_t, t_diff;
    gettimeofday(&start, NULL);
    */

    //compute what trajectory to drive along
    geometry_msgs::PoseStamped drive_cmds;
    drive_cmds.header.frame_id = costmap_ros_->getBaseFrameID();
    
    // call with updated footprint,调研DWAPlanner::findBestPath
    base_local_planner::Trajectory path = dp_->findBestPath(global_pose, robot_vel, drive_cmds);
    //ROS_ERROR("Best: %.2f, %.2f, %.2f, %.2f", path.xv_, path.yv_, path.thetav_, path.cost_);

    /* For timing uncomment
    gettimeofday(&end, NULL);
    start_t = start.tv_sec + double(start.tv_usec) / 1e6;
    end_t = end.tv_sec + double(end.tv_usec) / 1e6;
    t_diff = end_t - start_t;
    ROS_INFO("Cycle time: %.9f", t_diff);
    */

    //pass along drive commands
    cmd_vel.linear.x = drive_cmds.pose.position.x;
    cmd_vel.linear.y = drive_cmds.pose.position.y;
    cmd_vel.angular.z = tf2::getYaw(drive_cmds.pose.orientation);

    //if we cannot move... tell someone
    std::vector<geometry_msgs::PoseStamped> local_plan;
    if(path.cost_ < 0) {
      ROS_DEBUG_NAMED("dwa_local_planner",
          "The dwa local planner failed to find a valid plan, cost functions discarded all candidates. This can mean there is an obstacle too close to the robot.");
      local_plan.clear();
      publishLocalPlan(local_plan);
      return false;
    }

    ROS_DEBUG_NAMED("dwa_local_planner", "A valid velocity command of (%.2f, %.2f, %.2f) was found for this cycle.", 
                    cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z);

    // Fill out the local plan
    for(unsigned int i = 0; i < path.getPointsSize(); ++i) {
      double p_x, p_y, p_th;
      path.getPoint(i, p_x, p_y, p_th);

      geometry_msgs::PoseStamped p;
      p.header.frame_id = costmap_ros_->getGlobalFrameID();
      p.header.stamp = ros::Time::now();
      p.pose.position.x = p_x;
      p.pose.position.y = p_y;
      p.pose.position.z = 0.0;
      tf2::Quaternion q;
      q.setRPY(0, 0, p_th);
      tf2::convert(q, p.pose.orientation);
      local_plan.push_back(p);
    }

    //publish information to the visualizer

    publishLocalPlan(local_plan);
    return true;
  }




  bool DWAPlannerROS::computeVelocityCommands(geometry_msgs::Twist& cmd_vel) {
    // dispatches to either dwa sampling control or stop and rotate control, depending on whether we have been close enough to goal
    if ( ! costmap_ros_->getRobotPose(current_pose_)) {
      ROS_ERROR("Could not get robot pose");
      return false;
    }
    std::vector<geometry_msgs::PoseStamped> transformed_plan;
    if ( ! planner_util_.getLocalPlan(current_pose_, transformed_plan)) {
      ROS_ERROR("Could not get local plan");
      return false;
    }

    //if the global plan passed in is empty... we won't do anything
    if(transformed_plan.empty()) {
      ROS_WARN_NAMED("dwa_local_planner", "Received an empty transformed plan.");
      return false;
    }
    ROS_DEBUG_NAMED("dwa_local_planner", "Received a transformed plan with %zu points.", transformed_plan.size());

    // update plan in dwa_planner even if we just stop and rotate, to allow checkTrajectory
    dp_->updatePlanAndLocalCosts(current_pose_, transformed_plan, costmap_ros_->getRobotFootprint());

		// 到达目标点后的停止旋转运动控制类latchedStopRotateController_能到达终点就不用dwa了
    if (latchedStopRotateController_.isPositionReached(&planner_util_, current_pose_)) {
      //publish an empty plan because we've reached our goal position
      std::vector<geometry_msgs::PoseStamped> local_plan;
      std::vector<geometry_msgs::PoseStamped> transformed_plan;
      publishGlobalPlan(transformed_plan);
      publishLocalPlan(local_plan);
      base_local_planner::LocalPlannerLimits limits = planner_util_.getCurrentLimits();
      return latchedStopRotateController_.computeVelocityCommandsStopRotate(
          cmd_vel,
          limits.getAccLimits(),
          dp_->getSimPeriod(),
          &planner_util_,
          odom_helper_,
          current_pose_,
          [this](auto pos, auto vel, auto vel_samples){ return dp_->checkTrajectory(pos, vel, vel_samples); });
    } else {//使用dwaComputeVelocityCommands
      bool isOk = dwaComputeVelocityCommands(current_pose_, cmd_vel);
      if (isOk) {
        publishGlobalPlan(transformed_plan);
      } else {
        ROS_WARN_NAMED("dwa_local_planner", "DWA planner failed to produce path.");
        std::vector<geometry_msgs::PoseStamped> empty_plan;
        publishGlobalPlan(empty_plan);
      }
      return isOk;
    }
  }


};


namespace base_local_planner {
  
  SimpleScoredSamplingPlanner::SimpleScoredSamplingPlanner(std::vector<TrajectorySampleGenerator*> gen_list, std::vector<TrajectoryCostFunction*>& critics, int max_samples) {
    max_samples_ = max_samples;
    gen_list_ = gen_list; // DWAPlanner设置轨迹生成器
    critics_ = critics; // DWAPlanner设置轨迹打分器
  }

  double SimpleScoredSamplingPlanner::scoreTrajectory(Trajectory& traj, double best_traj_cost) {
    double traj_cost = 0;
    int gen_id = 0;
    // 每条轨迹遍历critics_进行打分
    for(std::vector<TrajectoryCostFunction*>::iterator score_function = critics_.begin(); score_function != critics_.end(); ++score_function) {
      TrajectoryCostFunction* score_function_p = *score_function;
      if (score_function_p->getScale() == 0) {
        continue;
      }
      double cost = score_function_p->scoreTrajectory(traj);
      if (cost < 0) {
        ROS_DEBUG("Velocity %.3lf, %.3lf, %.3lf discarded by cost function  %d with cost: %f", traj.xv_, traj.yv_, traj.thetav_, gen_id, cost);
        traj_cost = cost;
        break;
      }
      if (cost != 0) {
        cost *= score_function_p->getScale(); // 对每项打分进行加权
      }
      traj_cost += cost;
      if (best_traj_cost > 0) {
        // since we keep adding positives, once we are worse than the best, we will stay worse
        if (traj_cost > best_traj_cost) {
          break;
        }
      }
      gen_id ++;
    }


    return traj_cost;
  }

  bool SimpleScoredSamplingPlanner::findBestTrajectory(Trajectory& traj, std::vector<Trajectory>* all_explored) {
    Trajectory loop_traj;
    Trajectory best_traj;
    double loop_traj_cost, best_traj_cost = -1;
    bool gen_success;
    int count, count_valid;
    for (std::vector<TrajectoryCostFunction*>::iterator loop_critic = critics_.begin(); loop_critic != critics_.end(); ++loop_critic) {
      TrajectoryCostFunction* loop_critic_p = *loop_critic;
      if (loop_critic_p->prepare() == false) {
        ROS_WARN("A scoring function failed to prepare");
        return false;
      }
    }
    // 使用gen_list_遍历所有轨迹
    for (std::vector<TrajectorySampleGenerator*>::iterator loop_gen = gen_list_.begin(); loop_gen != gen_list_.end(); ++loop_gen) {
      count = 0;
      count_valid = 0;
      TrajectorySampleGenerator* gen_ = *loop_gen;
      while (gen_->hasMoreTrajectories()) {
        gen_success = gen_->nextTrajectory(loop_traj);// 生成下一条轨迹
        if (gen_success == false) {
          // TODO use this for debugging
          continue;
        }
        // 对轨迹进行打分
        loop_traj_cost = scoreTrajectory(loop_traj, best_traj_cost);
        if (all_explored != NULL) {
          loop_traj.cost_ = loop_traj_cost;
          all_explored->push_back(loop_traj);
        }

        if (loop_traj_cost >= 0) {
          count_valid++;
          if (best_traj_cost < 0 || loop_traj_cost < best_traj_cost) {
            best_traj_cost = loop_traj_cost;
            best_traj = loop_traj;
          }
        }
        count++;
        if (max_samples_ > 0 && count >= max_samples_) {
          break;
        }        
      }
      if (best_traj_cost >= 0) {
        traj.xv_ = best_traj.xv_;
        traj.yv_ = best_traj.yv_;
        traj.thetav_ = best_traj.thetav_;
        traj.cost_ = best_traj_cost;
        traj.resetPoints();
        double px, py, pth;
        for (unsigned int i = 0; i < best_traj.getPointsSize(); i++) {
          best_traj.getPoint(i, px, py, pth);
          traj.addPoint(px, py, pth);
        }
      }
      ROS_DEBUG("Evaluated %d trajectories, found %d valid", count, count_valid);
      if (best_traj_cost >= 0) {
        // do not try fallback generators
        break;
      }
    }
    return best_traj_cost >= 0;
  }

  
}// namespace

3、TEB(Time Elastic Band ,时间弹力带)
TEB代码

参考论文《Online Trajectory Optimization and Navigation

in Dynamic Environments in ROS》,TEB基于g2o库优化局部离散轨迹点(t,x,y, θ \theta θ)变量,根据相邻帧这些变量可以计算出v和w。由于增加了时间维度,便于根据障碍物预测轨迹进行避障。

检测动态物体步骤如下:

1)使用快滤波器Pf和慢滤波器Ps,当栅格有动态物体时占用概率会增大,Pf会快于Ps,而对于静态或无障碍物两者几乎相等

对于大障碍物公式可利用邻域栅格进行检测,belta用于调节中心栅格及相邻栅格权重:

当两者满足如下条件时,认为栅格中为动态物体的点

2)由上一步得到动态和非动态的二值栅格图,进行腐蚀膨胀和聚类可得到动态物体,如白色块

3)以栅格中心表示物体,使用匈牙利算法可对动态物体进行跟踪,估计障碍物速度、方向。从而可以得到和时间相关的动态栅格地图,用于避障

TEB结合了时间使用如下方式表示轨迹,

目标函数表示如下,fi(b)可以是如下几项

1)跟随路径约束和避障约束

距离障碍物越远越好,贴合全局路径越近越好

2)速度/角速度约束

约束相邻轨迹点的速度和加速度,使其不要超出容许范围

3)运动学越束

使轨迹尽可能平滑,更符合运动学模型

4)最快路径

到达目标点的时间尽可能短

参考论文主要有,基本是C. R¨osmann作者的

17\] C. R¨osmann, F. Hoffmann, T. Bertram. "Integrated online trajectory planning and optimization in distinctive topologies". In: Robotics and Autonomous Systems, Volume 88, pp. 142--153, 2017. \[18\] C. R¨osmann, F. Hoffmann, T. Bertram. "Online Trajectory Planning in ROS under Kinodynamic Constraints with Timed-Elastic-Bands". In: Robot Operating System (ROS) - The Complete Reference 2 (A. Koubaa, ed.), vol. 707 of Studies in Computational Intelligence, Springer International Publishing, 2017. 安装运行: 环境:Ubuntu 18.04 + ROS melodic ```bash $ mkdir -p ~/teb_ws/src $ cd ~/teb_ws/src $ catkin_init_workspace $ git clone https://github.com/rst-tu-dortmund/teb_local_planner.git $ git clone https://github.com/rst-tu-dortmund/teb_local_planner_tutorials.git $ git clone https://github.com/rst-tu-dortmund/costmap_converter.git $ git clone https://github.com/AMRobots/stage_ros.git $ cd ~/teb_ws $ catkin_make $ source devel/setup.bash # 检查是否正确安装 $ roslaunch teb_local_planner_tutorials dyn_obst_corridor_scenario.launch ``` 然后将teb_tutorials文件夹中文件拷到\~/teb_ws/src/teb_local_planner_tutorials对应文件夹下 dyn_obst_corridor_scenario_skew.launch -\> launch corridor_skew.yaml -\> maps move_obstacle.py - \> scripts (替换原文件) move_obstacle_skew_l.py - \> scripts move_obstacle_skew_r.py - \> scripts corridor_skew.world -\> stage ```bash $ roslaunch teb_local_planner_tutorials dyn_obst_corridor_scenario_skew.launch ``` 二、运动学模型 常见两轮差速和car_like汽车类模型大多车是非全向的,只有前向速度和旋转速度,而可以左右移动的全向移动的车才有y方向速度。根据运动学模型,由局部规划器输出的线速度v和角速度w,得到车辆控制量,控制车辆移动。 1)两轮差速模型:参考《ROS机器人编程》-表允晳 赵汉哲 郑黎蝹 林泰勋。无方向盘,通过左右轮差速控制方向,控制量一般是左右轮速度vl、vr。![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/31f940a3f1254c398dd885175fef5d12.png)![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d2a02561fbca4bea8b070c99534d6ab1.png) 当局部规划器给出vk、wk后,可通过下式计算左右轮速度: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b014bdb7f0fc41cb8a9b3bd40418d4b7.png) 2)[汽车模型](https://www.jishulink.com/post/1879235) 汽车car-like可能是前驱、后驱或者四驱,可简化成Bicycle Model(自行车模型)。根据v和w输出油门(与后轮速度Vr(Vrear)直接相关)及方向盘转向角 θ \\theta θ控制量。一般使用Ackermann(阿克曼)转向结构几何来计算,转弯时四个轮子都必须绕同一个瞬时圆心转,圆半径不一样。如下图所示,L为前轮和后轮轴距,r为后轮转弯半径,w为后轮转速。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7b25c7a79e874cd7887a0c2130c0fc4b.png) 三、避障算法 静态障碍物避障与全局规划区一样,但动态障碍物需预测其轨迹进行避障。 1.车辆模型 使用四方形、圆形表示? 2.障碍物形状 以圆形表示比较简单,只需计算点或线与圆心距离。不规则轮廓或轨迹能否通过离散点计算? 3.距离计算 点-点、点-线距离。

相关推荐
玄同7652 小时前
Llama.cpp 全实战指南:跨平台部署本地大模型的零门槛方案
人工智能·语言模型·自然语言处理·langchain·交互·llama·ollama
格林威2 小时前
Baumer相机金属焊缝缺陷识别:提升焊接质量检测可靠性的 7 个关键技术,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·视觉检测·堡盟相机
独处东汉2 小时前
freertos开发空气检测仪之按键输入事件管理系统设计与实现
人工智能·stm32·单片机·嵌入式硬件·unity
你大爷的,这都没注册了2 小时前
AI提示词,zero-shot,few-shot 概念
人工智能
AC赳赳老秦2 小时前
DeepSeek 辅助科研项目申报:可行性报告与经费预算框架的智能化撰写指南
数据库·人工智能·科技·mongodb·ui·rabbitmq·deepseek
瑞华丽PLM2 小时前
国产PLM软件源头厂家的AI技术应用与智能化升级
人工智能·plm·国产plm·瑞华丽plm·瑞华丽
你撅嘴真丑2 小时前
第八章 - 贪心法
开发语言·c++·算法
xixixi777772 小时前
基于零信任架构的通信
大数据·人工智能·架构·零信任·通信·个人隐私
玄同7652 小时前
LangChain v1.0+ Prompt 模板完全指南:构建精准可控的大模型交互
人工智能·语言模型·自然语言处理·langchain·nlp·交互·知识图谱