当前位置:   article > 正文

路规算法详细解读(一)—— FAST-Planner重要部分代码解读_fast_planner

fast_planner

由于最近的研究需要,需要对Fast-planner和Ego-planner的代码了解,所以写出这篇代码解读文章,本文持续更新。废话不多说了,上干货!

本文基于以下大佬的代码解析基础上去阅读、理解、总结而成,对我的帮助真的特别大。觉得有帮助的朋友记得给大佬点赞!

Fast-Planner代码阅读-1. Robust and Efficient Quadrotor Trajectory Generation for Fast Autonomous Flight_fast planner b样条_养生少年小余的博客-CSDN博客

本文之所以成就之高,原因在于其框架的完整性,代码主要解读包含三大板块:kinodynamic a_star 路径搜索non_uniform_bspline均匀B样条轨迹优化、非均匀B样条轨迹优化三大主要部分。

一、kinodynamic a_star 路径搜索

实现该功能的文件为/fast_planner/path_searching/src/kinodynamic_astar.cpp,将围绕其主要循环函数展开。

  1. int KinodynamicAstar::search(Eigen::Vector3d start_pt, Eigen::Vector3d start_v, Eigen::Vector3d start_a,
  2. Eigen::Vector3d end_pt, Eigen::Vector3d end_v, bool init, bool dynamic, double time_start)

该函数传入参数为分别为start_pt(起点位置)、start_v(起点速度)、start_a(起点加速度)、end_pt(终点位置)、end_v(终点速度)、init(初始化成功标志位)、dynamic(动静规划标志位)、time_start(起始时间)。

1. 读取并初始化参数、计算起点代价、优化时间,将起点加入到openlist中:

该步骤是为了将传入的vector转化为可带入运算的状态矩阵

  1. //函数传入参数为起始点的位置、速度、加速度、终点位置、速度、初始化标志位、动态(可行)标志位?、起始时间
  2. start_vel_ = start_v;//取出传入的起始点的速度
  3. start_acc_ = start_a;//传入起始点的加速度
  4. //path_node_pool_ 可能在初始化时被分配一定数量的节点,这些节点在运行时被重复使用,而不是每次需要节点时都动态地分配内存。这有助于提高性能,避免频繁的内存分配和释放。
  5. PathNodePtr cur_node = path_node_pool_[0];//取出第一个路径点赋给当前节点
  6. cur_node->parent = NULL;//父节点
  7. cur_node->state.head(3) = start_pt;//state矩阵前3列记录位置
  8. cur_node->state.tail(3) = start_v;//state矩阵后三列记录速度
  9. cur_node->index = posToIndex(start_pt);//获取全剧坐标系下的位置索引
  10. cur_node->g_score = 0.0;//记录当前点成本代价值
  11. Eigen::VectorXd end_state(6);//初始化目标点状态的
  12. Eigen::Vector3i end_index;//记录终点索引值
  13. double time_to_goal;//路径规划时间
  14. end_state.head(3) = end_pt;
  15. end_state.tail(3) = end_v;
  16. end_index = posToIndex(end_pt);
  17. cur_node->f_score = lambda_heu_ * estimateHeuristic(cur_node->state, end_state, time_to_goal);//记录当前节点的代价函数
  18. cur_node->node_state = IN_OPEN_SET;//标记为待探索列表
  19. open_set_.push(cur_node);//将当前节点添加到openlist中
  20. use_node_num_ += 1;//已探索点个数记录

其中,estimateHeuristic用于计算两点之间的启发函数代价,传入参数为起点与终点的状态,返回值为计算所得的代价值并且同时更新到达终点的最优时间optimal_time

  1. double KinodynamicAstar::estimateHeuristic(Eigen::VectorXd x1, Eigen::VectorXd x2, double& optimal_time)
  2. {
  3. const Vector3d dp = x2.head(3) - x1.head(3);//位置变化量
  4. const Vector3d v0 = x1.segment(3, 3);//起点速度矩阵
  5. const Vector3d v1 = x2.segment(3, 3);//终点速度矩阵
  6. double c1 = -36 * dp.dot(dp);//算一下alpha,belta带入后就能够清楚带入参数的含义了
  7. double c2 = 24 * (v0 + v1).dot(dp);
  8. double c3 = -4 * (v0.dot(v0) + v0.dot(v1) + v1.dot(v1));
  9. double c4 = 0;
  10. double c5 = w_time_;
  11. std::vector<double> ts = quartic(c5, c4, c3, c2, c1);//对下列c式子求导数,令其为0
  12. double v_max = max_vel_ * 0.5;
  13. double t_bar = (x1.head(3) - x2.head(3)).lpNorm<Infinity>() / v_max;//最短时间限制
  14. ts.push_back(t_bar);
  15. double cost = 100000000;
  16. double t_d = t_bar;
  17. for (auto t : ts)
  18. {
  19. if (t < t_bar)//小于最小时间不合理则跳出
  20. continue;
  21. double c = -c1 / (3 * t * t * t) - c2 / (2 * t * t) - c3 / t + w_time_ * t;//将alpha,belta带入到J*中化简得
  22. if (c < cost)
  23. {
  24. cost = c;
  25. t_d = t;
  26. }
  27. }
  28. optimal_time = t_d;
  29. return 1.0 * (1 + tie_breaker_) * cost;//返回启发式函数代价并且将对应的路径时间赋给optimal_time
  30. }

这部分对应论文的:

庞特里亚金极大值原理的基本思想是,如果在某个时间点,存在一个最优轨迹或最优控制策略, 本论文中的启发函数利用庞特里亚金原理解决两点边值问题,得到最优解后用最优解的控制代价作为启发函数。quartic函数是求解4次方程的函数,四次方程函数是由(5)将alpha,belta带入后求导所得到的,quartic函数用于返回最优的控制时间量。关于时间的一元四次方程是通过费拉里方法求解的,需要嵌套一个元三次方程进行求解,也就是代码中应的cubic函数。

  1. //四次方程的费拉里解法,https://zh.wikipedia.org/wiki/%E5%9B%9B%E6%AC%A1%E6%96%B9%E7%A8%8B,降次为三次方程电泳cubic
  2. vector<double> KinodynamicAstar::quartic(double a, double b, double c, double d, double e)
  3. {
  4. vector<double> dts;
  5. double a3 = b / a;
  6. double a2 = c / a;
  7. double a1 = d / a;
  8. double a0 = e / a;
  9. vector<double> ys = cubic(1, -a2, a1 * a3 - 4 * a0, 4 * a2 * a0 - a1 * a1 - a3 * a3 * a0);
  10. double y1 = ys.front();
  11. double r = a3 * a3 / 4 - a2 + y1;
  12. if (r < 0)
  13. return dts;
  14. double R = sqrt(r);
  15. double D, E;
  16. if (R != 0)
  17. {
  18. D = sqrt(0.75 * a3 * a3 - R * R - 2 * a2 + 0.25 * (4 * a3 * a2 - 8 * a1 - a3 * a3 * a3) / R);
  19. E = sqrt(0.75 * a3 * a3 - R * R - 2 * a2 - 0.25 * (4 * a3 * a2 - 8 * a1 - a3 * a3 * a3) / R);
  20. }
  21. else
  22. {
  23. D = sqrt(0.75 * a3 * a3 - 2 * a2 + 2 * sqrt(y1 * y1 - 4 * a0));
  24. E = sqrt(0.75 * a3 * a3 - 2 * a2 - 2 * sqrt(y1 * y1 - 4 * a0));
  25. }
  26. if (!std::isnan(D))
  27. {
  28. dts.push_back(-a3 / 4 + R / 2 + D / 2);
  29. dts.push_back(-a3 / 4 + R / 2 - D / 2);
  30. }
  31. if (!std::isnan(E))
  32. {
  33. dts.push_back(-a3 / 4 - R / 2 + E / 2);
  34. dts.push_back(-a3 / 4 - R / 2 - E / 2);
  35. }
  36. return dts;
  37. }
  38. vector<double> KinodynamicAstar::cubic(double a, double b, double c, double d)
  39. {
  40. vector<double> dts;
  41. double a2 = b / a;
  42. double a1 = c / a;
  43. double a0 = d / a;
  44. double Q = (3 * a1 - a2 * a2) / 9;
  45. double R = (9 * a1 * a2 - 27 * a0 - 2 * a2 * a2 * a2) / 54;
  46. double D = Q * Q * Q + R * R;
  47. if (D > 0)
  48. {
  49. double S = std::cbrt(R + sqrt(D));
  50. double T = std::cbrt(R - sqrt(D));
  51. dts.push_back(-a2 / 3 + (S + T));
  52. return dts;
  53. }
  54. else if (D == 0)
  55. {
  56. double S = std::cbrt(R);
  57. dts.push_back(-a2 / 3 + S + S);
  58. dts.push_back(-a2 / 3 - S);
  59. return dts;
  60. }
  61. else//<0
  62. {
  63. double theta = acos(R / sqrt(-Q * Q * Q));
  64. dts.push_back(2 * sqrt(-Q) * cos(theta / 3) - a2 / 3);
  65. dts.push_back(2 * sqrt(-Q) * cos((theta + 2 * M_PI) / 3) - a2 / 3);
  66. dts.push_back(2 * sqrt(-Q) * cos((theta + 4 * M_PI) / 3) - a2 / 3);
  67. return dts;
  68. }
  69. }

cubic函数中区分了Delta的情况采用了不同的方法求根,判别式>=0的情况利用了求根公式,判别式<0的情况使用了三角函数解法。最终求得到最优的控制量t,并且更新了最优时间。

关于dynamic标志位(我猜的,水平有限暂时这么理解,有清楚的大佬评论教我)

  1. if (dynamic)//为真时记录时间、将当前节点时间信息记录到expande_node中
  2. {
  3. time_origin_ = time_start;
  4. cur_node->time = time_start;
  5. cur_node->time_idx = timeToIndex(time_start);
  6. expanded_nodes_.insert(cur_node->index, cur_node->time_idx, cur_node);
  7. // cout << "time start: " << time_start << endl;
  8. }
  9. else
  10. expanded_nodes_.insert(cur_node->index, cur_node);//仅将当前节点的空间信息插入到 expanded_nodes_ 中
  11. PathNodePtr neighbor = NULL;//初始化邻居节点为空
  12. PathNodePtr terminate_node = NULL;//终点节点为空
  13. bool init_search = init;//初始化标记位
  14. const int tolerance = ceil(1 / resolution_);//容差,变换前每1单位表示的距离
  • 如果 dynamictrue

    • 该搜索算法会考虑时间信息,即路径规划是在动态环境中进行的。
    • 每个节点除了空间状态外,还包含了时间信息(如 cur_node->timecur_node->time_idx)。
    • 节点被插入到 expanded_nodes_ 中时,时间信息也会被考虑。
    • 最终搜索的路径是在三维空间和时间维度上的。
  • 如果 dynamicfalse

    • 该搜索算法只考虑空间状态,即路径规划是在静态环境中进行的。
    • 时间信息对于节点的存储和搜索不被考虑。
    • 最终搜索的路径仅包含在三维空间中。

2.主要循环:

while循环是大头,包含了轨迹检查、节点拓展、节点剪枝、轨迹细化等功能。当open_set待搜索列表不为空时就一直执行(备注很清楚,是主观解读,如果有错误还请评论区指正):

(1). 终止条件:

  1. //基本循环函数
  2. while (!open_set_.empty())
  3. {
  4. cur_node = open_set_.top();//在open_set中取节点。
  5. // Terminate?
  6. /*标志位在这个搜索算法中的主要作用是为了提前终止搜索。它表示当前节点是否已经离起点足够远,
  7. 超过了预定的 horizon_ 阈值。这个设计可能是为了在搜索空间较大时,避免过度的搜索,以提高算法的效率。
  8. ,当 reach_horizon 为真时,意味着当前节点已经离起点足够远,可能是因为在搜索空间中没有更好的路径,
  9. 或者当前路径不太可能变得更好。在这种情况下,算法可以提前结束搜索,以减少计算时间。
  10. */
  11. bool reach_horizon = (cur_node->state.head(3) - start_pt).norm() >= horizon_;
  12. bool near_end = abs(cur_node->index(0) - end_index(0)) <= tolerance &&//判断是否到达终点了。
  13. abs(cur_node->index(1) - end_index(1)) <= tolerance &&
  14. abs(cur_node->index(2) - end_index(2)) <= tolerance;
  15. if (reach_horizon || near_end)//离起点足够远或者判定到达起点了,停止条件当超出一定范围或者已经找到终点
  16. {
  17. terminate_node = cur_node;//将当前节点设为终点
  18. retrievePath(terminate_node);//反向搜索路径
  19. if (near_end)//当确实是靠近终点时
  20. {
  21. // Check whether shot traj exist
  22. estimateHeuristic(cur_node->state, end_state, time_to_goal);//计算优化的最小时间
  23. computeShotTraj(cur_node->state, end_state, time_to_goal);//带入优化的时间判断轨迹是否合理
  24. if (init_search)//如果初始化成功
  25. ROS_ERROR("Shot in first search loop!");
  26. }
  27. }
  28. if (reach_horizon)//先判断是否超出范围
  29. {
  30. if (is_shot_succ_)//搜索到的路径安全可行的
  31. {
  32. std::cout << "reach end" << std::endl;//此处终点是最远点(其实也不算是规划成功,因为超出距离后到达的是最远处路径可行,并非是原来要找的终点)
  33. return REACH_END;
  34. }
  35. else
  36. {
  37. std::cout << "reach horizon" << std::endl;//路径动态不可行(规划失败)
  38. return REACH_HORIZON;
  39. }
  40. }
  41. if (near_end)
  42. {
  43. if (is_shot_succ_)
  44. {
  45. std::cout << "reach end" << std::endl;
  46. return REACH_END;
  47. }
  48. else if (cur_node->parent != NULL)//规划路径动态不可行,但是上一节点是可行的,此时为规划到接近终点的状态
  49. {
  50. std::cout << "near end" << std::endl;
  51. return NEAR_END;
  52. }
  53. else
  54. {
  55. std::cout << "no path" << std::endl;
  56. return NO_PATH;
  57. }
  58. }
  59. /*
  60. 上述代码皆是单个循环内单个openlist节点内部的终止的判定条件,类似于递归代码的逻辑
  61. 下方代码可以看作是递归单个节点不满足逻辑时候需要处理的步骤(反正我是这么理解的)
  62. */

(2).节点扩张:

当节点不满足终止条件(到达终点或超出探索距离)时,以当前节点为根,离散输入控制量,得到下一状态。

  1. //当前节点无法到达,进行节点扩张
  2. open_set_.pop();//弹出该节点
  3. cur_node->node_state = IN_CLOSE_SET;//将已经探索的节点加入到close_list中,标记为已探索
  4. iter_num_ += 1;//搜索完的点+1
  5. double res = 1 / 2.0, time_res = 1 / 1.0, time_res_init = 1 / 20.0;//初始化时间常数
  6. Eigen::Matrix<double, 6, 1> cur_state = cur_node->state;//记录当前节点状态
  7. Eigen::Matrix<double, 6, 1> pro_state;//下一状态
  8. vector<PathNodePtr> tmp_expand_nodes;//临时扩展节点列表
  9. Eigen::Vector3d um;//离散的控制量,这里指的是三维上加速度
  10. double pro_t;//拓展节点的时间戳
  11. vector<Eigen::Vector3d> inputs;//输入
  12. vector<double> durations;//单位输入控制量的持续时间
  13. /*
  14. 这里大家要首先明确一点,init_search标志位用来标志是否用不连续的初始状态重试搜索。
  15. 本人的理解是如果有初始的节点输入状态存储,但是由于路径规划失败导致无法到达终点
  16. 所以需要拓展新的节点重新规划路径。
  17. */
  18. if (init_search)//具有上次搜索失败的离散状态量
  19. {
  20. inputs.push_back(start_acc_);//存储起始状态的加速度
  21. for (double tau = time_res_init * init_max_tau_; tau <= init_max_tau_ + 1e-3;
  22. tau += time_res_init * init_max_tau_)
  23. durations.push_back(tau);
  24. init_search = false;
  25. }//离散化时间
  26. else//没有初始离散状态,未初始化成功,通过最大最小加速度初始化离散加速度后再离散时间
  27. {
  28. for (double ax = -max_acc_; ax <= max_acc_ + 1e-3; ax += max_acc_ * res)
  29. for (double ay = -max_acc_; ay <= max_acc_ + 1e-3; ay += max_acc_ * res)
  30. for (double az = -max_acc_; az <= max_acc_ + 1e-3; az += max_acc_ * res)
  31. {
  32. um << ax, ay, az;
  33. inputs.push_back(um);
  34. }
  35. for (double tau = time_res * max_tau_; tau <= max_tau_; tau += time_res * max_tau_)
  36. durations.push_back(tau);//离散化时间
  37. }
  38. // cout << "cur state:" << cur_state.head(3).transpose() << endl;
  39. //组合不同的时间与加速度组合,得到不一样的下一状态集合
  40. for (int i = 0; i < inputs.size(); ++i)
  41. for (int j = 0; j < durations.size(); ++j)
  42. {
  43. um = inputs[i];
  44. double tau = durations[j];
  45. stateTransit(cur_state, pro_state, um, tau);//输入时间与加速度离散值,更新pro_state
  46. pro_t = cur_node->time + tau;//拓展状态时间记录
  47. Eigen::Vector3d pro_pos = pro_state.head(3);//拓展位置向量
  48. // Check if in close set
  49. Eigen::Vector3i pro_id = posToIndex(pro_pos);//拓展位置世界坐标系下的索引
  50. int pro_t_id = timeToIndex(pro_t);//拓展位置时间id
  51. PathNodePtr pro_node = dynamic ? expanded_nodes_.find(pro_id, pro_t_id) : expanded_nodes_.find(pro_id);//根据是否带时间戳记录拓展节点
  52. if (pro_node != NULL && pro_node->node_state == IN_CLOSE_SET)//当存在于closet中,跳过此次拓展
  53. {
  54. if (init_search)
  55. std::cout << "close" << std::endl;
  56. continue;
  57. }
  58. //剩下NULL或不存在与closelist中的点
  59. // Check maximal velocity
  60. Eigen::Vector3d pro_v = pro_state.tail(3);//检查速度限制超过则跳过该次检查
  61. if (fabs(pro_v(0)) > max_vel_ || fabs(pro_v(1)) > max_vel_ || fabs(pro_v(2)) > max_vel_)
  62. {
  63. if (init_search)
  64. std::cout << "vel" << std::endl;
  65. continue;
  66. }
  67. //剩下不超过速度限制的点,且不存在于close中,但可能为空
  68. // Check not in the same voxel
  69. Eigen::Vector3i diff = pro_id - cur_node->index;//检查拓展的栅格是否为当前栅格相同
  70. int diff_time = pro_t_id - cur_node->time_idx;//检查栅格
  71. if (diff.norm() == 0 && ((!dynamic) || diff_time == 0))//带时间戳时时间相同,或者时间相同跳出循环
  72. {
  73. if (init_search)
  74. std::cout << "same" << std::endl;
  75. continue;
  76. }
  77. //剩下不超过速度限制、且不是重复的点,且不存在于close中,但可能为空或者存在同一栅格中(但花费的时间不同的点)
  78. // Check safety
  79. Eigen::Vector3d pos;
  80. Eigen::Matrix<double, 6, 1> xt;
  81. bool is_occ = false;
  82. for (int k = 1; k <= check_num_; ++k)//分辨率提高检查点,比如a到b我检查5个点,判定每个点是否占用
  83. {
  84. double dt = tau * double(k) / double(check_num_);
  85. stateTransit(cur_state, xt, um, dt);//前向积分得到扩展节点的位置和速度
  86. pos = xt.head(3);
  87. if (edt_environment_->sdf_map_->getInflateOccupancy(pos) == 1 )//检查是否占用
  88. {
  89. is_occ = true;//占用标志位记为true
  90. break;
  91. }
  92. }
  93. if (is_occ)
  94. {
  95. if (init_search)
  96. std::cout << "safe" << std::endl;//当检测到被占用时,跳过
  97. continue;
  98. }
  99. //更新拓展状态的代价
  100. double time_to_goal, tmp_g_score, tmp_f_score;//定义变量暂时存储代价函数值
  101. tmp_g_score = (um.squaredNorm() + w_time_) * tau + cur_node->g_score;
  102. tmp_f_score = tmp_g_score + lambda_heu_ * estimateHeuristic(pro_state, end_state, time_to_goal);

(3)剪枝:

剪枝对同一栅格内所得到的采样点进行更新,仅保留最低代价的状态

  1. //节点剪枝
  2. // Compare nodes expanded from the same parent
  3. /*
  4. prune为剪枝标志位
  5. 如果 prune 为 true,表示需要对当前生成的节点进行剪枝。这说明当前节点与之前扩展的节点在相同的体素内(位置索引相同且时间索引相同,如果是动态规划)。
  6. 如果当前节点的代价(tmp_f_score)比之前扩展的相同节点的代价更小,则更新之前扩展的节点的信息,以保留更优的路径信息。
  7. 如果 prune 为 false,表示不需要对当前生成的节点进行剪枝。这说明当前节点与之前扩展的节点在不同的体素内。
  8. 如果当前节点是一个新的节点(pro_node == NULL),将其添加到搜索队列中,并更新相关信息。
  9. 如果当前节点已经在开放集合中,检查新路径的代价是否更小,如果是,则更新节点信息。
  10. */
  11. bool prune = false;
  12. for (int j = 0; j < tmp_expand_nodes.size(); ++j)//遍历所有临时拓展节点列表,用来检查拓展的点中有没有同个节点内的点,有的话标记剪枝操作
  13. {
  14. PathNodePtr expand_node = tmp_expand_nodes[j];
  15. if ((pro_id - expand_node->index).norm() == 0 && ((!dynamic) || pro_t_id == expand_node->time_idx))//当前遍历节点与本次循环拓展节点在相同的体素内
  16. {
  17. prune = true;//标记需要剪枝(即比较代价函数)
  18. if (tmp_f_score < expand_node->f_score)//比较代价函数,选取代价小的节点并保存。
  19. {
  20. expand_node->f_score = tmp_f_score;//将原来计算好的代价记录下来
  21. expand_node->g_score = tmp_g_score;
  22. expand_node->state = pro_state;//记录状态值
  23. expand_node->input = um;//往前拓展状态的输入
  24. expand_node->duration = tau;//往前拓展的积分的时间
  25. if (dynamic)
  26. expand_node->time = cur_node->time + tau;//如果是带时间戳的路径规划则记录时间戳信息
  27. }
  28. break;
  29. }
  30. }
  31. // tem中存有的点是所有离散点?expand中存的是剪枝后每个节点的最优点
  32. // This node end up in a voxel different from others
  33. if (!prune)//当前节点为全新节点
  34. {
  35. if (pro_node == NULL)//如果 pro_node 为空,说明这是一个新的节点,需要将其添加到搜索队列中。更新节点的信息,包括索引、状态、代价等。
  36. {
  37. pro_node = path_node_pool_[use_node_num_];
  38. pro_node->index = pro_id;
  39. pro_node->state = pro_state;
  40. pro_node->f_score = tmp_f_score;
  41. pro_node->g_score = tmp_g_score;
  42. pro_node->input = um;
  43. pro_node->duration = tau;
  44. pro_node->parent = cur_node;
  45. pro_node->node_state = IN_OPEN_SET;//将其加入到openset中
  46. if (dynamic)//带时间戳的规划需要带时间
  47. {
  48. pro_node->time = cur_node->time + tau;
  49. pro_node->time_idx = timeToIndex(pro_node->time);
  50. }
  51. open_set_.push(pro_node);
  52. //将其加入到探索列表节点中
  53. if (dynamic)
  54. expanded_nodes_.insert(pro_id, pro_node->time, pro_node);
  55. else
  56. expanded_nodes_.insert(pro_id, pro_node);
  57. //加入临时探索列表中
  58. tmp_expand_nodes.push_back(pro_node);//更新tmp
  59. //已使用点数量
  60. use_node_num_ += 1;
  61. if (use_node_num_ == allocate_num_)//超过原定义好的ptr存储序列
  62. {
  63. cout << "run out of memory." << endl;
  64. return NO_PATH;
  65. }
  66. }
  67. else if (pro_node->node_state == IN_OPEN_SET)//如果 pro_node 已经在开放集合中,检查新路径的代价是否更小,如果是,则更新节点信息。
  68. {
  69. if (tmp_g_score < pro_node->g_score)
  70. {
  71. // pro_node->index = pro_id;
  72. pro_node->state = pro_state;
  73. pro_node->f_score = tmp_f_score;
  74. pro_node->g_score = tmp_g_score;
  75. pro_node->input = um;
  76. pro_node->duration = tau;
  77. pro_node->parent = cur_node;
  78. if (dynamic)
  79. pro_node->time = cur_node->time + tau;
  80. }
  81. }
  82. else//如果 pro_node 的状态不是在开放集合中,可能存在错误。
  83. {
  84. cout << "error type in searching: " << pro_node->node_state << endl;
  85. }
  86. }
  87. }
  88. // init_search = false;
  89. }

3.流程分析 :

流程分析如下(字丑,别骂了,爱看不看,有错误欢迎指出)

 二、non_uniform_bspline均匀B样条轨迹优化

该部分的主要文件为:/fast_planner/bspline/src/non_uniform_bspline.cpp,用于实现B样条初始化,主要包含以下几个函数:B样条曲线参数初始化函数、节点处轨迹计算函数、轨迹拟合获取控制点函数、轨迹求导函数、动态可行性检查函数、轨迹节点调整函数。接下来将按照顺序介绍以上函数,写得备注很详细!

关于B样条轨迹的讲解可以阅读笔者下方这篇文章:

路径规划算法曲线篇(二)—— B样条曲线轨迹表示详解-CSDN博客

1.B样条初始化函数

读完上面那片文章应该对B样条有了初步认识了,此函数传入控制点、阶数、节点间隔实现对B样条参数的初始化。已知控制点数、阶数即可得求节点数。Fast中采用的是3次B样条曲线拟合轨迹,本代码初始化时候,节点向量从-3*interval开始的,一共有m_个节点。

  1. /*
  2. 输入参数为,点,阶数,时间间隔
  3. 功能:初始化成员变量:控制点矩阵、B样条次数、时间间隔、多样条阶数、节点向量
  4. */
  5. void NonUniformBspline::setUniformBspline(const Eigen::MatrixXd& points, const int& order,
  6. const double& interval) {
  7. //control_points_是动态大小的矩阵,元素为双浮点数,每行代表一个控制点,维度由列号确定即N*3矩阵
  8. control_points_ = points;
  9. //p为B样条次数
  10. p_ = order;
  11. //节点之间的是检查,即delta_t
  12. interval_ = interval;
  13. //B样条控制点个数-1得到阶数
  14. n_ = points.rows() - 1;
  15. //节点数为阶数+次数
  16. m_ = n_ + p_ + 1;
  17. //初始化存储节点的向量
  18. u_ = Eigen::VectorXd::Zero(m_ + 1);
  19. //B样条节点初始化,初始化为均匀B样条,即间隔相等。
  20. /*
  21. 前p_+1个节点被称为开头节点或者起始节点,他们决定了B样条的起始点,这样定义出来的起始节点为小于0的区间
  22. 中间的p_ + 1 到 m_ - p_ 之间节点为中间节点,由累加而成。确保连续
  23. 后m_ - p_ 个节点称为尾节点(trailing nodes)或结束节点,它们决定了 B 样条曲线的结束点,初始化时,这些节点的值与前一个节点相等,确保在这一段区间内的节点是连续的。这个区间的长度是 (m_ - p_)
  24. 自己写出来很好理解
  25. */
  26. for (int i = 0; i <= m_; ++i) {
  27. if (i <= p_) {
  28. u_(i) = double(-p_ + i) * interval_;
  29. } else if (i > p_ && i <= m_ - p_) {
  30. u_(i) = u_(i - 1) + interval_;
  31. } else if (i > m_ - p_) {
  32. u_(i) = u_(i - 1) + interval_;
  33. }
  34. }

2.求B样条时间节点u_处值

由B样条的性质可知,B样条上某一点的值可以由De Boor递归公式推导得到,代码中的做法是将需要求的点值映射到距离其最近的时间节点,然后将其带入计算得到传入时间节点u处的值。

  1. /*De Boor递归算法,计算传入参数u处的B样条曲线值,返回B-样条曲线在参数 u 处的B样条曲线函数*/
  2. Eigen::VectorXd NonUniformBspline::evaluateDeBoor(const double& u) {
  3. //限制参数 u 的范围: 将参数 u 限制在 B-样条曲线有效范围内,即 [u_(p_), u_(m_ - p_)]。
  4. double ub = min(max(u_(p_), u), u_(m_ - p_));
  5. // determine which [ui,ui+1] lay in
  6. //找到输入的u是第几个节点处
  7. int k = p_;
  8. while (true) {
  9. if (u_(k + 1) >= ub) break;
  10. ++k;
  11. }
  12. //通过k找到与当前节点相关的控制点k-p_到k
  13. /* deBoor's alg */
  14. vector<Eigen::VectorXd> d;
  15. for (int i = 0; i <= p_; ++i) {
  16. d.push_back(control_points_.row(k - p_ + i));
  17. // cout << d[i].transpose() << endl;
  18. }
  19. //De Boor递归计算控制点,计算过程中使用了混合参数 alpha,该参数用于混合现有控制点以生成新的控制点。
  20. //数学推导https://en.wikipedia.org/wiki/De_Boor%27s_algorithm
  21. for (int r = 1; r <= p_; ++r) {//外循环次数
  22. for (int i = p_; i >= r; --i) {//内循环控制节点,次数高一次,控制节点少一个(自己写以下就能理解)
  23. double alpha = (ub - u_[i + k - p_]) / (u_[i + 1 + k - r] - u_[i + k - p_]);
  24. // cout << "alpha: " << alpha << endl;
  25. d[i] = (1 - alpha) * d[i - 1] + alpha * d[i];
  26. }
  27. }
  28. //返回计算得到的 B-样条曲线在参数 u 处的点
  29. return d[p_];
  30. }

3.轨迹拟合获取控制点函数

这个函数很重要,起着承上启下的作用,在第一部分动态搜索得到了离散的路经点,此函数作用为求取控制点矩阵,该控制点矩阵生成的B样条曲线拟合路径点的路径。

其中,M矩阵表示了控制点与时间节点值之间的关系,来自于文献K. Qin, “General matrix representations for b-splines,” The Visual Computer, vol. 16, no. 3, pp. 177–186, 2000.

第一行表示了位置约束关系,第二行是速度、第三行是加速度。当传入的离散轨迹点为K时,有K-1段轨迹,由于是三次B样条曲线,()所以算上头部3个节点,此外为了保证曲线的连续性后面添2个节点(因为要保证速度与加速度约束),共有K+5个时间节点,可以得到所求控制点个数应为K+5-次数3为K+2个。然后通过上式构建AX=B方程即可求解出控制点向量X。

关于s(t),需要注意的是,当选择的约束为位置约束时为常数1,速度约束为1/t,加速度为1/t^2以此类推,需要高阶导数可以自己添加。另外添加了两个速度约束与两个加速度约束,此时的A矩阵大小应该为K+4*K+2。具体的代码含义我都以及备注了:

  1. /*
  2. 函数参数:ts:轨迹执行时间、point_set:原轨迹点集合、start_end_derivative:起点与终点的高阶约束
  3. 输出:更新ctrl_pts控制点矩阵的值
  4. 函数功能:将给定点集和起始/终止导数转换为 B-样条曲线的控制点矩阵,通过对原始轨迹的拟合得到B样条轨迹的控制点
  5. */
  6. void NonUniformBspline::parameterizeToBspline(const double& ts, const vector<Eigen::Vector3d>& point_set,
  7. const vector<Eigen::Vector3d>& start_end_derivative,
  8. Eigen::MatrixXd& ctrl_pts) {
  9. //判断输入的时间是否合理
  10. if (ts <= 0) {
  11. cout << "[B-spline]:time step error." << endl;
  12. return;
  13. }
  14. //判断输入的轨迹点是否合理
  15. if (point_set.size() < 2) {
  16. cout << "[B-spline]:point set have only " << point_set.size() << " points." << endl;
  17. return;
  18. }
  19. //起点与终点的导数限制(即一阶和二阶导数,有此信息才可以确保得到一个具有足够信息的线性方程组,从而可以求解出 B-样条曲线的控制点。)
  20. if (start_end_derivative.size() != 4) {
  21. cout << "[B-spline]:derivatives error." << endl;
  22. }
  23. //记录原曲线路经点个数
  24. int K = point_set.size();
  25. // write A
  26. //该矩阵用来构建线性方程组, B-样条曲线参数化过程中求解控制点
  27. //一阶 B-样条基函数的系数向量、一阶 B-样条基函数的导数系数向量、二阶 B-样条基函数的系数向量
  28. //取该数值的原因是B样条曲线矩阵表示论文中提出的,以满足 B-样条曲线的一些良好性质。
  29. Eigen::Vector3d prow(3), vrow(3), arow(3);
  30. prow << 1, 4, 1;
  31. vrow << -1, 0, 1;
  32. arow << 1, -2, 1;
  33. //K+4是因为添加了四行导数约束信息
  34. Eigen::MatrixXd A = Eigen::MatrixXd::Zero(K + 4, K + 2);
  35. for (int i = 0; i < K; ++i) A.block(i, i, 1, 3) = (1 / 6.0) * prow.transpose();
  36. A.block(K, 0, 1, 3) = (1 / 2.0 / ts) * vrow.transpose();
  37. A.block(K + 1, K - 1, 1, 3) = (1 / 2.0 / ts) * vrow.transpose();
  38. A.block(K + 2, 0, 1, 3) = (1 / ts / ts) * arow.transpose();
  39. A.block(K + 3, K - 1, 1, 3) = (1 / ts / ts) * arow.transpose();
  40. // cout << "A:\n" << A << endl;
  41. // A.block(0, 0, K, K + 2) = (1 / 6.0) * A.block(0, 0, K, K + 2);
  42. // A.block(K, 0, 2, K + 2) = (1 / 2.0 / ts) * A.block(K, 0, 2, K + 2);
  43. // A.row(K + 4) = (1 / ts / ts) * A.row(K + 4);
  44. // A.row(K + 5) = (1 / ts / ts) * A.row(K + 5);
  45. // write b
  46. Eigen::VectorXd bx(K + 4), by(K + 4), bz(K + 4);
  47. for (int i = 0; i < K; ++i) {
  48. bx(i) = point_set[i](0);
  49. by(i) = point_set[i](1);
  50. bz(i) = point_set[i](2);
  51. }
  52. for (int i = 0; i < 4; ++i) {
  53. bx(K + i) = start_end_derivative[i](0);
  54. by(K + i) = start_end_derivative[i](1);
  55. bz(K + i) = start_end_derivative[i](2);
  56. }
  57. // solve Ax = b
  58. //求解线性方程
  59. Eigen::VectorXd px = A.colPivHouseholderQr().solve(bx);
  60. Eigen::VectorXd py = A.colPivHouseholderQr().solve(by);
  61. Eigen::VectorXd pz = A.colPivHouseholderQr().solve(bz);
  62. // convert to control pts
  63. //控制点赋值
  64. ctrl_pts.resize(K + 2, 3);
  65. ctrl_pts.col(0) = px;
  66. ctrl_pts.col(1) = py;
  67. ctrl_pts.col(2) = pz;
  68. // cout << "[B-spline]: parameterization ok." << endl;
  69. }

4.轨迹求导函数

轨迹求导函数不难理解,主要用途是用于检测轨迹上的速度与加速度限制。利用B样条曲线的递归公式,先计算控制点,在通过控制点得到求导后的矩阵

  1. /*得到求导之后的区域显得控制点矩阵*/
  2. Eigen::MatrixXd NonUniformBspline::getDerivativeControlPoints() {
  3. // The derivative of a b-spline is also a b-spline, its order become p_-1
  4. // control point Qi = p_*(Pi+1-Pi)/(ui+p_+1-ui+1)
  5. Eigen::MatrixXd ctp = Eigen::MatrixXd::Zero(control_points_.rows() - 1, control_points_.cols());
  6. for (int i = 0; i < ctp.rows(); ++i) {
  7. ctp.row(i) =
  8. p_ * (control_points_.row(i + 1) - control_points_.row(i)) / (u_(i + p_ + 1) - u_(i + 1));
  9. }
  10. return ctp;
  11. }
  12. /*计算求导后的曲线,并且将新的控制点、次数、节点向量记录下来*/
  13. NonUniformBspline NonUniformBspline::getDerivative() {
  14. Eigen::MatrixXd ctp = getDerivativeControlPoints();
  15. NonUniformBspline derivative(ctp, p_ - 1, interval_);
  16. /* cut the first and last knot */
  17. Eigen::VectorXd knot(u_.rows() - 2);
  18. knot = u_.segment(1, u_.rows() - 2);
  19. derivative.setKnot(knot);
  20. return derivative;
  21. }

5. 速度、加速度检查函数: 

速度加速度这里计算的是控制点之间的值,因为B样条的特殊性质,限制控制点的速度与加速度可以间接控制B样条的速度与加速度,计算公式如论文中所示:

  1. /*轨迹安全性检查,并更新超阈值比例*/
  2. bool NonUniformBspline::checkFeasibility(bool show) {
  3. bool fea = true;//可行性标记位
  4. // SETY << "[Bspline]: total points size: " << control_points_.rows() << endl;
  5. Eigen::MatrixXd P = control_points_;//取出控制点
  6. int dimension = control_points_.cols();//求控制点列数即维度,x,y,z
  7. /* check vel feasibility and insert points */
  8. double max_vel = -1.0;//初始化最大速度为负
  9. for (int i = 0; i < P.rows() - 1; ++i) {//遍历每一个控制点
  10. Eigen::VectorXd vel = p_ * (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1));//根据控制点计算速度
  11. if (fabs(vel(0)) > limit_vel_ + 1e-4 || fabs(vel(1)) > limit_vel_ + 1e-4 ||//判断是否有任一维度上的速度超过阈值
  12. fabs(vel(2)) > limit_vel_ + 1e-4) {
  13. if (show) cout << "[Check]: Infeasible vel " << i << " :" << vel.transpose() << endl;
  14. fea = false;//标记为不可行
  15. for (int j = 0; j < dimension; ++j) {
  16. max_vel = max(max_vel, fabs(vel(j)));//采用计算出来的速度更新最大速度
  17. }
  18. }
  19. }
  20. /* acc feasibility */
  21. double max_acc = -1.0;
  22. for (int i = 0; i < P.rows() - 2; ++i) {//遍历n-2个点
  23. Eigen::VectorXd acc = p_ * (p_ - 1) *
  24. ((P.row(i + 2) - P.row(i + 1)) / (u_(i + p_ + 2) - u_(i + 2)) -
  25. (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1))) /
  26. (u_(i + p_ + 1) - u_(i + 2));//计算加速度
  27. if (fabs(acc(0)) > limit_acc_ + 1e-4 || fabs(acc(1)) > limit_acc_ + 1e-4 ||
  28. fabs(acc(2)) > limit_acc_ + 1e-4) {//判断是否有任一维度上的加速度超过阈值
  29. if (show) cout << "[Check]: Infeasible acc " << i << " :" << acc.transpose() << endl;
  30. fea = false;
  31. for (int j = 0; j < dimension; ++j) {//记录更新最大加速度
  32. max_acc = max(max_acc, fabs(acc(j)));
  33. }
  34. }
  35. }
  36. double ratio = max(max_vel / limit_vel_, sqrt(fabs(max_acc) / limit_acc_));//计算最大速度或加速度超过约束的
  37. return fea;
  38. }
  39. /*获取当前控制点与时间向量节点下计算出来的超阈值比例*/
  40. double NonUniformBspline::checkRatio() {
  41. Eigen::MatrixXd P = control_points_;
  42. int dimension = control_points_.cols();
  43. // find max vel
  44. double max_vel = -1.0;
  45. for (int i = 0; i < P.rows() - 1; ++i) {
  46. Eigen::VectorXd vel = p_ * (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1));
  47. for (int j = 0; j < dimension; ++j) {
  48. max_vel = max(max_vel, fabs(vel(j)));
  49. }
  50. }
  51. // find max acc
  52. double max_acc = -1.0;
  53. for (int i = 0; i < P.rows() - 2; ++i) {
  54. Eigen::VectorXd acc = p_ * (p_ - 1) *
  55. ((P.row(i + 2) - P.row(i + 1)) / (u_(i + p_ + 2) - u_(i + 2)) -
  56. (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1))) /
  57. (u_(i + p_ + 1) - u_(i + 2));
  58. for (int j = 0; j < dimension; ++j) {
  59. max_acc = max(max_acc, fabs(acc(j)));
  60. }
  61. }
  62. double ratio = max(max_vel / limit_vel_, sqrt(fabs(max_acc) / limit_acc_));
  63. ROS_ERROR_COND(ratio > 2.0, "max vel: %lf, max acc: %lf.", max_vel, max_acc);
  64. return ratio;
  65. }
  66. /*获取当前控制点与时间向量节点下计算出来的超阈值比例*/
  67. double NonUniformBspline::checkRatio() {
  68. Eigen::MatrixXd P = control_points_;
  69. int dimension = control_points_.cols();
  70. // find max vel
  71. double max_vel = -1.0;
  72. for (int i = 0; i < P.rows() - 1; ++i) {
  73. Eigen::VectorXd vel = p_ * (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1));
  74. for (int j = 0; j < dimension; ++j) {
  75. max_vel = max(max_vel, fabs(vel(j)));
  76. }
  77. }
  78. // find max acc
  79. double max_acc = -1.0;
  80. for (int i = 0; i < P.rows() - 2; ++i) {
  81. Eigen::VectorXd acc = p_ * (p_ - 1) *
  82. ((P.row(i + 2) - P.row(i + 1)) / (u_(i + p_ + 2) - u_(i + 2)) -
  83. (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1))) /
  84. (u_(i + p_ + 1) - u_(i + 2));
  85. for (int j = 0; j < dimension; ++j) {
  86. max_acc = max(max_acc, fabs(acc(j)));
  87. }
  88. }
  89. double ratio = max(max_vel / limit_vel_, sqrt(fabs(max_acc) / limit_acc_));
  90. ROS_ERROR_COND(ratio > 2.0, "max vel: %lf, max acc: %lf.", max_vel, max_acc);
  91. return ratio;
  92. }
  93. /*检查可行性,重新分配时间节点*/
  94. bool NonUniformBspline::reallocateTime(bool show) {
  95. // SETY << "[Bspline]: total points size: " << control_points_.rows() << endl;
  96. // cout << "origin knots:\n" << u_.transpose() << endl;
  97. bool fea = true;
  98. Eigen::MatrixXd P = control_points_;
  99. int dimension = control_points_.cols();
  100. double max_vel, max_acc;
  101. /* check vel feasibility and insert points */
  102. for (int i = 0; i < P.rows() - 1; ++i) {
  103. Eigen::VectorXd vel = p_ * (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1));
  104. if (fabs(vel(0)) > limit_vel_ + 1e-4 || fabs(vel(1)) > limit_vel_ + 1e-4 ||
  105. fabs(vel(2)) > limit_vel_ + 1e-4) {
  106. fea = false;
  107. if (show) cout << "[Realloc]: Infeasible vel " << i << " :" << vel.transpose() << endl;
  108. max_vel = -1.0;
  109. for (int j = 0; j < dimension; ++j) {
  110. max_vel = max(max_vel, fabs(vel(j)));
  111. }
  112. double ratio = max_vel / limit_vel_ + 1e-4;
  113. if (ratio > limit_ratio_) ratio = limit_ratio_;
  114. /*
  115. 在这段代码中,j = i + 2 处开始调整,是因为速度的计算涉及到两个相邻的控制点,即 P.row(i + 1) 和 P.row(i + 2)。
  116. 为了调整速度,需要改变这两个控制点之间的时间间隔,即 u_(i + p_ + 1) - u_(i + 1)。因此,j 从 i + 2 开始,
  117. 以便调整第 i + 1 和 i + 2 之间的时间节点。
  118. */
  119. double time_ori = u_(i + p_ + 1) - u_(i + 1);
  120. double time_new = ratio * time_ori;
  121. double delta_t = time_new - time_ori;
  122. double t_inc = delta_t / double(p_);
  123. for (int j = i + 2; j <= i + p_ + 1; ++j) {
  124. u_(j) += double(j - i - 1) * t_inc;
  125. if (j <= 5 && j >= 1) {
  126. // cout << "vel j: " << j << endl;
  127. }
  128. }
  129. for (int j = i + p_ + 2; j < u_.rows(); ++j) {
  130. u_(j) += delta_t;
  131. }
  132. }
  133. }
  134. /* acc feasibility */
  135. for (int i = 0; i < P.rows() - 2; ++i) {
  136. Eigen::VectorXd acc = p_ * (p_ - 1) *
  137. ((P.row(i + 2) - P.row(i + 1)) / (u_(i + p_ + 2) - u_(i + 2)) -
  138. (P.row(i + 1) - P.row(i)) / (u_(i + p_ + 1) - u_(i + 1))) /
  139. (u_(i + p_ + 1) - u_(i + 2));
  140. if (fabs(acc(0)) > limit_acc_ + 1e-4 || fabs(acc(1)) > limit_acc_ + 1e-4 ||
  141. fabs(acc(2)) > limit_acc_ + 1e-4) {
  142. fea = false;
  143. if (show) cout << "[Realloc]: Infeasible acc " << i << " :" << acc.transpose() << endl;
  144. max_acc = -1.0;
  145. for (int j = 0; j < dimension; ++j) {
  146. max_acc = max(max_acc, fabs(acc(j)));
  147. }
  148. double ratio = sqrt(max_acc / limit_acc_) + 1e-4;
  149. if (ratio > limit_ratio_) ratio = limit_ratio_;
  150. // cout << "ratio: " << ratio << endl;
  151. double time_ori = u_(i + p_ + 1) - u_(i + 2);
  152. double time_new = ratio * time_ori;
  153. double delta_t = time_new - time_ori;
  154. double t_inc = delta_t / double(p_ - 1);
  155. if (i == 1 || i == 2) {
  156. // cout << "acc i: " << i << endl;
  157. for (int j = 2; j <= 5; ++j) {
  158. u_(j) += double(j - 1) * t_inc;
  159. }
  160. for (int j = 6; j < u_.rows(); ++j) {
  161. u_(j) += 4.0 * t_inc;
  162. }
  163. } else {
  164. for (int j = i + 3; j <= i + p_ + 1; ++j) {
  165. u_(j) += double(j - i - 2) * t_inc;
  166. if (j <= 5 && j >= 1) {
  167. // cout << "acc j: " << j << endl;
  168. }
  169. }
  170. for (int j = i + p_ + 2; j < u_.rows(); ++j) {
  171. u_(j) += delta_t;
  172. }
  173. }
  174. }
  175. }
  176. return fea;
  177. }

6.时间重分配

对于速度、加速度分配不合理的曲线的时间节点进行重分配,注意速度与加速度不合理时不仅仅要修改当前时间节点,因为与多节点有关都要修改。调整时间比例因子通过计算超过的比例系数获得。

  1. /*
  2. 函数参数:ts:轨迹执行时间、point_set:原轨迹点集合、start_end_derivative:起点与终点的高阶约束
  3. 输出:更新ctrl_pts控制点矩阵的值
  4. 函数功能:将给定点集和起始/终止导数转换为 B-样条曲线的控制点矩阵,通过对原始轨迹的拟合得到B样条轨迹的控制点
  5. */
  6. void NonUniformBspline::parameterizeToBspline(const double& ts, const vector<Eigen::Vector3d>& point_set,
  7. const vector<Eigen::Vector3d>& start_end_derivative,
  8. Eigen::MatrixXd& ctrl_pts) {
  9. //判断输入的时间是否合理
  10. if (ts <= 0) {
  11. cout << "[B-spline]:time step error." << endl;
  12. return;
  13. }
  14. //判断输入的轨迹点是否合理
  15. if (point_set.size() < 2) {
  16. cout << "[B-spline]:point set have only " << point_set.size() << " points." << endl;
  17. return;
  18. }
  19. //起点与终点的导数限制(即一阶和二阶导数,有此信息才可以确保得到一个具有足够信息的线性方程组,从而可以求解出 B-样条曲线的控制点。)
  20. if (start_end_derivative.size() != 4) {
  21. cout << "[B-spline]:derivatives error." << endl;
  22. }
  23. //记录原曲线路经点个数
  24. int K = point_set.size();
  25. // write A
  26. //该矩阵用来构建线性方程组, B-样条曲线参数化过程中求解控制点
  27. //一阶 B-样条基函数的系数向量、一阶 B-样条基函数的导数系数向量、二阶 B-样条基函数的系数向量
  28. //取该数值的原因是B样条曲线矩阵表示论文中提出的,以满足 B-样条曲线的一些良好性质。
  29. Eigen::Vector3d prow(3), vrow(3), arow(3);
  30. prow << 1, 4, 1;
  31. vrow << -1, 0, 1;
  32. arow << 1, -2, 1;
  33. //K+4是因为添加了四行导数约束信息
  34. Eigen::MatrixXd A = Eigen::MatrixXd::Zero(K + 4, K + 2);
  35. for (int i = 0; i < K; ++i) A.block(i, i, 1, 3) = (1 / 6.0) * prow.transpose();
  36. A.block(K, 0, 1, 3) = (1 / 2.0 / ts) * vrow.transpose();
  37. A.block(K + 1, K - 1, 1, 3) = (1 / 2.0 / ts) * vrow.transpose();
  38. A.block(K + 2, 0, 1, 3) = (1 / ts / ts) * arow.transpose();
  39. A.block(K + 3, K - 1, 1, 3) = (1 / ts / ts) * arow.transpose();
  40. // cout << "A:\n" << A << endl;
  41. // A.block(0, 0, K, K + 2) = (1 / 6.0) * A.block(0, 0, K, K + 2);
  42. // A.block(K, 0, 2, K + 2) = (1 / 2.0 / ts) * A.block(K, 0, 2, K + 2);
  43. // A.row(K + 4) = (1 / ts / ts) * A.row(K + 4);
  44. // A.row(K + 5) = (1 / ts / ts) * A.row(K + 5);
  45. // write b
  46. Eigen::VectorXd bx(K + 4), by(K + 4), bz(K + 4);
  47. for (int i = 0; i < K; ++i) {
  48. bx(i) = point_set[i](0);
  49. by(i) = point_set[i](1);
  50. bz(i) = point_set[i](2);
  51. }
  52. for (int i = 0; i < 4; ++i) {
  53. bx(K + i) = start_end_derivative[i](0);
  54. by(K + i) = start_end_derivative[i](1);
  55. bz(K + i) = start_end_derivative[i](2);
  56. }
  57. // solve Ax = b
  58. //求解线性方程
  59. Eigen::VectorXd px = A.colPivHouseholderQr().solve(bx);
  60. Eigen::VectorXd py = A.colPivHouseholderQr().solve(by);
  61. Eigen::VectorXd pz = A.colPivHouseholderQr().solve(bz);
  62. // convert to control pts
  63. //控制点赋值
  64. ctrl_pts.resize(K + 2, 3);
  65. ctrl_pts.col(0) = px;
  66. ctrl_pts.col(1) = py;
  67. ctrl_pts.col(2) = pz;
  68. // cout << "[B-spline]: parameterization ok." << endl;
  69. }

 通过以上步骤,我们通过离散的路径点求得B样条控制点,此时为均匀B样条并通过速度、加速度约束条件调整不合理的时间节点,此时曲线也变成了非均匀B样条。但是,为了获得更加光滑且安全的路径,还需要对路径进行优化,接下来将基于控制点构建路径优化模型,并对约束条件建模,将此问题建模为一个带约束的优化问题,通过优化器得到最优轨迹。

三、B样条优化

由上一步得到了初始化的B样条轨迹,次部分为了得到更加光滑安全的轨迹,此处通过优化控制点优化其生成的B样条轨迹.

1.代价函数

a.平滑代价函数

采用jerk最小化,代码中采用的是差分的形式,对应于论文中的下式子:

  1. /*计算光滑代价惩罚函数*/
  2. /*输入:三维点集合(控制点)、更新cost、梯度信息gradient*/
  3. void BsplineOptimizer::calcSmoothnessCost(const vector<Eigen::Vector3d>& q, double& cost,
  4. vector<Eigen::Vector3d>& gradient) {
  5. //初始化代价为0
  6. cost = 0.0;
  7. //初始化一个0向量并且用0向量初始化梯度矩阵
  8. Eigen::Vector3d zero(0, 0, 0);
  9. std::fill(gradient.begin(), gradient.end(), zero);
  10. //初始化临时变量
  11. Eigen::Vector3d jerk, temp_j;
  12. //遍历控制点求jerk
  13. for (int i = 0; i < q.size() - order_; i++) {
  14. /* evaluate jerk */
  15. //采用三阶差分的形式求取jerk
  16. jerk = q[i + 3] - 3 * q[i + 2] + 3 * q[i + 1] - q[i];
  17. //累加所有带价值
  18. cost += jerk.squaredNorm();
  19. //*2是为了调整效果
  20. temp_j = 2.0 * jerk;
  21. /* jerk gradient */
  22. //由于上述采用的计算形式是差分,所以直接求导就是得到系数,这里设计得很巧妙因为是累加
  23. gradient[i + 0] += -temp_j;
  24. gradient[i + 1] += 3.0 * temp_j;
  25. gradient[i + 2] += -3.0 * temp_j;
  26. gradient[i + 3] += temp_j;
  27. }
  28. }

b.障碍物碰撞惩罚函数:

障碍物碰撞距离获取直接通过地图函数调用实现,对应于文中以下公式(跟代码不是统一方法):

  1. /*计算障碍物距离代价惩罚函数*/
  2. void BsplineOptimizer::calcDistanceCost(const vector<Eigen::Vector3d>& q, double& cost,
  3. vector<Eigen::Vector3d>& gradient) {
  4. //初始化代价为0
  5. cost = 0.0;
  6. //初始化一个0向量并且用0向量初始化梯度矩阵
  7. Eigen::Vector3d zero(0, 0, 0);
  8. std::fill(gradient.begin(), gradient.end(), zero);
  9. //初始化距离值
  10. double dist;
  11. //定义变量存储梯度向量
  12. Eigen::Vector3d dist_grad, g_zero(0, 0, 0);
  13. //位运算检查是否包含ENDPOINT标志,函数根据是否需要考虑路径的终点来调整计算
  14. //如果包含 ENDPOINT,则处理整个路径点向量;如果不包含,就排除掉最后 order_ 个点,可能是为了避免在计算中出现索引越界的问题。
  15. int end_idx = (cost_function_ & ENDPOINT) ? q.size() : q.size() - order_;
  16. for (int i = order_; i < end_idx; i++) {
  17. //通过调用evaluateEDTWithGrad计算距离dist与距离梯度dist_grad
  18. edt_environment_->evaluateEDTWithGrad(q[i], -1.0, dist, dist_grad);
  19. //果梯度的范数大于阈值,则对梯度进行归一化。
  20. if (dist_grad.norm() > 1e-4) dist_grad.normalize();
  21. //当路径点距离环境较近时,代价增加,通过梯度的计算,可以在后续的优化过程中调整路径点,以增加与环境的距离,从而减少代价。
  22. if (dist < dist0_) {
  23. cost += pow(dist - dist0_, 2);
  24. gradient[i] += 2.0 * (dist - dist0_) * dist_grad;
  25. }
  26. }
  27. }

 c.动力学可行性惩罚

对每一个超过速度、加速度限制的控制范围进行惩罚,对应于文中以下公式:

  1. /*计算控制点速度、加速度可行性函数*/
  2. void BsplineOptimizer::calcFeasibilityCost(const vector<Eigen::Vector3d>& q, double& cost,
  3. vector<Eigen::Vector3d>& gradient) {
  4. //初始化代价为0
  5. cost = 0.0;
  6. //初始化一个0向量并且用0向量初始化梯度矩阵
  7. Eigen::Vector3d zero(0, 0, 0);
  8. std::fill(gradient.begin(), gradient.end(), zero);
  9. //速度、加速度平方,ts时间倒数平方与四次方
  10. /* abbreviation */
  11. double ts, vm2, am2, ts_inv2, ts_inv4;
  12. vm2 = max_vel_ * max_vel_;
  13. am2 = max_acc_ * max_acc_;
  14. ts = bspline_interval_;
  15. ts_inv2 = 1 / ts / ts;
  16. ts_inv4 = ts_inv2 * ts_inv2;
  17. /* velocity feasibility */
  18. for (int i = 0; i < q.size() - 1; i++) {
  19. //位置差分,位置差/步长等于速度
  20. Eigen::Vector3d vi = q[i + 1] - q[i];
  21. for (int j = 0; j < 3; j++) {
  22. //vm2是最大的速度,由于速度是的一阶导数,因此在从位置差分转换为加速度时要/t^2
  23. double vd = vi(j) * vi(j) * ts_inv2 - vm2;
  24. if (vd > 0.0) {
  25. //方超过最大速度时累加惩罚项
  26. cost += pow(vd, 2);
  27. //4为比例系数
  28. double temp_v = 4.0 * vd * ts_inv2;
  29. gradient[i + 0](j) += -temp_v * vi(j);
  30. gradient[i + 1](j) += temp_v * vi(j);
  31. }
  32. }
  33. }
  34. /* acceleration feasibility */
  35. for (int i = 0; i < q.size() - 2; i++) {
  36. //采用差分形式计算加速度差
  37. Eigen::Vector3d ai = q[i + 2] - 2 * q[i + 1] + q[i];
  38. for (int j = 0; j < 3; j++) {
  39. //每个方向上加速度大小的平方/t^4,由于加速度是位置的二阶导数,因此在从位置差分转换为加速度时要/t^4
  40. double ad = ai(j) * ai(j) * ts_inv4 - am2;
  41. if (ad > 0.0) {
  42. cost += pow(ad, 2);
  43. //计算梯度的向量采用累加
  44. double temp_a = 4.0 * ad * ts_inv4;
  45. gradient[i + 0](j) += temp_a * ai(j);
  46. gradient[i + 1](j) += -2 * temp_a * ai(j);
  47. gradient[i + 2](j) += temp_a * ai(j);
  48. }
  49. }
  50. }
  51. }

2.计算总代价函数和

对于以上三者求和得到总代价函数,其比例系数在上述函数已经单独乘了,此处只需要直接相加,其中还定义了其他代价函数。对应于论文中的下式:
 

  1. /*优化项结合函数,输入为控制点、引用更新梯度向量、联合函数*/
  2. void BsplineOptimizer::combineCost(const std::vector<double>& x, std::vector<double>& grad,
  3. double& f_combine) {
  4. /* convert the NLopt format vector to control points. */
  5. /*NLopt 格式的向量转换到控制点表示,以支持1D至3D的B样条优化*/
  6. // This solver can support 1D-3D B-spline optimization, but we use Vector3d to store each control point
  7. // For 1D case, the second and third elements are zero, and similar for the 2D case.
  8. for (int i = 0; i < order_; i++) {
  9. for (int j = 0; j < dim_; ++j) {
  10. g_q_[i][j] = control_points_(i, j);
  11. }
  12. //如果 dim_ 小于3,则剩余的维度设置为0。这是为了处理1D和2D情况。
  13. for (int j = dim_; j < 3; ++j) {
  14. g_q_[i][j] = 0.0;
  15. }
  16. }
  17. //处理由NLopt传入的变量 x,将其转换为控制点并存储在 g_q_ 中。同样,如果 dim_ 小于3,则剩余的维度设置为0,得到矩阵g_q_
  18. for (int i = 0; i < variable_num_ / dim_; i++) {
  19. for (int j = 0; j < dim_; ++j) {
  20. g_q_[i + order_][j] = x[dim_ * i + j];
  21. }
  22. for (int j = dim_; j < 3; ++j) {
  23. g_q_[i + order_][j] = 0.0;
  24. }
  25. }
  26. //考虑终点,如果 cost_function_ 不包含 ENDPOINT 标志,则将 control_points_ 中的最后 order_ 个控制点复制到 g_q_ 的相应位置
  27. if (!(cost_function_ & ENDPOINT)) {
  28. for (int i = 0; i < order_; i++) {
  29. for (int j = 0; j < dim_; ++j) {
  30. g_q_[order_ + variable_num_ / dim_ + i][j] =
  31. control_points_(control_points_.rows() - order_ + i, j);
  32. }
  33. for (int j = dim_; j < 3; ++j) {
  34. g_q_[order_ + variable_num_ / dim_ + i][j] = 0.0;
  35. }
  36. }
  37. }
  38. //定义总代价函数
  39. f_combine = 0.0;
  40. grad.resize(variable_num_);
  41. fill(grad.begin(), grad.end(), 0.0);
  42. if (cost_function_ & SMOOTHNESS) {
  43. //此处思路真的很清晰,标志位cost_function_设计的很巧妙
  44. /* evaluate costs and their gradient */
  45. //每个代价变量
  46. double f_smoothness, f_distance, f_feasibility, f_endpoint, f_guide, f_waypoints;
  47. f_smoothness = f_distance = f_feasibility = f_endpoint = f_guide = f_waypoints = 0.0;
  48. if (cost_function_ & SMOOTHNESS) {
  49. calcSmoothnessCost(g_q_, f_smoothness, g_smoothness_);
  50. f_combine += lambda1_ * f_smoothness;
  51. for (int i = 0; i < variable_num_ / dim_; i++)
  52. for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda1_ * g_smoothness_[i + order_](j);
  53. }
  54. if (cost_function_ & DISTANCE) {
  55. calcDistanceCost(g_q_, f_distance, g_distance_);
  56. f_combine += lambda2_ * f_distance;
  57. for (int i = 0; i < variable_num_ / dim_; i++)
  58. for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda2_ * g_distance_[i + order_](j);
  59. }
  60. if (cost_function_ & FEASIBILITY) {
  61. calcFeasibilityCost(g_q_, f_feasibility, g_feasibility_);
  62. f_combine += lambda3_ * f_feasibility;
  63. for (int i = 0; i < variable_num_ / dim_; i++)
  64. for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda3_ * g_feasibility_[i + order_](j);
  65. }
  66. if (cost_function_ & ENDPOINT) {
  67. calcEndpointCost(g_q_, f_endpoint, g_endpoint_);
  68. f_combine += lambda4_ * f_endpoint;
  69. for (int i = 0; i < variable_num_ / dim_; i++)
  70. for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda4_ * g_endpoint_[i + order_](j);
  71. }
  72. if (cost_function_ & GUIDE) {
  73. calcGuideCost(g_q_, f_guide, g_guide_);
  74. f_combine += lambda5_ * f_guide;
  75. for (int i = 0; i < variable_num_ / dim_; i++)
  76. for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda5_ * g_guide_[i + order_](j);
  77. }
  78. if (cost_function_ & WAYPOINTS) {
  79. calcWaypointsCost(g_q_, f_waypoints, g_waypoints_);
  80. f_combine += lambda7_ * f_waypoints;
  81. for (int i = 0; i < variable_num_ / dim_; i++)
  82. for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda7_ * g_waypoints_[i + order_](j);
  83. }
  84. /* print cost */
  85. // if ((cost_function_ & WAYPOINTS) && iter_num_ % 10 == 0) {
  86. // cout << iter_num_ << ", total: " << f_combine << ", acc: " << lambda8_ * f_view
  87. // << ", waypt: " << lambda7_ * f_waypoints << endl;
  88. // }
  89. // if (optimization_phase_ == SECOND_PHASE) {
  90. // << ", smooth: " << lambda1_ * f_smoothness
  91. // << " , dist:" << lambda2_ * f_distance
  92. // << ", fea: " << lambda3_ * f_feasibility << endl;
  93. // << ", end: " << lambda4_ * f_endpoint
  94. // << ", guide: " << lambda5_ * f_guide
  95. // }
  96. }
  97. }

3.通过Nlopt进行优化:

  1. Eigen::MatrixXd BsplineOptimizer::BsplineOptimizeTraj(const Eigen::MatrixXd& points, const double& ts,
  2. const int& cost_function, int max_num_id,
  3. int max_time_id) {
  4. //初始化控制点与维度
  5. setControlPoints(points);
  6. //初始化节点向量
  7. setBsplineInterval(ts);
  8. //初始化目代价函数
  9. setCostFunction(cost_function);
  10. //初始化最大控制点与时间id
  11. setTerminateCond(max_num_id, max_time_id);
  12. //执行优化
  13. optimize();
  14. //返回优化后的控制点
  15. return this->control_points_;
  16. }
  17. /*优化函数,目标最小化代价输出的控制点*/
  18. void BsplineOptimizer::optimize() {
  19. /* initialize solver */
  20. //初始化迭代次数和最小代价、设置控制点的数量
  21. iter_num_ = 0;
  22. min_cost_ = std::numeric_limits<double>::max();
  23. const int pt_num = control_points_.rows();
  24. //初始化存储代价值向量
  25. g_q_.resize(pt_num);
  26. g_smoothness_.resize(pt_num);
  27. g_distance_.resize(pt_num);
  28. g_feasibility_.resize(pt_num);
  29. g_endpoint_.resize(pt_num);
  30. g_waypoints_.resize(pt_num);
  31. g_guide_.resize(pt_num);
  32. //判断是否有终点硬约束
  33. if (cost_function_ & ENDPOINT) {
  34. variable_num_ = dim_ * (pt_num - order_);
  35. // end position used for hard constraint
  36. end_pt_ = (1 / 6.0) *
  37. (control_points_.row(pt_num - 3) + 4 * control_points_.row(pt_num - 2) +
  38. control_points_.row(pt_num - 1));
  39. } else {
  40. variable_num_ = max(0, dim_ * (pt_num - 2 * order_)) ;
  41. }
  42. /* do optimization using NLopt slover */
  43. //创建 NLopt 优化器,选择合适的算法。
  44. nlopt::opt opt(nlopt::algorithm(isQuadratic() ? algorithm1_ : algorithm2_), variable_num_);
  45. opt.set_min_objective(BsplineOptimizer::costFunction, this);
  46. //设置最大迭代次数、最长运行时间和相对容差。
  47. opt.set_maxeval(max_iteration_num_[max_num_id_]);
  48. opt.set_maxtime(max_iteration_time_[max_time_id_]);
  49. opt.set_xtol_rel(1e-5);
  50. vector<double> q(variable_num_);
  51. for (int i = order_; i < pt_num; ++i) {
  52. if (!(cost_function_ & ENDPOINT) && i >= pt_num - order_) continue;
  53. for (int j = 0; j < dim_; j++) {
  54. q[dim_ * (i - order_) + j] = control_points_(i, j);
  55. }
  56. }
  57. if (dim_ != 1) {
  58. vector<double> lb(variable_num_), ub(variable_num_);
  59. const double bound = 10.0;
  60. for (int i = 0; i < variable_num_; ++i) {
  61. lb[i] = q[i] - bound;
  62. ub[i] = q[i] + bound;
  63. }
  64. opt.set_lower_bounds(lb);
  65. opt.set_upper_bounds(ub);
  66. }
  67. try {
  68. // cout << fixed << setprecision(7);
  69. // vec_time_.clear();
  70. // vec_cost_.clear();
  71. // time_start_ = ros::Time::now();
  72. //执行优化
  73. double final_cost;
  74. nlopt::result result = opt.optimize(q, final_cost);
  75. /* retrieve the optimization result */
  76. // cout << "Min cost:" << min_cost_ << endl;
  77. } catch (std::exception& e) {
  78. ROS_WARN("[Optimization]: nlopt exception");
  79. cout << e.what() << endl;
  80. }
  81. //遍历向量
  82. for (int i = order_; i < control_points_.rows(); ++i) {
  83. if (!(cost_function_ & ENDPOINT) && i >= pt_num - order_) continue;
  84. for (int j = 0; j < dim_; j++) {
  85. control_points_(i, j) = best_variable_[dim_ * (i - order_) + j];
  86. }
  87. }
  88. if (!(cost_function_ & GUIDE)) ROS_INFO_STREAM("iter num: " << iter_num_);
  89. }

综上,通过最小化代价,得到一组最优的控制点。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/542816
推荐阅读
相关标签
  

闽ICP备14008679号