参考
相关文章
曲率成本函数
曲率是描述曲线局部性质的量。
一般曲率由三点求外界圆的 R 来计算,但是这里是通过曲率的定义来计算。
离散点顺序: pt_m -> pt -> pt_p
曲率定义为极小弧长内转过的角度:
向量 u 是 pt_m -> pt,向量 v 是 pt -> pt_p,向量 u 和 v 夹角 α 。
![](https://img-blog.csdnimg.cn/direct/d626bdb25d724e68b181f8821aadb1bd.png)
在 smac 中将向量 u 的模近似看做弧长,夹角 α 看做转角:
这种近似计算是有条件的,先看看转角的情况:
1. 转角
以 3 个点 ptm,pt,ptp 作外接圆 c,圆心 C。β 角是弧 ptm-pt 对应的圆心角。
当向量 u 的模小于 v 的模时候,此时 β 明显小于 α 。
![](https://img-blog.csdnimg.cn/direct/2b90b5739fff48308d1ab5c3ac031a05.png)
当向量 u 的模大于 v 的模时候,此时 β 明显大于 α 。
![](https://img-blog.csdnimg.cn/direct/1719b109537a4cb0be755fdb13a65182.png)
只有当 pt 落在弧 ptm 和 ptp 中间,也就是向量 u 的模等于向量 v 的模时候,才有 α = β。
![](https://img-blog.csdnimg.cn/direct/d87b5ddc531c4560ab9cb4f8767e1b65.png)
2. 弧长
假设 3 点同圆情况下,向量 u 的模相当于弧 ptm - pt 对应的弦。
弧长公式:
弦长公式:
将两式相除,弧长 / 弦长:
其图像如下:
![](https://img-blog.csdnimg.cn/direct/6c55a3809b55418f8cac0f69275017f1.png)
很明显,只有当 θ 趋近 0 时候,弧长才会近似等于弦长。
用求极限的方法也可以得到这个结论:
通过上面的分析可以知道 smac 曲率成本计算的前提条件有两个:
-
离散曲线点分布均匀,间距相等。
-
离散曲线点密集。
曲率成本函数为:
是 pt_m -> pt 向量,
是 pt -> pt_p 向量,
是限制的最大曲率。当超出曲率限制部分小于 0 时候成本为 0。
smac 用超出曲率限制部分的大小的平方作为惩罚,源码如下:
inline void addCurvatureResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
CurvatureComputations & curvature_params,
double & r) const
{
// pt_m -> pt -> pt_p
curvature_params.valid = true;
curvature_params.delta_xi = Eigen::Vector2d(pt[0] - pt_m[0], pt[1] - pt_m[1]);
curvature_params.delta_xi_p = Eigen::Vector2d(pt_p[0] - pt[0], pt_p[1] - pt[1]);
curvature_params.delta_xi_norm = curvature_params.delta_xi.norm();
curvature_params.delta_xi_p_norm = curvature_params.delta_xi_p.norm();
if (curvature_params.delta_xi_norm < EPSILON || curvature_params.delta_xi_p_norm < EPSILON ||
std::isnan(curvature_params.delta_xi_p_norm) || std::isnan(curvature_params.delta_xi_norm) ||
std::isinf(curvature_params.delta_xi_p_norm) || std::isinf(curvature_params.delta_xi_norm))
{
// ensure we have non-nan values returned
curvature_params.valid = false;
return;
}
// 矢量(pt - pt_m)和矢量(pt_p - pt)的夹角 cos 值
const double & delta_xi_by_xi_p =
curvature_params.delta_xi_norm * curvature_params.delta_xi_p_norm;
double projection =
curvature_params.delta_xi.dot(curvature_params.delta_xi_p) / delta_xi_by_xi_p;
if (fabs(1 - projection) < EPSILON || fabs(projection + 1) < EPSILON) {
projection = 1.0;
}
curvature_params.delta_phi_i = std::acos(projection); // 矢量夹角
// 曲率 k = 转角 theta / 弧长 s,这里将向量模近似看做弧长
curvature_params.turning_rad = curvature_params.delta_phi_i / curvature_params.delta_xi_norm;
curvature_params.ki_minus_kmax = curvature_params.turning_rad - _params.max_curvature;
if (curvature_params.ki_minus_kmax <= EPSILON) {
// Quadratic penalty need not apply
curvature_params.valid = false;
return;
}
r += weight *
curvature_params.ki_minus_kmax * curvature_params.ki_minus_kmax; // objective function value
}
curvature_params.turning_rad 是点 pt 位置的曲率,curvature_params.ki_minus_kmax 就是超出曲率限制部分的值,可以是负值,负值表示当前曲率还在限定范围内。
曲率成本梯度函数
根据链式法则,把曲率成本函数看做多个函数的复合,令
,向量 u 和 v 的夹角 cos 值
,向量 u 和 v 的夹角 θ
,向量 u 的模
,曲率超限值函数
,曲率成本代价函数
复合函数求导
- n(x) 求导
curvatureDelta 就是求导位置的曲率超限值,对应源码变量 curvature_params.ki_minus_kmax。
- m(x) 求导
是求导位置的向量(ptm 指向 pt),对应源码变量 curvature_params.delta_xi。
就是向量的模,对应源码变量 curvature_params.delta_xi_p_norm。
θ 是求导位置的向量 u 和 v 的夹角,对应源码变量 curvature_params.delta_phi_i。
把 代入
,有
- g(x) 求导
,对 g(x) 偏求导有
把 代入
,有
这里可以看出,向量的模求导得到向量的方向,向量的方向则用单位向量表示。
- f(x) 求导
把 代入
,有
- i(x) 求导
对 i(x) 偏求导有
- j(x) 求导
根据前面对矢量的模求导的结论,有
- h(x) 求导
把 和
代入
,有
再把 代入
就能够得到完整的曲率成本梯度函数,完整公式太长了这里就不写了。
!!!注意!!!
h(x) 的求导公式是唯一与 smac 源码(foxy版本)不符合的地方。
源码的公式根据代码反过来推算大概如下:
这里丢失了向量 u 比较奇怪,因为 pt 与两个向量 u 和 v 都是有关联的。
源码:
inline Eigen::Vector2d derivation_hx( // 本文对 h(x) 函数的求导
const Eigen::Vector2d & a,
const Eigen::Vector2d & b,
const double & a_norm,
const double & b_norm) const
{
Eigen::Vector2d prefix = (b-a)/(a_norm*b_norm);
Eigen::Vector2d suffix = (a.dot(b)*(-(a*a.squaredNorm()+b*b.squaredNorm())))/(a_norm*b_norm);
return prefix + suffix;
}
inline void addCurvatureJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & /*pt_m*/,
CurvatureComputations & curvature_params,
double & j0,
double & j1) const
{
if (!curvature_params.isValid()) {
return;
}
const double & partial_delta_phi_i_wrt_cost_delta_phi_i =
-1 / std::sqrt(1 - std::pow(std::cos(curvature_params.delta_phi_i), 2));
Eigen::Vector2d p3 = derivation_hx(curvature_params.delta_xi,
curvature_params.delta_xi_p, curvature_params.delta_xi_norm, curvature_params.delta_xi_p_norm);
const double & u = 2 * curvature_params.ki_minus_kmax;
const double & common_prefix =
(1 / curvature_params.delta_xi_norm) * partial_delta_phi_i_wrt_cost_delta_phi_i;
const double & common_suffix = curvature_params.delta_phi_i /
(curvature_params.delta_xi_norm * curvature_params.delta_xi_norm);
const Eigen::Vector2d & d_delta_xi_d_xi = curvature_params.delta_xi /
curvature_params.delta_xi_norm;
const Eigen::Vector2d jacobian = u *
((common_prefix * p3) - (common_suffix * d_delta_xi_d_xi));
j0 += weight * jacobian[0]; // xi x component of partial-derivative
j1 += weight * jacobian[1]; // xi x component of partial-derivative
}
曲率梯度函数优化对比
使用 ceres 的默认优化参数,smac 的 smoother 路径优化器仅打开曲率和距离成本函数和成本梯度函数的优化对比。
绿色路径是随机生成的路径,红色路径是 smac 源码优化后的路径,黄色路径是使用本文曲率成本梯度函数优化后的路径。由于路径的起点和终点并不参与优化过程,所以起点和终点的位置始终不会变,这里的路径发布我去掉了优化后路径的终点。
新旧对比曲率梯度-红旧黄新