路径规划——动态规划在Apollo的Planner中的应用及C++代码实现

什么是动态规划(Dynamic Programming, DP)

  1. 贝尔曼最优原理

  多阶段决策过程的特点是每个阶段都要进行决策,具有n个阶段的决策过程的策略是由n个相继进行的阶段决策构成的决策序列。由于前阶段的终止状态又是后一阶段的初始状态,因此确定阶段最优决策不能只从本阶段的效应出发,必须通盘考虑,整体规划。就是说,阶段k的最优决策不应只是本阶段的最优,而必须是本阶段及其所有后续阶段的总体最优,即关于整个后部子过程的最优决策。 对此,贝尔曼在深入研究的基础上,针对具有无后效性的多阶段决策过程的特点,提出了著名的多阶段决策的最优性原理:
   整个过程的最优策略具有这样的性质:即无论过程过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。 简而言之,最优性原理的含意就是:最优策略的任何一部分子策略也必须是最优的。

  1. 动态规划算法

基于动态规划的路径选择算法

EM planner中,路径的选择是基于S-L坐标系进行的,主要分为以下几个步骤:

  1. 道路撒点
    撒点规则主要由以下几个方面确定:车辆的宽度,车辆的位置,车道宽度,车辆速度,撒点的最大步长(S和L方向),撒点的最小步长(S和L方向),撒点的最小长度,撒点的最大长度等;

  2. 利用DP生成Cost最小的路径
    动态规划示意图
    如上图, p点的cost计算方式为:
    p . c o s t = s t d : : m i n { p 1 . c o s t + R 1 , . . . , p 7 . c o s t + R 7 } p.{cost} = std::min\{p_1.cost + R_1, ..., p_7.cost +R_7\} p.cost=std::min{p1.cost+R1,...,p7.cost+R7}.
    其中, R 1 R_1 R1表示从节点 p 1 p_1 p1到节点 p p p的代价。

C++代码实现

// node.h file
struct SlPoint {
  SlPoint(const double _s, const double _l) : s(_s), l(_l) {
  }
  double s;
  double l;
};

struct Node {
  Node(const SlPoint& _sl_point)
      : sl_point(_sl_point), pre_node(nullptr), cost(std::numeric_limits<double>::max()) {
  }
  SlPoint sl_point;
  Node* pre_node;
  double cost;
};
// class DpPath file
class DpPath final {
 public:
  explicit DpPath(const std::vector<std::vector<SlPoint>>& sample_points,
                  const std::vector<Box2d>& obstacles);

  bool Search(std::vector<SlPoint>* const path);

 private:
  void Init(const std::vector<std::vector<SlPoint>>& sample_points);

  void CalculateCostTable();

  void CalculateCostAt(const int32_t l, const int32_t s);

  double CalculateAllObstacleCost(const SlPoint& pre_point, const SlPoint& cur_point) const;

  double CalculateObstacleCost(const Box2d& obs, const Box2d& veh) const;

  double CalculateReferenceLineCost(const SlPoint& pre_point, const SlPoint& cur_point) const;

 private:
  std::vector<std::vector<Node>> cost_table_;
  std::vector<Box2d> obstacles_;
  const double vehicle_length_ = 2.0;
  const double vehicle_width_ = 1.0;
};
DpPath::DpPath(const std::vector<std::vector<SlPoint>>& sample_points,
               const std::vector<Box2d>& obstacles)
    : obstacles_(obstacles) {
  CHECK_GT(sample_points.size(), 1);
  CHECK_EQ(sample_points.front().size(), 1);
  Init(sample_points);
}

bool DpPath::Search(std::vector<SlPoint>* const path) {
  CHECK(path!=nullptr);

  CalculateCostTable();
  auto& last_level = cost_table_.back();
  Node* min_cost_node = nullptr;
  double min_cost = std::numeric_limits<double>::max();
  for (auto& p : last_level) {
    if (p.cost < min_cost) {
      min_cost = p.cost;
      min_cost_node = &p;
    }
  }
  if (min_cost_node == nullptr || min_cost == std::numeric_limits<double>::max()) {
    return false;
  }

  while (min_cost_node != nullptr) {
    path->emplace_back(min_cost_node->sl_point);
    min_cost_node = min_cost_node->pre_node;
  }
  std::reverse(path->begin(), path->end());
  return true;
}

void DpPath::Init(const std::vector<std::vector<SlPoint>>& sample_points) {
  for (const auto& level : sample_points) {
    std::vector<Node> level_points;
    for (const auto& p : level) {
      level_points.emplace_back(p);
    }
    cost_table_.emplace_back(level_points);
  }
}

void DpPath::CalculateCostTable() {
  cost_table_[0][0].cost = 0.0;
  for (uint32_t s = 1; s < cost_table_.size(); ++s) {
    for (uint32_t l = 0; l < cost_table_[s].size(); ++l) {
      CalculateCostAt(s, l);
    }
  }
}

void DpPath::CalculateCostAt(const int32_t s, const int32_t l) {
  auto& pre_level = cost_table_[s - 1];
  double min_cost = std::numeric_limits<double>::max();
  for (auto& pre_point : pre_level) {
    double cost = CalculateAllObstacleCost(pre_point.sl_point, cost_table_[s][l].sl_point) +
                  CalculateReferenceLineCost(pre_point.sl_point, cost_table_[s][l].sl_point);
    cost += pre_point.cost;
    if (cost < min_cost) {
      min_cost = cost;
      cost_table_[s][l].pre_node = &pre_point;
      cost_table_[s][l].cost = min_cost;
    }
  }
}

double DpPath::CalculateAllObstacleCost(const SlPoint& pre_point, const SlPoint& cur_point) const {
  const double curve_length = cur_point.s - pre_point.s;
  QuinticPolynomialCurve1d curve(pre_point.l, 0.0, 0.0, cur_point.l, 0.0, 0.0, curve_length);
  double cost = 0.0;
  constexpr double kStep = 0.1;
  for (double s = 0.0; s < curve_length; s += kStep) {
    double vehicle_l = curve.Evaluate(0, s);
    double vehicle_s = pre_point.s + s;
    double vehicle_heading = std::atan(curve.Evaluate(1, s));
    Box2d veh({vehicle_s, vehicle_l}, vehicle_heading, vehicle_length_, vehicle_width_);
    for (const auto& obs : obstacles_) {
      cost += CalculateObstacleCost(obs, veh);
    }
  }
  return cost;
}

double DpPath::CalculateObstacleCost(const Box2d& obs, const Box2d& veh) const {
  if (obs.HasOverlap(veh)) {
    return std::numeric_limits<double>::max();
  }
  const double dis = obs.DistanceTo(veh);
  if (dis > 2 * vehicle_width_) {
    return 0.0;
  }
  return 1.0 / (dis + std::numeric_limits<double>::epsilon());
}

double DpPath::CalculateReferenceLineCost(const SlPoint& pre_point,
                                          const SlPoint& cur_point) const {
  const double curve_length = cur_point.s - pre_point.s;
  QuinticPolynomialCurve1d curve(pre_point.l, 0.0, 0.0, cur_point.l, 0.0, 0.0, curve_length);
  double cost = 0.0;
  constexpr double kStep = 0.1;
  for (double s = 0.0; s < curve_length; s += kStep) {
    double l = curve.Evaluate(0, s);
    cost += std::fabs(l * l);
  }
  return cost;
}
// test file
class DpPathTest : public ::testing::Test {
 public:
  void SetUp() {
    SlPoint vehicle_position(0.0, 0.0);
    sample_points_.emplace_back(std::vector<SlPoint>{vehicle_position});
    for (double s = 3; s < 20; s += 3) {
      std::vector<SlPoint> level_points;
      for (double l = -1.5; l < 2.0; l += 0.5) {
        level_points.emplace_back(s, l);
      }
      sample_points_.emplace_back(level_points);
    }
  }

 protected:
  std::shared_ptr<DpPath> dp_path_ = nullptr;
  std::vector<std::vector<SlPoint>> sample_points_;
  std::vector<Box2d> obstacles_;
};

TEST_F(DpPathTest, Search1) {
  dp_path_ = std::make_shared<DpPath>(sample_points_, obstacles_);
  std::vector<SlPoint> path;
  bool ret = dp_path_->Search(&path);
  EXPECT_TRUE(ret);
  EXPECT_GT(path.size(), 2);
}

TEST_F(DpPathTest, Search2) {
  Box2d obs1({3, -0.5}, 0.0, 0.8, 1.5);
  Box2d obs2({12, 0.5}, 0.0, 0.8, 1.5);
  obstacles_.emplace_back(obs1);
  obstacles_.emplace_back(obs2);
  dp_path_ = std::make_shared<DpPath>(sample_points_, obstacles_);
  std::vector<SlPoint> path;
  bool ret = dp_path_->Search(&path);
  EXPECT_TRUE(ret);
  EXPECT_GT(path.size(), 2);
}

测试结果

Search1 result
Search2 Result
Search2 gif

说明

代码中用到的Box2d, QuinticPolynomialCurve1d等库函数,见Apollo开源代码,不做详细叙述,在计算cost的时候,点的过滤,车辆的运动学模型等限制条件,不做讨论,本文假设车辆在道路中心线上,即 l = 0 l=0 l=0

  • 12
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
### 回答1: E二次规划(EQP)是一种最优化问题的数学模型,它允许在一组线性约束条件的基础上求解二次目标函数的最小值。在Apollo路径规划,通过E二次规划求解,可以让自动驾驶车辆在行驶过程遵循最短、最安全、最有效的路径,从而有效地提高路况下自动驾驶车辆的驾驶能力。 具体来说,在Apollo路径规划,E二次规划是用来求解三维Carla地图上的自动驾驶路径。首先,根据车辆当前的位置和目标位置,计算出自动驾驶车辆的路径段。然后,通过EQP模型来寻找最优路径,从而确保自动驾驶车辆能够以最快的速度、最短的路线到达目的地。 E二次规划Apollo路径规划应用,不但可以提高自动驾驶车辆的路况适应性,还能够有效降低事故风险和提升驾驶舒适度。目前,E二次规划已经成为自动驾驶领域最重要的路径规划技术之一,将在未来自动驾驶车辆的运作发挥越来越重要的作用。 ### 回答2: E二次规划(EQP)在Apollo路径规划是一种重要的数学算法,用于寻找最优的车辆路径Apollo是一款无人驾驶平台,其路径规划是其的一个重要模块,用于规划车辆在道路上移动的路径。EQP是一种二次规划问题,其解决方法是迭代计算非线性方程组。 在Apollo路径规划,EQP可以用来处理不同轮廓的车辆在具有复杂形状的车道上的路径规划。它基于车辆的轮廓和其它约束条件,计算出车辆移动的最佳路径。这种路径规则可以让车辆避免障碍物和其他难以通过的地方,同时保证车辆的稳定性和安全性。EQP算法可以使用多种方法来求解最优解,比如牛顿法和Uzawa-Brahmian算法等。 EQP的一大优势是能够处理非线性约束。例如,如果车道侧边缘变曲,车辆的轮廓也将发生变化。这时就需要EQP来计算新的路径规划。EQP还能够同时考虑多个约束条件,例如车辆转弯半径和最大加速度等,确保路径规划的合理性和安全性。 总之,EQP算法是Apollo路径规划非常重要的一个组成部分。它可以让车辆在复杂的路况行驶,避免障碍物和危险区域,同时保证车辆的安全和稳定性。EQP虽然运算量大,但在这个领域是最好的解决方案之一。 ### 回答3: APOLLO是一款自动驾驶系统,是国最早的自动驾驶系统之一,该系统具有高度自动化和安全性,它使用了多种传感器和路况信息来实现对车辆定位、路径规划、行驶决策等功能的控制。 其APOLLO路径规划的e二次规划是一种重要的优化方法,它主要是用来解决路径规划的非线性规划问题。具体来讲,e二次规划是在求解路径规划最优路径时非常有用的工具,它可以更快更准确地确定车辆的路线以及行驶速度、制动等参数,以保证车辆行驶的稳定性和安全性。 e二次规划的核心思想是利用二次函数来近似描述非线性约束条件下的最优化问题,将原问题转化为一个凸二次规划问题。在路径规划,利用e二次规划方法求解可得到一个满足约束条件的最优路径。 具体来讲,APOLLO路径规划的e二次规划通过约束方法和拉格朗日乘子法进行求解。其主要步骤如下: 1. 确定目标函数,即车辆行驶路径的代价函数。这个代价函数包含了车辆行驶的时间、路线、速度、制动等参数,使得车辆能够顺利地到达目的地。 2. 确定约束条件,将代价函数视作目标函数,同时约束条件是车辆在行驶过程受到的各种限制,如车速、转弯半径、转角限制、障碍物等。 3. 在约束情况下,利用拉格朗日乘子法将优化目标转换为一个带有约束的最小二乘问题。 4. 通过求解该最小二乘问题,得到满足约束条件的最优路径。此时,可以通过数值计算方法对路径进行优化,例如差分进化算法、遗传算法等。 总之,APOLLO路径规划的e二次规划通过近似描述非线性约束条件下的最优化问题,将原问题转化为一个凸二次规划问题,使车辆能够更加高效、安全地行驶。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值