轨迹优化 | 基于Savitzky-Golay滤波的无约束路径平滑(附ROS C++/Python仿真)

目录

  • 0 专栏介绍
  • 1 什么是Savitzky-Golay滤波?
  • 2 Savitzky-Golay滤波推导
  • 3 算法仿真与验证
    • 3.1 ROS C++仿真
    • 3.2 Python仿真

0 专栏介绍

🔥课设、毕设、创新竞赛必备!🔥本专栏涉及更高阶的运动规划算法轨迹优化实战,包括:曲线生成、碰撞检测、安全走廊、优化建模(QP、SQP、NMPC、iLQR等)、轨迹优化(梯度法、曲线法等),每个算法都包含代码实现加深理解

🚀详情:运动规划实战进阶:轨迹优化篇


1 什么是Savitzky-Golay滤波?

Savitzky--Golay滤波器是一种数字滤波器,能够应用于一组数字数据点,用于平滑数据,即在不扭曲信号趋势的情况下提高数据的精度,如下图所示。Savitzky--Golay滤波器使用低阶多项式通过最小二乘法拟合相邻数据点的连续子集。当数据点是等间距时,可以找到最小二乘方程的解析解,形式为一组卷积系数,这些系数可以应用于所有数据子集,用于估算平滑信号在每个子集中心点的值。该方法可以扩展到2维和3维数据的处理。

2 Savitzky-Golay滤波推导

形式化地,针对由 K K K个点组成的离散路径 { x k } \left\{ x_k \right\} {xk},构造以 x k x_k xk为中心,其相邻的共 2 M + 1 2M+1 2M+1个数据点组成的连续子集窗口 W = { x ^ i } i = − M M W=\left\{ \hat{x}_i \right\} _{i=-M}^{M} W={x^i}i=−MM。用 N N N次多项式

p ( i ) = ∑ n = 0 N a n i n p\left( i \right) =\sum_{n=0}^N{a_ni^n} p(i)=n=0∑Nanin

拟合 W W W内的数据,拟合残差为

ε = ∑ i = − M M ( p ( i ) − x ^ i ) 2 = ∑ i = − M M ( ∑ j = 0 N a j i j − x ^ i ) 2 \varepsilon =\sum_{i=-M}^M{\left( p\left( i \right) -\hat{x}i \right) ^2}=\sum{i=-M}^M{\left( \sum_{j=0}^N{a_ji^j}-\hat{x}_i \right) ^2} ε=i=−M∑M(p(i)−x^i)2=i=−M∑M(j=0∑Najij−x^i)2

为了使残差最小,令

∂ ε ∂ a = 2 ∑ i = − M M i 0 ( ∑ j = 0 N a j i j − x \^ i ) ⋯ 2 ∑ i = − M M i N ( ∑ j = 0 N a j i j − x \^ i ) ( N + 1 ) × 1 T = 0 \frac{\partial \varepsilon}{\partial \boldsymbol{a}}=\left \\begin{matrix} 2\\sum_{i=-M}\^M{i\^0\\left( \\sum_{j=0}\^N{a_ji\^j}-\\hat{x}_i \\right)}\& \\cdots\& 2\\sum_{i=-M}\^M{i\^N\\left( \\sum_{j=0}\^N{a_ji\^j}-\\hat{x}_i \\right)}\\\\\\end{matrix} \\right {{\left( N+1 \right) \times 1}}^{T}=\mathbf{0} ∂a∂ε=2∑i=−MMi0(∑j=0Najij−x\^i)⋯2∑i=−MMiN(∑j=0Najij−x\^i)(N+1)×1T=0

其中 a = a 0 a 1 ⋯ a N T \boldsymbol{a}=\left \\begin{matrix} a_0\& a_1\& \\cdots\& a_N\\\\\\end{matrix} \\right ^T a=a0a1⋯aNT为多项式系数向量

设只关联于预设的窗口大小 M M M和多项式拟合次数 N N N的参数矩阵

A = ( − M ) 0 ( − M ) 1 ⋯ ( − M ) N ⋮ ⋮ ⋱ ⋮ ( M ) 0 ( M ) 1 ⋯ ( M ) N ( 2 M + 1 ) × ( N + 1 ) \boldsymbol{A}=\left \\begin{matrix} \\left( -M \\right) \^0\& \\left( -M \\right) \^1\& \\cdots\& \\left( -M \\right) \^N\\\\ \\vdots\& \\vdots\& \\ddots\& \\vdots\\\\ \\left( M \\right) \^0\& \\left( M \\right) \^1\& \\cdots\& \\left( M \\right) \^N\\\\\\end{matrix} \\right _{\left( 2M+1 \right) \times \left( N+1 \right)} A=⎣⎢⎡(−M)0⋮(M)0(−M)1⋮(M)1⋯⋱⋯(−M)N⋮(M)N⎦⎥⎤(2M+1)×(N+1)

则最小二乘关系可表示为

A T A a = A T x ^ ⇒ a = H x ^ \boldsymbol{A}^T\boldsymbol{Aa}=\boldsymbol{A}^T\boldsymbol{\hat{x}}\Rightarrow \boldsymbol{a}=\boldsymbol{H\hat{x}} ATAa=ATx^⇒a=Hx^

其中 H = ( A T A ) − 1 A T \boldsymbol{H}=\left( \boldsymbol{A}^T\boldsymbol{A} \right) ^{-1}\boldsymbol{A}^T H=(ATA)−1AT

由于每次平滑只输出中心的平滑信号,因此只需考虑 H \boldsymbol{H} H的第一行元素,称为Savitzky-Golay滤波器的卷积核

3 算法仿真与验证

3.1 ROS C++仿真

核心算法如下所示

cpp 复制代码
bool SavitzkyGolayPathProcessor::_applyFilterOverPath(const Points3d& path_in, Points3d& path_out)
{
  int path_size = static_cast<int>(path_in.size());
  if (path_size < kernel.size())
  {
    return true;
  }

  path_out.clear();
  auto pt_m3 = path_in[0];
  auto pt_m2 = path_in[0];
  auto pt_m1 = path_in[0];
  auto pt = path_in[1];
  auto pt_p1 = path_in[2];
  auto pt_p2 = path_in[3];
  auto pt_p3 = path_in[4];

  // First ang last point is fixed
  path_out.emplace_back(path_in[0].x(), path_in[0].y());
  for (unsigned int idx = 1; idx < path_size; idx++)
  {
    Point3d smooth_pt;
    if (!_applyFilter({ pt_m3, pt_m2, pt_m1, pt, pt_p1, pt_p2, pt_p3 }, smooth_pt))
    {
      return false;
    }
    pt_m3 = pt_m2;
    pt_m2 = pt_m1;
    pt_m1 = pt;
    pt = pt_p1;
    pt_p1 = pt_p2;
    pt_p2 = pt_p3;
    pt_p3 = idx + 4 < path_size - 1 ? path_in[idx + 4] : path_in.back();
    path_out.emplace_back(smooth_pt.x(), smooth_pt.y());
  }
  return true;
}

算法效果如下所示


3.2 Python仿真

核心算法如下所示

python 复制代码
def applyFilterOverAxes(self, waypoints: list) -> list:
    M = int((len(self.kernel) - 1) / 2)
    filtered_waypoints = [waypoints[0]]
    window = [waypoints[0] for _ in range(M)]
    window.append(waypoints[1])
    for i in range(M):
        window.append(waypoints[2 + i])

    # First point is fixed
    path_len = len(waypoints)
    for i in range(path_len):
        smooth_pt = self.applyFilter(window)
        filtered_waypoints.append((smooth_pt.x, smooth_pt.y))
        for j in range(2 * M):
            window[j] = window[j + 1]

        if i + M + 1 < path_len - 1:
            window[-1] = waypoints[i + M + 1]
        else:
            # Return the last point
            window[-1] = waypoints[-1]
    return filtered_waypoints

算法效果如下所示

完整工程代码请联系下方博主名片获取


🔥 更多精彩专栏

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

相关推荐
申通之声10 小时前
以体验和AI重构竞争力,申通要“构建生态共同体”
大数据·网络·人工智能·重构·交通物流
terry60010 小时前
2026图形验证码服务商横向测评|口碑、接入、安全选型全指南
java·大数据·人工智能·web安全·信息与通信·数据库架构
Parry10 小时前
用语种检测、翻译、纠错和情感分析构建多语言内容审核 Agent
人工智能
xiami_world10 小时前
私有化部署协同白板选型指南:从Docker容器化到信创全栈适配的架构实践
运维·人工智能·docker·ai·持续部署
Eloudy10 小时前
ns-3 在数据中心网络仿真
网络·人工智能·量子计算
weixin_3077791310 小时前
从脚本执行到智能体协作:AI辅助测试能力的范式重构
运维·开发语言·人工智能·算法·测试用例
量化君也10 小时前
从回测到全自动实盘交易,全天候策略需要经历哪些改造?
大数据·人工智能·python·算法·金融
装不满的克莱因瓶10 小时前
自然语言处理发展历史——从规则系统到大语言模型的演进之路
网络·人工智能·python·深度学习·语言模型·自然语言处理
GensAI10 小时前
智能语音机器人哪家好?实测4款主流产品,从方言识别到外呼稳定性的全面对比
人工智能·语音识别
暂未成功人士!10 小时前
简单了解李群和李代数的相关概念以及典型应用
人工智能·机器人·slam·姿态·李群李代数