参考线平滑算法详解(FEM-Based Path Smoothing)
目录
1. 算法概述
1.1 问题定义
输入:离散的原始参考路径点
{
(
x
i
r
e
f
,
y
i
r
e
f
)
}
i
=
0
n
−
1
\\{(x_i^{ref}, y_i^{ref})\\}_{i=0}^{n-1}
{(xiref,yiref)}i=0n−1
输出:平滑后的路径点
{
(
x
i
,
y
i
)
}
i
=
0
n
−
1
\\{(x_i, y_i)\\}_{i=0}^{n-1}
{(xi,yi)}i=0n−1
目标:在保持接近原始路径的同时,生成一条平滑、连续、曲率变化小的路径。
1.2 应用场景
在自动驾驶路径规划中,参考线平滑主要用于:
- 全局路径平滑:优化路径规划模块输出的折线路径
- 局部路径优化:消除DP动态规划产生的锯齿状路径
- 曲率优化:确保车辆可以平稳跟踪(减少急转弯)
1.3 核心思想
使用**有限元法(FEM)将路径点之间的连续性约束转化为代价函数,通过求解二次规划(QP)**问题得到最优平滑路径。
2. 数学理论基础
2.1 有限元法基本原理
有限元法将连续的路径用分段线性/多项式近似:
p
(
s
)
=
∑
i
=
0
n
−
1
N
i
(
s
)
⋅
p
i
(1)
p(s) = \\sum_{i=0}^{n-1} N_i(s) \\cdot p_i \\tag{1}
p(s)=i=0∑n−1Ni(s)⋅pi(1)
其中:
-
p
(
s
)
p(s)
p(s):路径在弧长s
s
s 处的位置 -
N
i
(
s
)
N_i(s)
Ni(s):第i
i
i 个形状函数(shape function) -
p
i
p_i
pi:第i
i
i 个控制点(优化变量)
2.2 三点有限元估计
对于路径点
p
i
p_i
pi,使用相邻三点的有限元估计:
p
^
i
=
p
i
−
1
+
p
i
+
1
2
(2)
\\hat{p}_i = \\frac{p_{i-1} + p_{i+1}}{2} \\tag{2}
p^i=2pi−1+pi+1(2)
物理意义:如果路径是一条直线,则中间点应该是两端点的平均值。
偏差:
e
i
=
p
i
−
p
^
i
=
p
i
−
p
i
−
1
+
p
i
+
1
2
(3)
e_i = p_i – \\hat{p}_i = p_i – \\frac{p_{i-1} + p_{i+1}}{2} \\tag{3}
ei=pi−p^i=pi−2pi−1+pi+1(3)
2.3 路径长度估计
相邻两点之间的路径长度(一阶差分):
Δ
p
i
=
p
i
+
1
−
p
i
(4)
\\Delta p_i = p_{i+1} – p_i \\tag{4}
Δpi=pi+1−pi(4)
总路径长度:
L
=
∑
i
=
0
n
−
2
∥
Δ
p
i
∥
2
=
∑
i
=
0
n
−
2
(
p
i
+
1
−
p
i
)
T
(
p
i
+
1
−
p
i
)
(5)
L = \\sum_{i=0}^{n-2} \\|\\Delta p_i\\|^2 = \\sum_{i=0}^{n-2} (p_{i+1} – p_i)^T (p_{i+1} – p_i) \\tag{5}
L=i=0∑n−2∥Δpi∥2=i=0∑n−2(pi+1−pi)T(pi+1−pi)(5)
3. 优化问题建模
3.1 代价函数
参考线平滑的总代价函数由三部分组成:
J
t
o
t
a
l
=
J
f
e
m
+
J
l
e
n
g
t
h
+
J
r
e
f
(6)
J_{total} = J_{fem} + J_{length} + J_{ref} \\tag{6}
Jtotal=Jfem+Jlength+Jref(6)
(1) 有限元偏差代价
J
f
e
m
J_{fem}
Jfem
惩罚路径的非平滑性(曲率大的位置):
J
f
e
m
=
w
f
e
m
∑
i
=
1
n
−
2
∥
p
i
−
p
i
−
1
+
p
i
+
1
2
∥
2
(7)
J_{fem} = w_{fem} \\sum_{i=1}^{n-2} \\left\\| p_i – \\frac{p_{i-1} + p_{i+1}}{2} \\right\\|^2 \\tag{7}
Jfem=wfemi=1∑n−2
pi−2pi−1+pi+1
2(7)
物理意义:
- 如果
p
i
p_i
pi 偏离相邻两点的中点,说明路径在此处有"弯曲" - 该项越小,路径越平滑(接近直线)
展开形式(针对点
p
i
p_i
pi):
∥
p
i
−
p
i
−
1
+
p
i
+
1
2
∥
2
=
∥
2
p
i
−
p
i
−
1
−
p
i
+
1
2
∥
2
(8)
\\left\\| p_i – \\frac{p_{i-1} + p_{i+1}}{2} \\right\\|^2 = \\left\\| \\frac{2p_i – p_{i-1} – p_{i+1}}{2} \\right\\|^2 \\tag{8}
pi−2pi−1+pi+1
2=
22pi−pi−1−pi+1
2(8)
=
1
4
(
p
i
−
1
−
2
p
i
+
p
i
+
1
)
T
(
p
i
−
1
−
2
p
i
+
p
i
+
1
)
(9)
= \\frac{1}{4} (p_{i-1} – 2p_i + p_{i+1})^T (p_{i-1} – 2p_i + p_{i+1}) \\tag{9}
=41(pi−1−2pi+pi+1)T(pi−1−2pi+pi+1)(9)
这是路径的二阶差分,近似于曲率的平方。
(2) 路径长度代价
J
l
e
n
g
t
h
J_{length}
Jlength
惩罚路径总长度(鼓励走捷径):
J
l
e
n
g
t
h
=
w
l
e
n
g
t
h
∑
i
=
0
n
−
2
∥
p
i
+
1
−
p
i
∥
2
(10)
J_{length} = w_{length} \\sum_{i=0}^{n-2} \\|p_{i+1} – p_i\\|^2 \\tag{10}
Jlength=wlengthi=0∑n−2∥pi+1−pi∥2(10)
物理意义:
- 最小化路径长度,避免绕远路
- 与FEM项平衡:太平滑可能导致路径变长
(3) 参考点偏差代价
J
r
e
f
J_{ref}
Jref
惩罚偏离原始参考路径:
J
r
e
f
=
w
r
e
f
∑
i
=
0
n
−
1
∥
p
i
−
p
i
r
e
f
∥
2
(11)
J_{ref} = w_{ref} \\sum_{i=0}^{n-1} \\|p_i – p_i^{ref}\\|^2 \\tag{11}
Jref=wrefi=0∑n−1∥pi−piref∥2(11)
物理意义:
- 确保平滑后的路径不会偏离原始意图太远
- 例如:不能为了平滑而切到对向车道
3.2 二次型矩阵形式
将所有路径点堆叠成向量:
p
=
[
x
0
,
y
0
,
x
1
,
y
1
,
…
,
x
n
−
1
,
y
n
−
1
]
T
∈
R
2
n
(12)
\\mathbf{p} = [x_0, y_0, x_1, y_1, \\ldots, x_{n-1}, y_{n-1}]^T \\in \\mathbb{R}^{2n} \\tag{12}
p=[x0,y0,x1,y1,…,xn−1,yn−1]T∈R2n(12)
总代价函数可以写成标准二次型:
J
t
o
t
a
l
=
1
2
p
T
P
p
+
q
T
p
+
c
o
n
s
t
(13)
J_{total} = \\frac{1}{2} \\mathbf{p}^T \\mathbf{P} \\mathbf{p} + \\mathbf{q}^T \\mathbf{p} + const \\tag{13}
Jtotal=21pTPp+qTp+const(13)
其中
P
\\mathbf{P}
P 是 Hessian 矩阵(对称半正定),
q
\\mathbf{q}
q 是线性项。
4. 有限元法离散化
4.1 Hessian矩阵构造
对于
n
n
n 个路径点(
2
n
2n
2n 个变量),Hessian矩阵
P
∈
R
2
n
×
2
n
\\mathbf{P} \\in \\mathbb{R}^{2n \\times 2n}
P∈R2n×2n 的结构如下:
矩阵块结构(以6个点为例)
定义:
-
I
\\mathbf{I}
I:2
×
2
2 \\times 2
2×2 单位矩阵 -
X
=
w
f
e
m
⋅
I
\\mathbf{X} = w_{fem} \\cdot \\mathbf{I}
X=wfem⋅I -
Y
=
w
l
e
n
g
t
h
⋅
I
\\mathbf{Y} = w_{length} \\cdot \\mathbf{I}
Y=wlength⋅I -
Z
=
w
r
e
f
⋅
I
\\mathbf{Z} = w_{ref} \\cdot \\mathbf{I}
Z=wref⋅I -
0
\\mathbf{0}
0:2
×
2
2 \\times 2
2×2 零矩阵
则 Hessian 矩阵为(仅上三角,对称矩阵):
P
=
[
X
+
Y
+
Z
−
2
X
−
Y
X
0
0
0
⋅
5
X
+
2
Y
+
Z
−
4
X
−
Y
X
0
0
⋅
⋅
6
X
+
2
Y
+
Z
−
4
X
−
Y
X
0
⋅
⋅
⋅
6
X
+
2
Y
+
Z
−
4
X
−
Y
X
⋅
⋅
⋅
⋅
5
X
+
2
Y
+
Z
−
2
X
−
Y
⋅
⋅
⋅
⋅
⋅
X
+
Y
+
Z
]
(14)
\\mathbf{P} = \\begin{bmatrix} \\mathbf{X}+\\mathbf{Y}+\\mathbf{Z} & -2\\mathbf{X}-\\mathbf{Y} & \\mathbf{X} & \\mathbf{0} & \\mathbf{0} & \\mathbf{0} \\\\ \\cdot & 5\\mathbf{X}+2\\mathbf{Y}+\\mathbf{Z} & -4\\mathbf{X}-\\mathbf{Y} & \\mathbf{X} & \\mathbf{0} & \\mathbf{0} \\\\ \\cdot & \\cdot & 6\\mathbf{X}+2\\mathbf{Y}+\\mathbf{Z} & -4\\mathbf{X}-\\mathbf{Y} & \\mathbf{X} & \\mathbf{0} \\\\ \\cdot & \\cdot & \\cdot & 6\\mathbf{X}+2\\mathbf{Y}+\\mathbf{Z} & -4\\mathbf{X}-\\mathbf{Y} & \\mathbf{X} \\\\ \\cdot & \\cdot & \\cdot & \\cdot & 5\\mathbf{X}+2\\mathbf{Y}+\\mathbf{Z} & -2\\mathbf{X}-\\mathbf{Y} \\\\ \\cdot & \\cdot & \\cdot & \\cdot & \\cdot & \\mathbf{X}+\\mathbf{Y}+\\mathbf{Z} \\end{bmatrix} \\tag{14}
P=
X+Y+Z⋅⋅⋅⋅⋅−2X−Y5X+2Y+Z⋅⋅⋅⋅X−4X−Y6X+2Y+Z⋅⋅⋅0X−4X−Y6X+2Y+Z⋅⋅00X−4X−Y5X+2Y+Z⋅000X−2X−YX+Y+Z
(14)
4.2 系数推导
以中间点
p
i
p_i
pi(
2
≤
i
≤
n
−
3
2 \\leq i \\leq n-3
2≤i≤n−3)为例,展开代价函数:
FEM项贡献:
J
f
e
m
(
i
)
=
w
f
e
m
(
p
i
−
1
−
2
p
i
+
p
i
+
1
)
T
(
p
i
−
1
−
2
p
i
+
p
i
+
1
)
(15)
J_{fem}^{(i)} = w_{fem} (p_{i-1} – 2p_i + p_{i+1})^T (p_{i-1} – 2p_i + p_{i+1}) \\tag{15}
Jfem(i)=wfem(pi−1−2pi+pi+1)T(pi−1−2pi+pi+1)(15)
对
p
i
p_i
pi 的 Hessian:
∂
2
J
f
e
m
(
i
)
∂
p
i
2
=
4
w
f
e
m
I
(16)
\\frac{\\partial^2 J_{fem}^{(i)}}{\\partial p_i^2} = 4 w_{fem} \\mathbf{I} \\tag{16}
∂pi2∂2Jfem(i)=4wfemI(16)
但注意
p
i
p_i
pi 出现在三个FEM项中:
-
J
f
e
m
(
i
−
1
)
J_{fem}^{(i-1)}
Jfem(i−1):作为p
i
+
1
p_{i+1}
pi+1 -
J
f
e
m
(
i
)
J_{fem}^{(i)}
Jfem(i):作为p
i
p_i
pi(中心点) -
J
f
e
m
(
i
+
1
)
J_{fem}^{(i+1)}
Jfem(i+1):作为p
i
−
1
p_{i-1}
pi−1
总贡献:
4
w
f
e
m
+
w
f
e
m
+
w
f
e
m
=
6
w
f
e
m
4w_{fem} + w_{fem} + w_{fem} = 6w_{fem}
4wfem+wfem+wfem=6wfem
长度项贡献:
J
l
e
n
g
t
h
(
i
)
=
w
l
e
n
g
t
h
[
(
p
i
−
p
i
−
1
)
T
(
p
i
−
p
i
−
1
)
+
(
p
i
+
1
−
p
i
)
T
(
p
i
+
1
−
p
i
)
]
(17)
J_{length}^{(i)} = w_{length} [(p_i – p_{i-1})^T(p_i – p_{i-1}) + (p_{i+1} – p_i)^T(p_{i+1} – p_i)] \\tag{17}
Jlength(i)=wlength[(pi−pi−1)T(pi−pi−1)+(pi+1−pi)T(pi+1−pi)](17)
对
p
i
p_i
pi 的 Hessian:
∂
2
J
l
e
n
g
t
h
∂
p
i
2
=
2
w
l
e
n
g
t
h
I
(18)
\\frac{\\partial^2 J_{length}}{\\partial p_i^2} = 2 w_{length} \\mathbf{I} \\tag{18}
∂pi2∂2Jlength=2wlengthI(18)
参考点偏差项:
J
r
e
f
(
i
)
=
w
r
e
f
(
p
i
−
p
i
r
e
f
)
T
(
p
i
−
p
i
r
e
f
)
(19)
J_{ref}^{(i)} = w_{ref} (p_i – p_i^{ref})^T (p_i – p_i^{ref}) \\tag{19}
Jref(i)=wref(pi−piref)T(pi−piref)(19)
对
p
i
p_i
pi 的 Hessian:
∂
2
J
r
e
f
(
i
)
∂
p
i
2
=
w
r
e
f
I
(20)
\\frac{\\partial^2 J_{ref}^{(i)}}{\\partial p_i^2} = w_{ref} \\mathbf{I} \\tag{20}
∂pi2∂2Jref(i)=wrefI(20)
总Hessian(对角块):
P
i
i
=
(
6
w
f
e
m
+
2
w
l
e
n
g
t
h
+
w
r
e
f
)
I
(21)
\\mathbf{P}_{ii} = (6w_{fem} + 2w_{length} + w_{ref}) \\mathbf{I} \\tag{21}
Pii=(6wfem+2wlength+wref)I(21)
对应代码:fem_pos_deviation_osqp_interface.cpp 第172-173行:
col_index, 6.0 * weight_fem_pos_deviation_ +
2.0 * weight_path_length_ + weight_ref_deviation_
5. 二次规划求解
5.1 标准QP问题
优化问题最终形式:
min
p
1
2
p
T
P
p
+
q
T
p
s.t.
l
≤
A
p
≤
u
(22)
\\begin{aligned} \\min_{\\mathbf{p}} \\quad & \\frac{1}{2} \\mathbf{p}^T \\mathbf{P} \\mathbf{p} + \\mathbf{q}^T \\mathbf{p} \\\\ \\text{s.t.} \\quad & \\mathbf{l} \\leq \\mathbf{A} \\mathbf{p} \\leq \\mathbf{u} \\end{aligned} \\tag{22}
pmins.t.21pTPp+qTpl≤Ap≤u(22)
其中:
-
P
\\mathbf{P}
P:Hessian矩阵(2
n
×
2
n
2n \\times 2n
2n×2n,稀疏对称) -
q
\\mathbf{q}
q:线性项,来自参考点偏差 -
A
\\mathbf{A}
A:约束矩阵(盒约束,这里是单位矩阵) -
l
,
u
\\mathbf{l}, \\mathbf{u}
l,u:下界和上界(横向偏移限制)
5.2 线性项
q
\\mathbf{q}
q 的计算
参考点偏差项展开:
J
r
e
f
=
w
r
e
f
∑
i
=
0
n
−
1
(
p
i
−
p
i
r
e
f
)
T
(
p
i
−
p
i
r
e
f
)
(23)
J_{ref} = w_{ref} \\sum_{i=0}^{n-1} (p_i – p_i^{ref})^T (p_i – p_i^{ref}) \\tag{23}
Jref=wrefi=0∑n−1(pi−piref)T(pi−piref)(23)
=
w
r
e
f
∑
i
=
0
n
−
1
(
p
i
T
p
i
−
2
p
i
T
p
i
r
e
f
+
(
p
i
r
e
f
)
T
p
i
r
e
f
)
(24)
= w_{ref} \\sum_{i=0}^{n-1} (p_i^T p_i – 2 p_i^T p_i^{ref} + (p_i^{ref})^T p_i^{ref}) \\tag{24}
=wrefi=0∑n−1(piTpi−2piTpiref+(piref)Tpiref)(24)
线性项系数:
q
i
=
−
2
w
r
e
f
p
i
r
e
f
(25)
q_i = -2 w_{ref} p_i^{ref} \\tag{25}
qi=−2wrefpiref(25)
对应代码:fem_pos_deviation_osqp_interface.cpp 第218-219行:
q->push_back(–2.0 * weight_ref_deviation_ * ref_point_xy.first);
q->push_back(–2.0 * weight_ref_deviation_ * ref_point_xy.second);
5.3 约束条件
盒约束(Box Constraints):限制平滑后的点不能偏离原始点太远
p
i
r
e
f
−
b
i
≤
p
i
≤
p
i
r
e
f
+
b
i
(26)
p_i^{ref} – b_i \\leq p_i \\leq p_i^{ref} + b_i \\tag{26}
piref−bi≤pi≤piref+bi(26)
其中
b
i
b_i
bi 是第
i
i
i 个点的横向边界(lateral bound)。
特殊处理:
- 起点和终点:
b
0
=
b
n
−
1
=
0
b_0 = b_{n-1} = 0
b0=bn−1=0(强制固定) - 中间点:
b
i
b_i
bi 根据道路宽度设置
对应代码:discrete_points_reference_line_smoother.cpp 第25-26行:
anchorpoints_lateralbound.front() = 0.0; // 起点固定
anchorpoints_lateralbound.back() = 0.0; // 终点固定
6. 代码实现详解
6.1 算法流程
主流程(DiscretePointsReferenceLineSmoother::Smooth)
// discrete_points_reference_line_smoother.cpp: 8-51行
bool Smooth(const MatrixXd& raw_reference_line, MatrixXd &smoothed_reference_line) {
// 步骤1:提取原始参考点
std::vector<std::pair<double, double>> raw_point2d;
for (const auto& anchor_point : anchor_points_) {
raw_point2d.emplace_back(anchor_point.x, anchor_point.y);
anchorpoints_lateralbound.emplace_back(anchor_point.lateral_bound);
}
// 步骤2:归一化(相对于起点)
NormalizePoints(&raw_point2d);
// 步骤3:调用FEM优化求解器
std::vector<std::pair<double, double>> smoothed_point2d;
bool status = FemPosSmooth(raw_point2d, anchorpoints_lateralbound, &smoothed_point2d);
// 步骤4:反归一化(恢复全局坐标)
DeNormalizePoints(&smoothed_point2d);
// 步骤5:计算曲率、航向角等几何属性
referencePath(xy, smoothed_reference_line);
return true;
}
数学映射:
原始路径
→
归一化
局部坐标
→
QP求解
优化路径
→
反归一化
全局平滑路径
(27)
\\text{原始路径} \\xrightarrow{\\text{归一化}} \\text{局部坐标} \\xrightarrow{\\text{QP求解}} \\text{优化路径} \\xrightarrow{\\text{反归一化}} \\text{全局平滑路径} \\tag{27}
原始路径归一化
局部坐标QP求解
优化路径反归一化
全局平滑路径(27)
6.2 Hessian矩阵构造(核心算法)
矩阵填充规则
第0, 1点(起点和第二个点):
// fem_pos_deviation_osqp_interface.cpp: 145-159行
// 对角元素
P[0,0] = weight_fem + weight_length + weight_ref // X+Y+Z
P[1,1] = 5*weight_fem + 2*weight_length + weight_ref // 5X+2Y+Z
// 非对角元素
P[0,2] = –2*weight_fem – weight_length // -2X-Y
P[1,3] = –4*weight_fem – weight_length // -4X-Y
中间点
i
i
i(
2
≤
i
≤
n
−
3
2 \\leq i \\leq n-3
2≤i≤n−3):
// fem_pos_deviation_osqp_interface.cpp: 162-176行
P[i,i–2] = weight_fem // X
P[i,i–1] = –4*weight_fem – weight_length // -4X-Y
P[i,i] = 6*weight_fem + 2*weight_length + weight_ref // 6X+2Y+Z
倒数第2, 1点:
// fem_pos_deviation_osqp_interface.cpp: 180-199行
// 对称于起点的处理
P[n–2,n–2] = 5*weight_fem + 2*weight_length + weight_ref
P[n–1,n–1] = weight_fem + weight_length + weight_ref
推导过程
以中间点
p
2
p_2
p2 为例(6点系统中的第3个点):
FEM项:
J
f
e
m
=
w
X
[
(
p
0
−
2
p
1
+
p
2
)
2
+
(
p
1
−
2
p
2
+
p
3
)
2
+
(
p
2
−
2
p
3
+
p
4
)
2
]
(28)
J_{fem} = w_X \\left[ (p_0 – 2p_1 + p_2)^2 + (p_1 – 2p_2 + p_3)^2 + (p_2 – 2p_3 + p_4)^2 \\right] \\tag{28}
Jfem=wX[(p0−2p1+p2)2+(p1−2p2+p3)2+(p2−2p3+p4)2](28)
对
p
2
p_2
p2 求二阶偏导:
∂
2
J
f
e
m
∂
p
2
2
=
w
X
(
1
+
4
+
1
)
=
6
w
X
(29)
\\frac{\\partial^2 J_{fem}}{\\partial p_2^2} = w_X(1 + 4 + 1) = 6w_X \\tag{29}
∂p22∂2Jfem=wX(1+4+1)=6wX(29)
∂
2
J
f
e
m
∂
p
2
∂
p
1
=
w
X
(
−
2
−
2
)
=
−
4
w
X
(30)
\\frac{\\partial^2 J_{fem}}{\\partial p_2 \\partial p_1} = w_X(-2 – 2) = -4w_X \\tag{30}
∂p2∂p1∂2Jfem=wX(−2−2)=−4wX(30)
长度项:
J
l
e
n
g
t
h
=
w
Y
[
(
p
2
−
p
1
)
2
+
(
p
3
−
p
2
)
2
]
(31)
J_{length} = w_Y \\left[ (p_2 – p_1)^2 + (p_3 – p_2)^2 \\right] \\tag{31}
Jlength=wY[(p2−p1)2+(p3−p2)2](31)
对
p
2
p_2
p2 求二阶偏导:
∂
2
J
l
e
n
g
t
h
∂
p
2
2
=
2
w
Y
(32)
\\frac{\\partial^2 J_{length}}{\\partial p_2^2} = 2w_Y \\tag{32}
∂p22∂2Jlength=2wY(32)
∂
2
J
l
e
n
g
t
h
∂
p
2
∂
p
1
=
−
w
Y
(33)
\\frac{\\partial^2 J_{length}}{\\partial p_2 \\partial p_1} = -w_Y \\tag{33}
∂p2∂p1∂2Jlength=−wY(33)
总Hessian(
p
2
p_2
p2 对应的块):
P
22
=
6
w
X
+
2
w
Y
+
w
Z
(34)
\\mathbf{P}_{22} = 6w_X + 2w_Y + w_Z \\tag{34}
P22=6wX+2wY+wZ(34)
P
21
=
−
4
w
X
−
w
Y
(35)
\\mathbf{P}_{21} = -4w_X – w_Y \\tag{35}
P21=−4wX−wY(35)
7. 可视化示意图
7.1 有限元估计示意图
原始路径(有锯齿):
p₂ ●
/ \\
/ \\
/ \\
● ●
p₁ p₃
FEM估计:中点应该在p₁和p₃的中间
p̂₂ = (p₁ + p₃)/2
p₂ ● ← 实际位置
|
| ← 偏差 e₂
|
p̂₂ ● ← FEM估计位置
/ \\
/ \\
●──────●
p₁ p₃
平滑后的路径:
●
/ \\
/ \\ ← 更平滑的曲线
/ \\
● ●
7.2 三种代价的几何意义
(a) FEM代价 (b) 长度代价 (c) 参考偏差代价
● ● ●ref
\\ \\ |
\\ ← 曲率大 \\ ← 路径长 | ← 偏移
● ●―――――――● ●actual
/ 代价高 代价高 代价高
●
优化目标:平衡三者
●―――●―――● ← 太平滑但偏离原路径且过长
●⌒●⌒● ← 跟随原路径但不平滑
●~●~● ← 最优:平滑且不过度偏离
8. 代码实现对照
8.1 Hessian矩阵构造
理论公式:
P
i
i
=
6
w
f
e
m
+
2
w
l
e
n
g
t
h
+
w
r
e
f
(36)
\\mathbf{P}_{ii} = 6w_{fem} + 2w_{length} + w_{ref} \\tag{36}
Pii=6wfem+2wlength+wref(36)
对应代码:
// fem_pos_deviation_osqp_interface.cpp: 172-173行
columns[col_index].emplace_back(
col_index, 6.0 * weight_fem_pos_deviation_ +
2.0 * weight_path_length_ + weight_ref_deviation_);
8.2 线性项计算
理论公式:
q
i
=
−
2
w
r
e
f
⋅
p
i
r
e
f
(37)
q_i = -2 w_{ref} \\cdot p_i^{ref} \\tag{37}
qi=−2wref⋅piref(37)
对应代码:
// fem_pos_deviation_osqp_interface.cpp: 218-219行
q->push_back(–2.0 * weight_ref_deviation_ * ref_point_xy.first);
q->push_back(–2.0 * weight_ref_deviation_ * ref_point_xy.second);
8.3 约束条件
理论公式:
p
i
r
e
f
−
b
i
≤
p
i
≤
p
i
r
e
f
+
b
i
(38)
p_i^{ref} – b_i \\leq p_i \\leq p_i^{ref} + b_i \\tag{38}
piref−bi≤pi≤piref+bi(38)
对应代码:
// fem_pos_deviation_osqp_interface.cpp: 238-241行
upper_bounds->push_back(ref_point_xy.first + bounds_around_refs_[i]);
upper_bounds->push_back(ref_point_xy.second + bounds_around_refs_[i]);
lower_bounds->push_back(ref_point_xy.first – bounds_around_refs_[i]);
lower_bounds->push_back(ref_point_xy.second – bounds_around_refs_[i]);
8.4 归一化处理
理论意义:
将坐标原点移到起点,提高数值稳定性(避免处理很大的坐标值)。
对应代码:
// discrete_points_reference_line_smoother.cpp: 112-127行
void NormalizePoints(std::vector<std::pair<double, double>>* xy_points) {
zero_x_ = xy_points->front().first; // 记录起点x
zero_y_ = xy_points->front().second; // 记录起点y
// 所有点减去起点坐标
std::for_each(xy_points->begin(), xy_points->end(),
[this](std::pair<double, double>& point) {
point.first -= zero_x_; // x相对化
point.second -= zero_y_; // y相对化
});
}
数学表示:
p
~
i
=
p
i
−
p
0
(39)
\\tilde{p}_i = p_i – p_0 \\tag{39}
p~i=pi−p0(39)
8.5 OSQP求解器调用
求解流程:
// fem_pos_deviation_osqp_interface.cpp: 71-88行
// 1. 配置OSQP参数
osqp_set_default_settings(settings);
settings->max_iter = max_iter_; // 最大迭代次数
settings->time_limit = time_limit_; // 时间限制
settings->verbose = verbose_; // 是否输出详细信息
settings->warm_start = warm_start_; // 热启动
// 2. 设置优化问题数据
data->P = csc_matrix(...); // Hessian矩阵(CSC稀疏格式)
data->q = q->data(); // 线性项
data->A = csc_matrix(...); // 约束矩阵
data->l = lower_bounds; // 下界
data->u = upper_bounds; // 上界
// 3. 初始化workspace并求解
osqp_setup(&work, data, settings);
osqp_warm_start_x(*work, primal_warm_start);
osqp_solve(*work);
// 4. 提取解
for (int i = 0; i < num_of_points_; ++i) {
x_[i] = work->solution->x[2*i]; // 优化后的x坐标
y_[i] = work->solution->x[2*i+1]; // 优化后的y坐标
}
9. 矩阵示例推导
9.1 4点系统完整示例
假设有4个路径点
p
0
,
p
1
,
p
2
,
p
3
p_0, p_1, p_2, p_3
p0,p1,p2,p3,权重
w
X
=
1
,
w
Y
=
1
,
w
Z
=
1
w_X=1, w_Y=1, w_Z=1
wX=1,wY=1,wZ=1。
Hessian矩阵(
8
×
8
8 \\times 8
8×8,每个点2维):
P
=
[
3
I
−
3
I
I
0
⋅
7
I
−
5
I
I
⋅
⋅
7
I
−
3
I
⋅
⋅
⋅
3
I
]
(40)
\\mathbf{P} = \\begin{bmatrix} 3\\mathbf{I} & -3\\mathbf{I} & \\mathbf{I} & \\mathbf{0} \\\\ \\cdot & 7\\mathbf{I} & -5\\mathbf{I} & \\mathbf{I} \\\\ \\cdot & \\cdot & 7\\mathbf{I} & -3\\mathbf{I} \\\\ \\cdot & \\cdot & \\cdot & 3\\mathbf{I} \\end{bmatrix} \\tag{40}
P=
3I⋅⋅⋅−3I7I⋅⋅I−5I7I⋅0I−3I3I
(40)
展开为标量形式(
I
=
diag
(
1
,
1
)
\\mathbf{I} = \\text{diag}(1,1)
I=diag(1,1)):
P
=
[
3
0
−
3
0
1
0
0
0
0
3
0
−
3
0
1
0
0
⋅
⋅
7
0
−
5
0
1
0
⋅
⋅
0
7
0
−
5
0
1
⋅
⋅
⋅
⋅
7
0
−
3
0
⋅
⋅
⋅
⋅
0
7
0
−
3
⋅
⋅
⋅
⋅
⋅
⋅
3
0
⋅
⋅
⋅
⋅
⋅
⋅
0
3
]
(41)
\\mathbf{P} = \\begin{bmatrix} 3 & 0 & -3 & 0 & 1 & 0 & 0 & 0 \\\\ 0 & 3 & 0 & -3 & 0 & 1 & 0 & 0 \\\\ \\cdot & \\cdot & 7 & 0 & -5 & 0 & 1 & 0 \\\\ \\cdot & \\cdot & 0 & 7 & 0 & -5 & 0 & 1 \\\\ \\cdot & \\cdot & \\cdot & \\cdot & 7 & 0 & -3 & 0 \\\\ \\cdot & \\cdot & \\cdot & \\cdot & 0 & 7 & 0 & -3 \\\\ \\cdot & \\cdot & \\cdot & \\cdot & \\cdot & \\cdot & 3 & 0 \\\\ \\cdot & \\cdot & \\cdot & \\cdot & \\cdot & \\cdot & 0 & 3 \\end{bmatrix} \\tag{41}
P=
30⋅⋅⋅⋅⋅⋅03⋅⋅⋅⋅⋅⋅−3070⋅⋅⋅⋅0−307⋅⋅⋅⋅10−5070⋅⋅010−507⋅⋅0010−303000010−303
(41)
线性项
q
\\mathbf{q}
q:
q
=
−
2
w
Z
[
x
0
r
e
f
y
0
r
e
f
x
1
r
e
f
y
1
r
e
f
x
2
r
e
f
y
2
r
e
f
x
3
r
e
f
y
3
r
e
f
]
(42)
\\mathbf{q} = -2w_Z \\begin{bmatrix} x_0^{ref} \\\\ y_0^{ref} \\\\ x_1^{ref} \\\\ y_1^{ref} \\\\ x_2^{ref} \\\\ y_2^{ref} \\\\ x_3^{ref} \\\\ y_3^{ref} \\end{bmatrix} \\tag{42}
q=−2wZ
x0refy0refx1refy1refx2refy2refx3refy3ref
(42)
9.2 数值计算示例
输入:
原始点:
p0 = (0, 0)
p1 = (1, 0.5) ← 有偏移
p2 = (2, 0)
p3 = (3, 0)
权重:w_fem=10, w_length=1, w_ref=1
边界:b = [0, 0.5, 0.5, 0]
FEM分析:
- p1的FEM估计:
p
^
1
=
(
p
0
+
p
2
)
/
2
=
(
1
,
0.25
)
\\hat{p}_1 = (p_0 + p_2)/2 = (1, 0.25)
p^1=(p0+p2)/2=(1,0.25) - 偏差:
e
1
=
p
1
−
p
^
1
=
(
0
,
0.25
)
e_1 = p_1 – \\hat{p}_1 = (0, 0.25)
e1=p1−p^1=(0,0.25) - 代价:
J
f
e
m
(
1
)
=
10
×
0.25
2
=
0.625
J_{fem}^{(1)} = 10 \\times 0.25^2 = 0.625
Jfem(1)=10×0.252=0.625
优化后(示意):
平滑点:
p0 = (0, 0) ← 固定
p1 ≈ (1, 0.3) ← 偏移减小
p2 ≈ (2, 0.05) ← 轻微调整
p3 = (3, 0) ← 固定
路径更平滑,偏差变小
10. 算法特性分析
10.1 计算复杂度
- 变量数量:
2
n
2n
2n(n
n
n 个点,每个点2维) - Hessian矩阵:
2
n
×
2
n
2n \\times 2n
2n×2n,带状稀疏(每行最多5个非零元素) - 时间复杂度:
O
(
n
)
O(n)
O(n)(利用稀疏性,OSQP内部求解) - 空间复杂度:
O
(
n
)
O(n)
O(n)
10.2 收敛性
OSQP求解器保证:
- 凸优化问题,全局最优解存在
- 通常 10-50 次迭代内收敛
- 收敛条件:原始残差和对偶残差同时满足容差
10.3 参数敏感性分析
|
w f e m w_{fem} wfem |
路径更平滑(接近直线) | 允许更大曲率 |
|
w l e n g t h w_{length} wlength |
路径更短(走捷径) | 路径可以绕远 |
|
w r e f w_{ref} wref |
更接近原始路径 | 允许更大偏离 |
推荐配置(Apollo默认值):
weight_fem_pos_deviation = 1e10; // 强调平滑性
weight_path_length = 1.0; // 弱惩罚长度
weight_ref_deviation = 1.0; // 弱惩罚偏差
物理解释:
- FEM项权重非常大 → 平滑性优先
- 长度和偏差项较小 → 作为软约束
11. 关键代码段详解
11.1 主Hessian循环
// fem_pos_deviation_osqp_interface.cpp: 162-176行
int second_point_from_last_index = num_of_points_ – 2;
for (int point_index = 2; point_index < second_point_from_last_index; ++point_index) {
int col_index = point_index * 2; // 每个点占2列(x,y)
for (int col = 0; col < 2; ++col) {
col_index += col;
// 填充上三角矩阵的三个元素
columns[col_index].emplace_back(col_index – 4, weight_fem_pos_deviation_);
// ↑ 对应 p[i-2] 的交叉项:系数 = w_fem
columns[col_index].emplace_back(
col_index – 2,
–4.0 * weight_fem_pos_deviation_ – weight_path_length_);
// ↑ 对应 p[i-1] 的交叉项:系数 = -4w_fem – w_length
columns[col_index].emplace_back(
col_index,
6.0 * weight_fem_pos_deviation_ +
2.0 * weight_path_length_ +
weight_ref_deviation_);
// ↑ 对应 p[i] 的对角项:系数 = 6w_fem + 2w_length + w_ref
++col_num;
}
}
数学对应:
p
i
−
2
:
w
f
e
m
p
i
−
1
:
−
4
w
f
e
m
−
w
l
e
n
g
t
h
p
i
:
6
w
f
e
m
+
2
w
l
e
n
g
t
h
+
w
r
e
f
(43)
\\begin{aligned} &p_{i-2}: \\quad w_{fem} \\\\ &p_{i-1}: \\quad -4w_{fem} – w_{length} \\\\ &p_i: \\quad \\quad 6w_{fem} + 2w_{length} + w_{ref} \\end{aligned} \\tag{43}
pi−2:wfempi−1:−4wfem−wlengthpi:6wfem+2wlength+wref(43)
11.2 曲率计算
平滑后需要计算路径的几何属性:
// discrete_points_reference_line_smoother.cpp: 179-180行
double kappa = (ddy * dx – ddx * dy) / pow((pow(dx, 2) + pow(dy, 2)), (1.5));
path(4, i) = kappa;
理论公式:
曲率的标准定义:
κ
=
∣
x
′
y
′
′
−
y
′
x
′
′
∣
(
x
′
2
+
y
′
2
)
3
/
2
(44)
\\kappa = \\frac{|x'y'' – y'x''|}{(x'^2 + y'^2)^{3/2}} \\tag{44}
κ=(x′2+y′2)3/2∣x′y′′−y′x′′∣(44)
有符号曲率(考虑左转/右转):
κ
=
x
′
y
′
′
−
y
′
x
′
′
(
x
′
2
+
y
′
2
)
3
/
2
(45)
\\kappa = \\frac{x'y'' – y'x''}{(x'^2 + y'^2)^{3/2}} \\tag{45}
κ=(x′2+y′2)3/2x′y′′−y′x′′(45)
其中:
-
x
′
=
d
x
d
s
x' = \\frac{dx}{ds}
x′=dsdx:x对弧长的一阶导数 -
y
′
=
d
y
d
s
y' = \\frac{dy}{ds}
y′=dsdy:y对弧长的一阶导数 -
x
′
′
=
d
2
x
d
s
2
x'' = \\frac{d^2x}{ds^2}
x′′=ds2d2x:x对弧长的二阶导数 -
y
′
′
=
d
2
y
d
s
2
y'' = \\frac{d^2y}{ds^2}
y′′=ds2d2y:y对弧长的二阶导数
数值近似(中心差分):
d
x
≈
x
i
+
1
−
x
i
(46)
dx \\approx x_{i+1} – x_i \\tag{46}
dx≈xi+1−xi(46)
d
d
x
≈
x
i
+
1
+
x
i
−
1
−
2
x
i
(47)
ddx \\approx x_{i+1} + x_{i-1} – 2x_i \\tag{47}
ddx≈xi+1+xi−1−2xi(47)
12. 完整算法伪代码
def smooth_reference_line(raw_points, lateral_bounds):
"""
参考线平滑算法
输入:
raw_points: List[(x, y)] – 原始路径点
lateral_bounds: List[float] – 每个点的横向偏移限制
输出:
smoothed_points: List[(x, y)] – 平滑后的路径点
"""
# 步骤1:归一化
origin = raw_points[0]
normalized_points = [(p.x – origin.x, p.y – origin.y) for p in raw_points]
# 步骤2:构造QP问题
n = len(normalized_points)
P = construct_hessian_matrix(n, w_fem, w_length, w_ref) # 2n×2n
q = construct_linear_term(normalized_points, w_ref) # 2n×1
A = identity_matrix(2*n) # 2n×2n
lb = [p_ref[i] – bounds[i] for i in range(n)] # 下界
ub = [p_ref[i] + bounds[i] for i in range(n)] # 上界
# 步骤3:求解QP
solution = osqp_solve(P, q, A, lb, ub)
# 步骤4:反归一化
smoothed_points = [(sol.x + origin.x, sol.y + origin.y) for sol in solution]
# 步骤5:计算几何属性(yaw, kappa, dkappa)
compute_geometric_attributes(smoothed_points)
return smoothed_points
13. 参数调优指南
13.1 权重参数配置
场景1:城市道路(频繁转弯)
weight_fem_pos_deviation: 1e8 # 中等平滑
weight_path_length: 0.5 # 允许绕路
weight_ref_deviation: 2.0 # 紧跟原路径
场景2:高速公路(大曲率转弯少)
weight_fem_pos_deviation: 1e10 # 强平滑
weight_path_length: 1.0 # 走捷径
weight_ref_deviation: 0.5 # 允许偏离
场景3:避障路径(DP输出的锯齿路径)
weight_fem_pos_deviation: 1e12 # 极强平滑
weight_path_length: 2.0 # 尽量缩短
weight_ref_deviation: 0.1 # 大幅偏离允许
13.2 边界参数
// reference_line_provider.cpp: 8行
longitudinal_boundary_bound = 0.1; // 纵向偏移限制(沿路径方向)
min_lateral_boundary_bound = 0.25; // 最小横向偏移限制
max_lateral_boundary_bound = 2.0; // 最大横向偏移限制
物理意义:
- 纵向边界小 → 保持路径点的纵向分布
- 横向边界大 → 允许横向调整(用于平滑)
14. 数学公式总结
14.1 优化问题完整形式
min
p
J
(
p
)
=
J
f
e
m
(
p
)
+
J
l
e
n
g
t
h
(
p
)
+
J
r
e
f
(
p
)
where
J
f
e
m
(
p
)
=
w
X
∑
i
=
1
n
−
2
1
4
∥
p
i
−
1
−
2
p
i
+
p
i
+
1
∥
2
J
l
e
n
g
t
h
(
p
)
=
w
Y
∑
i
=
0
n
−
2
∥
p
i
+
1
−
p
i
∥
2
J
r
e
f
(
p
)
=
w
Z
∑
i
=
0
n
−
1
∥
p
i
−
p
i
r
e
f
∥
2
s.t.
p
i
r
e
f
−
b
i
≤
p
i
≤
p
i
r
e
f
+
b
i
,
i
=
0
,
1
,
…
,
n
−
1
(48)
\\begin{aligned} \\min_{\\mathbf{p}} \\quad & J(\\mathbf{p}) = J_{fem}(\\mathbf{p}) + J_{length}(\\mathbf{p}) + J_{ref}(\\mathbf{p}) \\\\[0.5em] \\text{where} \\quad & J_{fem}(\\mathbf{p}) = w_X \\sum_{i=1}^{n-2} \\frac{1}{4} \\|p_{i-1} – 2p_i + p_{i+1}\\|^2 \\\\[0.5em] & J_{length}(\\mathbf{p}) = w_Y \\sum_{i=0}^{n-2} \\|p_{i+1} – p_i\\|^2 \\\\[0.5em] & J_{ref}(\\mathbf{p}) = w_Z \\sum_{i=0}^{n-1} \\|p_i – p_i^{ref}\\|^2 \\\\[0.5em] \\text{s.t.} \\quad & p_i^{ref} – b_i \\leq p_i \\leq p_i^{ref} + b_i, \\quad i = 0, 1, \\ldots, n-1 \\end{aligned} \\tag{48}
pminwheres.t.J(p)=Jfem(p)+Jlength(p)+Jref(p)Jfem(p)=wXi=1∑n−241∥pi−1−2pi+pi+1∥2Jlength(p)=wYi=0∑n−2∥pi+1−pi∥2Jref(p)=wZi=0∑n−1∥pi−piref∥2piref−bi≤pi≤piref+bi,i=0,1,…,n−1(48)
14.2 矩阵形式
min
p
1
2
p
T
P
p
+
q
T
p
s.t.
l
≤
A
p
≤
u
(49)
\\begin{aligned} \\min_{\\mathbf{p}} \\quad & \\frac{1}{2} \\mathbf{p}^T \\mathbf{P} \\mathbf{p} + \\mathbf{q}^T \\mathbf{p} \\\\ \\text{s.t.} \\quad & \\mathbf{l} \\leq \\mathbf{A} \\mathbf{p} \\leq \\mathbf{u} \\end{aligned} \\tag{49}
pmins.t.21pTPp+qTpl≤Ap≤u(49)
其中:
P
=
H
f
e
m
+
H
l
e
n
g
t
h
+
H
r
e
f
(50)
\\mathbf{P} = \\mathbf{H}_{fem} + \\mathbf{H}_{length} + \\mathbf{H}_{ref} \\tag{50}
P=Hfem+Hlength+Href(50)
q
=
−
2
w
Z
p
r
e
f
(51)
\\mathbf{q} = -2w_Z \\mathbf{p}^{ref} \\tag{51}
q=−2wZpref(51)
14.3 关键矩阵元素
对于中间点
i
i
i (
2
≤
i
≤
n
−
3
2 \\leq i \\leq n-3
2≤i≤n−3):
P
i
,
i
−
2
=
w
X
P
i
,
i
−
1
=
−
4
w
X
−
w
Y
P
i
,
i
=
6
w
X
+
2
w
Y
+
w
Z
P
i
,
i
+
1
=
−
4
w
X
−
w
Y
P
i
,
i
+
2
=
w
X
(52)
\\begin{aligned} \\mathbf{P}_{i,i-2} &= w_X \\\\ \\mathbf{P}_{i,i-1} &= -4w_X – w_Y \\\\ \\mathbf{P}_{i,i} &= 6w_X + 2w_Y + w_Z \\\\ \\mathbf{P}_{i,i+1} &= -4w_X – w_Y \\\\ \\mathbf{P}_{i,i+2} &= w_X \\end{aligned} \\tag{52}
Pi,i−2Pi,i−1Pi,iPi,i+1Pi,i+2=wX=−4wX−wY=6wX+2wY+wZ=−4wX−wY=wX(52)
15. 实现文件对应表
| 平滑器接口 | discrete_points_reference_line_smoother.h | Smooth | 21-28 |
| 平滑器实现 | discrete_points_reference_line_smoother.cpp | Smooth | 8-51 |
| FEM求解器 | fem_pos_deviation_osqp_interface.h | Solve | 68-75 |
| FEM实现 | fem_pos_deviation_osqp_interface.cpp | Solve | 25-116 |
| Hessian构造 | fem_pos_deviation_osqp_interface.cpp | CalculateKernel | 119-213 |
| 线性项计算 | fem_pos_deviation_osqp_interface.cpp | CalculateOffset | 215-221 |
| 约束构造 | fem_pos_deviation_osqp_interface.cpp | CalculateAffineConstraint | 223-243 |
| 归一化 | discrete_points_reference_line_smoother.cpp | NormalizePoints | 112-127 |
| 几何计算 | discrete_points_reference_line_smoother.cpp | referencePath | 141-203 |
| 锚点提取 | reference_line_provider.cpp | GetAnchorPoints | 14-30 |
16. 使用示例
16.1 基本使用
// 创建平滑器对象
DiscretePointsReferenceLineSmoother smoother;
// 设置锚点(原始路径点)
std::vector<AnchorPoint> anchor_points;
for (const auto& point : raw_path) {
AnchorPoint anchor;
anchor.x = point.x;
anchor.y = point.y;
anchor.s = point.s;
anchor.lateral_bound = 0.25; // 横向偏移限制
anchor_points.push_back(anchor);
}
smoother.SetAnchorPoints(anchor_points);
// 执行平滑
Eigen::MatrixXd raw_line; // 7×n:[x, y, v, yaw, kappa, dkappa, s]
Eigen::MatrixXd smoothed_line; // 输出
bool success = smoother.Smooth(raw_line, smoothed_line);
if (success) {
// 使用平滑后的路径
use_path(smoothed_line);
}
16.2 参数调整
// 在discrete_points_reference_line_smoother.h中修改
weight_fem_pos_deviation = 1e10; // 平滑度
weight_path_length = 1.0; // 路径长度
weight_ref_deviation = 1.0; // 参考偏差
17. 参考文献
Apollo Planning Module https://github.com/ApolloAuto/apollo/tree/master/modules/planning
OSQP: Operator Splitting QP Solver Stellato, B., et al. (2020). “OSQP: An operator splitting solver for quadratic programs.” Mathematical Programming Computation, 12(4), 637-672.
Finite Element Method for Path Smoothing Dolgov, D., et al. (2010). “Path planning for autonomous vehicles in unknown semi-structured environments.” The International Journal of Robotics Research, 29(5), 485-501.
二次规划理论 Nocedal, J., & Wright, S. (2006). “Numerical optimization.” Springer Science & Business Media.
18. 附录:代码完整调用链
main.cpp: ros2_sim_planning_callbak()
↓
motion_planning.cpp: global_callback()
↓
(可选)motion_planning.cpp: process()
↓
(如需平滑)reference_line_provider.cpp: SmoothReferenceLine()
↓
discrete_points_reference_line_smoother.cpp: Smooth()
↓
discrete_points_reference_line_smoother.cpp: FemPosSmooth()
↓
discrete_points_reference_line_smoother.cpp: Solve()
↓
fem_pos_deviation_osqp_interface.cpp: Solve()
├─ CalculateKernel() // 构造P矩阵
├─ CalculateOffset() // 构造q向量
├─ CalculateAffineConstraint() // 构造约束
└─ OptimizeWithOsqp() // 调用OSQP求解



![P11246 [GESP202409 六级] 小杨和整数拆分-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108100631-6af049a75855c-220x150.png)