不断分段找中点,判断中点是否是要寻找的num
1 | // 获取 num 在 vector 中的 index |
不断分段找中点,判断中点是否是要寻找的num
1 | // 获取 num 在 vector 中的 index |
无人车的轨迹优化,都是通过建立优化问题,是非线性规划问题,通过IPopt来求解,
这个过程有个重要的前提,那就是f(x)是连续可导。如果f(x)这个函数不可导,或者不可微呢?比如说,在SLAM中需要处理姿态估计的问题。而姿态是通过旋转矩阵和平移向量表示的,这显然是不连续的。但是,由于旋转矩阵是一个李群,那么我们可以将其映射到其李代数上。而且,李代数是可以求导的。所以我们就还可以利用非线性优化的方法对姿态进行估计。
上面的解法求得的只是局部最优解,不一定是全局最优解。因为它只看到下一时刻,在邻域内的最小值,很容易陷入到局部最优,或者说求出的最优值是跟初始位置相关的。即使初始值在同一个位置,也很有可能落入到两个不同的局部最优值,也就是说这个解是很不稳定的。如何解决这个问题,至今仍然存在很大的挑战。
Apollo高精地图给出的道路中心线的平滑性往往都不能满足规划模块的需求,因此规划中是不能拿来直接用的,需要对其进行平滑操作。Apollo目前有三种平滑算法如下,默认为离散点平滑。
离散点平滑三种求解方式,分别是利用不同求解器。如果考虑参考线的曲率约束,其优化问题是非线性的,可以使用ipopt非线性求解器求解(内点法),也可以将曲率约束线性化后使用osqp求解器来用SQP方法求解;如果不考虑曲率约束,则直接用osqp求解二次规划问题。目前Apollo 6.0中默认的参数为不考虑曲率约束-
从原始的reference_line
来选取中间点,即根据reference_line
的anchor_s
值进行采样,采样的间隔为0.25m。采样完毕后,就能得到一系列的ref_points
,而每个anchor_point
就包含了一个ref_point
以及横纵向的裕度。
横纵向裕度的默认参数为:
longitudinal_boundary_bound : 2.0
max_lateral_boundary_bound : 0.5
min_lateral_boundary_bound : 0.1
然后再根据自车宽度,车道边界类型(是否为curb)等再对横向裕度进行收缩。AnchorPoint结构中的enforced变量表示该点是否为强约束,在参考线平滑中只有首尾两点为强约束。
计算完anchor points后,将其赋值到smoother对象中。
1 | // 省略不重要部分 |
1 | // lane_info_ptr->accumulate_s(); 的来源 |
1 | /** |
补充
1 | vector<Point2D> samplePoints(const vector<Point2D>& input_points, int interval) |
1 | /** |
1 | PathPoint GetWeightedAverageOfTwoPathPoints(const PathPoint& p1, |
1 | // Test whether two float or double numbers are equal. |
reference_line是在笛卡尔坐标系下进行优化,主要考虑的在不偏离原始道路中心线太多的情况下(即约束条件)保证相邻点之间的连续性(即代价函数)。
Apollo的二次规划问题都使用OSQP
优化
曲率约束的假设:
a. 三点共圆
b. 向量,即前后两段的长度近似相等
c. 向量 和向量 夹角很小
曲率约束用到了最小转弯半径,而差速机器人的这一项是0,所以只能用于car-like机器人。
如果只取cost2
和cost3
,二次规划问题的约束其实就是限定 的上下界,仍然可以优化出结果。但是看不出有什么意义,只是在每个路径点位置误差内,让路径更紧凑了而已。
IPOPT是一种常用的非线性优化求解器,使用内点法进行求解。对于复杂问题,需要借助自动微分工具,帮助求解梯度、雅各比矩阵、Hessian矩阵,如ADOL-C,CppAD
参考非线性优化求解器IPOPT 和 ubuntu 环境下 IPOPT 安装与使用
我安装完后,部分文件和官方说明的不同,目录coin
在我的电脑上是coin-or
,sudo vim /usr/include/cppad/ipopt/solve_callback.hpp
,修改如下
1 |
CMake配置
1 | message(status "IPOPT_CFLAGS: " ${IPOPT_CFLAGS}) |
状态变量 $ x = [x_1,x_2,x_3,x_4]^\mathrm{T}$
目标函数
约束条件:
起始点
优化结果:
1 |
|
对于QP问题,可以通过数学的带入方法将约束条件转换到代价函数中,那么就变成了无约束的优化问题,就可以进行闭式求解。闭式求解的好处是求解速度更快,数值稳定性更高。
多项式的系数有实际的物理意义,所以在优化的时候可以会出现一些特别小的值,此时可能就会计算精度的影响,导致数值不稳定。因此Bry的论文提出了将系数通过一个映射矩阵转换成轨迹端点的导数,也就是说我们不再优化轨迹的系数,而是对轨迹在端点处的位置、速度、加速度、Jerk等进行优化,由于这些量都是有实际物理含义,不会出现太离谱的数值,所以在一定程度上是比较稳定的。
以最小化Jerk为例,那么根据前面的结论,我们需要构建次数为5次的多项式,那么它将有6个未知的系数,我们需要提供。
置换矩阵就是重新排列后的单位矩阵。实际上,最简单的置换矩阵P就是单位矩阵I本身。对一个矩阵进行行交换,需要通过置换矩阵完成。
用分块矩阵表示J时,需要注意J是标量,这样才能得到中间两项相等,高斯牛顿法中的推导也用到标量的技巧。
非线性优化问题:优化函数或约束条件至少有一个是非线性函数的最优化问题。
主要有三类:
针对上述三类优化问题主要有三种不同的处理策略,对于无约束的优化问题,可直接对其求导,并使其为0,这样便能得到最终的最优解。一般有梯度法、牛顿法、拟牛顿法;对于含等式约束的优化问题,主要通过拉格朗日乘数法、消元法转换成为无约束优化问题求解;对于含有不等式约束的优化问题,主要通过KKT条件( Karush-Kuhn-Tucker Condition )将其转化成无约束优化问题求解。
最小二乘问题是无约束的优化问题。
对于多元函数,通过Hessian矩阵的正定性判断,如果Hessian矩阵是半正定矩阵,那就是凸函数。如果优化函数是凸函数,那么局部最小就是全局最小,不会陷入局部最优里。
基于Hessian矩阵,可以推断多元函数的极值情况了。结论如下:
为什么需要平滑轨迹?
A star、RRT star等算法,找到了一段轨迹路径,也就是一系列的点,但没有指明点的连接方式,可能是平滑的曲线,也可能是折线。这些算法是不考虑机器人运动学约束的,因此轨迹上会出现明显的不光滑点,可以想象,机器人不可能在某一点出现运动突变,如果是万向轮的机器人要想按照这个轨迹走,它必须每走完一段路,就要停下来,然后旋转一个角度对着路径然后再加速往前走。这样很明显浪费很多时间和效率。如果是阿克曼的机器人那么它就无法按照这个路径进行运动。
目标:
要素:
A*
, RRT*
等)可以找到注意:
论文实际是关于无人机的,这里只记录有参考价值的部分。
论文提出了一种方法,即构建一个无约束的二次规划问题,对分段的多项式路径进行联合优化,这个二次规划对高次多项式和大量分段数值稳定。另一项contribution是自动选择每段的时间。使用多项式路径,不必进行大量的采样和机器人在高维状态空间的模拟。本算法不能保证渐进收敛到全局最优。
算法将搜索和优化结合起来,比纯粹基于搜索的规划算法在计算上的表现好得多。对每段时间的分配使planner能自动适应变化范围很大的size scales,只使用一个用户设置的参数
使用RRT*
算法找到无碰撞的路径,(开始运动后可以有动态障碍)开始只考虑运动学,不考虑动力学。路径被裁剪为极少的路径点,对路径点的选取是根据line-of-sight
技巧。使用分段的多项式路径插入到各路径点,生成光滑的minimum-snap路径。二次规划将路径导数(Path Derivatives)的代价函数最小化,路径的终点可以是固定值,也可以是free.(我的理解是类似三次样条曲线的自由边界) 多项式的导数连续性保证了移动的平滑性。
论文的框架:
要求每段的时间(vector of times
)是固定的,也要预先确定。大致可以根据车的期望平均速度确定,但这样不会生成最低代价的方案。因此我们将遍历地refine时间向量。
3.5 保证轨迹是无碰撞的
如果轨迹中某一段和障碍相交了,就在这段轨迹中添加中点,将其一分为二。这个中点是无碰撞的,因为它取决于搜索算法返回的分段路径。我的理解是全局路径返回的一定是无碰撞的,在它上面取一点,肯定还是无碰撞。然后多项式路径添加中点后再次二次规划,而且这个再规划过程可能会重复,直到多项式路径无碰撞(递归取中点)。图4表示了这个过程
a图的直线部分本来是无碰撞的,经过优化反而碰撞了,此时就需要增加b中的两个中点
在稠密环境中,轨迹需要增加很多额外路径点以处理碰撞情况,因此二次规划会进行很多次,增加了计算量。但是根据我们在室内环境的经验,增加的路径点不会超过原有路径点的一般,所以计算量增加很少。
设置一个目标代价函数,目的在于找到这几个变量在取一定值时使得我们设计的代价函数取得最小值。更通俗的说:就是这几个变量取一定值的时候可以使机器人能量耗费最少,或者机器人运动的转动更少等等。这种思想不仅适用于minimum snap算法。
机器人在起点和终点的速度或者加速度,我们都是可以设置的。机器人中间航迹点的位置也是需要事先给定的,但是机器人中间点的速度和加速度 Jerk等信息,我们没法去约束它,因为很难去确定机器人到达某一个点的速度或者加速度。这正是我们研究算法的目的。如果中间点的速度加速度事先设置,机器人会忽快忽慢或者速度不变,很难实现,即使能实现,也十分不美观。
因此我们认为中间点的速度、加速度等信息是一个可以自由变化的状态,那么对于这种中间状态不确定的问题,我们可以构建一个优化问题,使得中间状态取得某一个值时,我们制定的代价函数达到最小值,因此通过优化代价函数得到一个最优的轨迹。根据上面的分析,对于中间点的速度和加速度,或者更高阶的Jerk甚至Snap等,我们不对其进行微分限制,只进行连续性约束,然后让优化器给它们分配最优的状态。
minimum snap 可以理解为最小化加加加速度。
Jerk: 所受力的变化率。 (如每秒增加一牛顿)
snap: 所受力的变化率的变化。(如前一秒增加一牛顿,接下来一秒增加两N,第二秒受力与最初相比增加了三个N)
最小snap,就让jerk变化比较小。如果snap为0,就代表每秒加速度稳定增加 (受力稳定增加)。
实际工程中,对于高于加速度之后的导数,都将其设置为0,也就是最小化Snap,只需给出位置、速度、加速度的设置值,而Jerk就默认为0。
时间对于最终生成轨迹的好坏有很大影响。如果把每段时间也加入到优化的代价函数中,这样固然是好,但是势必会增加优化的时间,并且无法再闭式的求解这个问题。
梯形时间分配:先假设机器人以最大的加速度加速到最大速度,然后运行一段时间,再以最大的减速度到终点,到达是速度为0。
规划最小snap时,占用的CPU也比较大
矩阵求导求时,矩阵可以尝试用LLT分解等方法绕开。 最后求向量P 时的也可以尝试绕开。但是中的必须计算,这会非常耗时间。因此要看近年的论文。