想象你站在乱石坡的顶端,眼前是坑坑洼洼、散落巨石的山谷,而你的任务是从山顶到山脚修一条小路。你不会立刻铺满石子再回头看效果(那是随机采样),而是先拉一根绳子到山脚,然后反复观察调整:绳子太弯就拉直一点,绳子碰到石头就绕开一点,每调整一次都比上一版更顺、更安全。这就是“基于优化的规划”最朴素的样子——把“修一条好路”变成“不断修正一条已有的路”。 CHOMP、STOMP、TrajOpt 正是三种拥有不同风格的“修路人”。CHOMP 像拿着指南针,每一步都沿最陡的坡向下走;STOMP 像撒一把豆子,让豆子各自滚出许多条路线,择优录取;TrajOpt 像用望远镜只看眼前一小段,把这段弯路拉直后再向前推进。三种方法殊途同归:让机器人最终走出一条平滑、无碰撞的轨迹。
理解这三种优化规划方法,能让你在实际机器人项目里根据“有没有梯度、约束多不多、要不要实时”快速选出合适的轨迹生成方案,这是运动规划工程师的核心基本功之一。
可视化演示
动态演示深入理解基于优化的规划(CH的核心机制,通过采集/分析/决策/反馈四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
深入理解基于优化的规划(CH:优化规划 优化规划框架与代价函数 CHOMP协变梯度下降规划 核心机制可视化 深入理解基于优化的规划(CH整体架构与关键组件
优化规划概述
修路前,我们需要先把“路”表示成数学对象。乱石坡上的绳子不可能画成一条连续曲线给计算机,于是工程师把绳子等距打上十八个结,每个结的坐标都记下来。这些结连起来就是一条轨迹。修路的过程,就是不断移动这些结的位置——结太多,计算慢;结太少,路不够细腻。选择多少个结,本身就是一门权衡的艺术。
有了路的表示,还需要一把尺子来量“这条路有多差”。这把尺子就是目标函数:它同时惩罚两件事——撞上障碍物和拐弯太急。如果我们把每条路都拿到这把尺子下量一遍,修路就成了“找到一把尺子读数最小的那条路”。
于是,优化规划把轨迹生成问题写成如下形式:
为什么这种思路比采样法先进?想象最原始的修路工人——随机采样法——他们漫山遍野撒几千块石头,希望其中一串恰好连成一条好路,一旦山谷复杂就极其低效。而优化规划从一条初始路线开始,每次只做局部小修正,迭代几十次就能得到可用的光滑轨迹。当然,代价是它需要一个不错的起点,否则容易修进死胡同,这一点我们稍后会详细讨论。
- 最小值符号 min修路的目标是在所有可能路线中挑出“最差程度”最低的那一条,min 就是“取最小”。
- 轨迹变量 ξ一条轨迹由一串路径点组成,$\xi$ 表示整串点的集合,相当于那根打满结的绳子。
- 碰撞代价 F_obs绳子每根结离障碍物越近,这一项就越大,它逼着路线绕开石头。
- 平滑代价 F_smooth相邻两个结之间拉得越直、长度越均匀,这一项越小,它逼着路线不来回折返。
- 权重系数 λ它是一个旋钮,调大 $\lambda$ 就更重视平滑,调小就更重视避障。
数学基础铺垫
让我们把“目标函数”想象成一张地形等高线图。每条候选轨迹都对应山坡上的一个点:路越差,这个点越高;路越好,这个点越低。修路变成下山——你站在山坡上,脚下就是当前轨迹的“高度”,而你要一步步走到谷底。
下山最关键的信息是“哪个方向最陡”。在地形图上,这个方向由梯度给出。梯度是一个向量,它指向“向上爬升最快”的方向;所以我们要做的,就是把轨迹沿着梯度的反方向移动一步。每一小步都是由“方向”和“步长”共同决定的:方向错了越走越高,步长太大容易跳过谷底。
目标函数由碰撞项和平滑项相加而成,梯度自然也是两项之和:
有了这套“下山”工具,三种修路人的差异就清楚了:CHOMP 精确计算梯度,STOMP 用随机扰动隐式估计梯度,TrajOpt 则在局部把山坡近似成碗形再求最低点。
- 梯度算子 ∇读作"nabla",它表示对每个路径点分别求导,合成一个指向上升最快方向的向量。
- 碰撞项梯度障碍物越近,这一项越大,梯度把轨迹点从障碍物旁边推开。
- 平滑项梯度当某个点比邻居凸出时,这一项指向“把它拉回与邻居对齐”的方向。
- 迭代更新式每次沿负梯度方向走一小步,$\alpha$ 是步长。
CHOMP原理
CHOMP 是一位严谨的修路人。它手里有一张精确的坡度图,每走一步之前都要测量当前位置最陡的方向,然后稳稳地迈出一步。这种“沿负梯度方向迭代”的做法,就是梯度下降法。
但 CHOMP 有一个巧妙升级:它不会只移动绳子上单独一个结。想象绳子是一串被弹簧串联的珠子,你拽其中一颗,邻居也会跟着动。CHOMP 的协变梯度下降正是利用了一个光滑度矩阵 $A$,它编码了相邻路径点之间的牵连关系。这样,梯度方向会被“翻译”成考虑邻居牵制的真实移动方向,轨迹更新既平滑又高效。
CHOMP 的迭代公式:
CHOMP 的障碍物梯度主要来自距离场:提前计算地图上每个位置到最近障碍物的距离,轨迹点只要靠近障碍物,距离场就会给出一个“推开”的梯度方向。得益于这种预处理,CHOMP 在机械臂关节空间中非常高效,几十次迭代就能收敛出一条漂亮的轨迹。但它的软肋也很明显——梯度下降天然是局部搜索,如果初始绳套在了深坑里,它很难自己跳出来。
- 迭代下标 k第 $k$ 轮修路结束后的轨迹,下一轮从它开始继续修。
- 光滑度矩阵 A 的逆$A$ 刻画相邻路径点的耦合强度,$A^{-1}$ 让更新量在轨迹度量空间里更自然,避免单个点乱窜。
- 步长 1/η$\eta$ 越大步长越小,修路越稳但越慢;$\eta$ 越小修正越激进,但可能震荡。
STOMP原理
STOMP 是另一个极端的修路人:他不看坡度图,而是闭着眼睛撒出一把豆子。每颗豆子都代表一条随机抖动的候选路线,有的往左偏,有的往右偏。豆子落地后,他先用尺子量每条路线的质量,再按质量好坏把这些路线“加权平均”成一条新路。如此反复,好路线的基因会逐渐保留下来。
这种策略最大的好处是:完全不要求目标函数可导。现实中的很多约束——比如机械臂关节限位、任务空间避障——写不出光滑的数学公式,梯度根本不存在,CHOMP 会束手无策,而 STOMP 照样能量出每条候选路线的好坏并择优进化。
STOMP 的更新公式:
STOMP 的“概率策略”让它特别擅长处理复杂约束。它天然支持并行计算——一次撒 100 颗豆子,10 个处理器各评估 10 颗,速度几乎不变。不过代价也很直观:因为不利用梯度信息,它收敛通常比 CHOMP 慢,而且随机扰动需要调参,方差太大路会乱抖,太小又探索不开。
- 候选数量 K一次撒出 $K$ 颗豆子,每条都是对当前轨迹加随机噪声得到的。
- 扰动 Δξ_k第 $k$ 条候选路线的偏移量,可以同时扰动所有路径点,形成一条完整候选轨迹。
- 权重 w_k将 $K$ 条候选路线的代价经过指数映射后归一化,好路线权重大,坏路线权重小。
TrajOpt原理
TrajOpt 的修路风格像一位稳健的工程师。他不看整座山,只用望远镜观察脚下这一小段路:把眼前十来米的复杂地形,近似成一个光滑的碗形坡,然后直接算出碗底在哪,一步迈过去。每迈一步,再重新观察、重新近似、重新迈步。这就是“序列凸优化”——把难啃的非凸问题,切成一个个容易求解的凸子问题。
凸问题有什么好处?碗形坡只有一个最低点,不存在无数个坑让你迷路;求解它就像是套公式,快且稳定。TrajOpt 在每个迭代步,把原目标函数在当前位置附近做二阶泰勒展开,得到一个凸二次规划子问题:
TrajOpt 对碰撞规避的处理同样巧妙:它显式地把“轨迹与障碍物最近距离大于安全阈值”写成不等式约束,加入子问题中,保证每一步解出的增量都不会让轨迹撞进障碍物。这使得 TrajOpt 在自动驾驶和飞行器规划中非常受欢迎——因为每次迭代只解一个小的凸问题,速度快到可以实时运行。它的局限则在于:局部近似可能偏离真实地形太远,需要配合回退策略保证每一步真的在变好。
- 改进量 Δξ每次迭代要寻找的轨迹增量,找到后更新为 $\xi \leftarrow \xi + \Delta\xi$。
- 曲率矩阵 B近似的 Hessian 矩阵,表示目标函数在当前位置的弯曲程度,决定增量方向的可信范围。
- 线性项一阶梯度信息驱动增量向下坡方向走。
- 交替迭代求解凸子问题得到增量,更新轨迹,再在新的位置重新近似。
对比与选择
三种修路人,三种性格,没有绝对的王者。CHOMP 是“精准的登山者”,有梯度就能高效下山;STOMP 是“乱撒豆子的探索家”,不要梯度、不怕复杂约束;TrajOpt 是“稳扎稳打的工程师”,每次只看一小段但速度极快。
实际选型时,可以用一个加权评分公式来辅助决策:
| 方法 | 优势 | 局限 | 典型场景 |
| --- | --- | --- | --- |
| CHOMP | 收敛快、轨迹光滑 | 需要梯度、易陷局部最优 | 机械臂关节空间避障 |
| STOMP | 无需梯度、约束友好 | 收敛慢、调参多 | 复杂约束、多障碍环境 |
| TrajOpt | 实时性强、理论清晰 | 局部近似有风险 | 自动驾驶、飞行器规划 |
一句话总结:能拿到干净梯度且初始轨迹不差,选 CHOMP;代价函数满是坑洼、梯度不存在,选 STOMP;要求在线实时响应,选 TrajOpt。实践中,很多系统甚至先用快速搜索算法生成初始轨迹,再交给优化规划器精修,各取所长。
- 候选方法集合$\mathcal{M}$ 包含 CHOMP、STOMP、TrajOpt 三种方法。
- 耗时项 T_m从初始化到输出轨迹的计算时间,实时性要求高时权重 $w_1$ 调大。
- 质量项 P_m轨迹的平滑度、成功率等综合指标,追求轨迹品质时权重 $w_2$ 调大。
- 取最小值选综合分最低的方法,得到最适合当前任务的规划器。
AI 应用场景
机械臂抓取与避障
阶段:规划与控制
解决的问题:传统示教编程只能让机械臂走固定路径,遇到障碍物或工件位置变化就得重新人工调整。CHOMP 将关节角序列作为优化变量,用距离场梯度把机械臂推离障碍物,同时保证运动平滑,几毫秒即可生成一条新的无碰撞轨迹。
具体案例:3C 电子装配线上,机械臂需要从传送带抓取不同尺寸的零件。工程师用 CHOMP 为每次抓取在线规划轨迹:先由深度相机建立障碍物距离场,再以当前机械臂姿态为初始轨迹迭代优化。即使零件摆放角度变化,机械臂也能流畅绕开夹具和料框。
仓储移动机器人导航
阶段:规划与控制
解决的问题:仓库中货架、人员、其他机器人造成大量不规则障碍,代价函数往往无法写出解析梯度。STOMP 通过随机扰动并行采样,不需要梯度也能找出安全轨迹,适合这种“地形复杂但可快速评估”的场景。
具体案例:某无人仓中,搬运机器人需要在密集货架间穿行。系统使用 STOMP 在预处理的栅格代价地图上迭代优化路径,同时加入“远离货架角点”和“尽量不突然转向”的代价项。由于 STOMP 天然支持并行,机器人车载计算机用 4 个线程同时评估 40 条候选轨迹,在 200ms 内完成重规划。
自动驾驶局部轨迹规划
阶段:规划与控制
解决的问题:自动驾驶需要在极短时间内对前方动态障碍做出反应,TrajOpt 的序列凸优化把每帧规划压缩为求解一个小型凸二次规划,计算延迟极低,适合毫秒级实时控制。
具体案例:城市道路场景中,自车以 60km/h 行驶时检测到前方慢车。局部规划器以当前车道线为参考生成初始轨迹,TrajOpt 在每一帧把“避障安全距离”和“车道保持”构造成约束,迭代求解出平滑的变道轨迹,并传递给底层控制模块执行。
Python 演示
import math class ChompPlanner: """极简 CHOMP: 在不碰障碍的前提下让轨迹尽量平滑。""" def __init__(self, start, goal, obstacles, n_points=20, lam=0.8): self.start = start self.goal = goal self.obs = obstacles # 障碍物列表 (x, y, r) self.n = n_points self.lam = lam # 碰撞代价的权重 self.traj = [] for i in range(n_points): t = i / (n_points - 1) self.traj.append((start[0] + t * (goal[0] - start[0]), start[1] + t * (goal[1] - start[1]))) def cost(self, traj): c = 0.0 for i in range(1, len(traj)): dx = traj[i][0] - traj[i - 1][0] dy = traj[i][1] - traj[i - 1][1] c += dx * dx + dy * dy for p in traj: for ox, oy, r in self.obs: d = math.hypot(p[0] - ox, p[1] - oy) - r if d < 0.1: c += self.lam * (0.1 - d) * (0.1 - d) return c def gradient(self, traj): grad = [] for i in range(self.n): if i == 0 or i == self.n - 1: grad.append((0.0, 0.0)) continue gx = 2 * (2 * traj[i][0] - traj[i - 1][0] - traj[i + 1][0]) gy = 2 * (2 * traj[i][1] - traj[i - 1][1] - traj[i + 1][1]) for ox, oy, r in self.obs: dx = traj[i][0] - ox dy = traj[i][1] - oy dist = math.hypot(dx, dy) d = dist - r if d < 0.1: w = -2 * self.lam * (0.1 - d) / max(dist, 1e-6) gx += w * dx gy += w * dy grad.append((gx, gy)) return grad def optimize(self, iters=60, lr=0.08): for _ in range(iters): g = self.gradient(self.traj) new_traj = [] for i, p in enumerate(self.traj): if i in (0, self.n - 1): new_traj.append(p) else: new_traj.append((p[0] - lr * g[i][0], p[1] - lr * g[i][1])) self.traj = new_traj return self.traj if __name__ == "__main__": planner = ChompPlanner((0.0, 0.0), (10.0, 10.0), [(5.0, 5.0, 1.0), (7.0, 3.0, 0.8)]) planner.optimize() print("优化后轨迹(前5个点):") for x, y in planner.traj[:5]: print(" (%.2f, %.2f)" % (x, y)) print("最终代价: %.4f" % planner.cost(planner.traj))
self.traj.append((start[0] + t * (goal[0] - start[0]), ...))用直线插值初始化轨迹,作为修路的起点。c += dx * dx + dy * dy相邻点越近越平滑,这是光滑代价的核心。if d < 0.1: c += self.lam * (0.1 - d) * (0.1 - d)进入障碍物缓冲区后开始惩罚,越近罚得越狠。gx = 2 * (2 * traj[i][0] - traj[i-1][0] - traj[i+1][0])平滑项的解析梯度,指向把当前点拉回与邻居对齐的方向。w = -2 * self.lam * (0.1 - d) / max(dist, 1e-6)碰撞梯度的缩放系数,负号让梯度指向障碍物,更新时反向推离。p[0] - lr * g[i][0]沿负梯度方向更新路径点,这就是 CHOMP 的迭代核心。if i in (0, self.n - 1)保持起点和终点固定,只优化中间路径点。
常见误区
误区我一直以为 CHOMP、STOMP、TrajOpt 都必须先写出代价函数的解析梯度才能运行,所以遇到不可导的约束就束手无策。
正解只有 CHOMP 强依赖梯度,STOMP 完全不计算解析梯度,它通过随机扰动轨迹并评估代价来隐式判断“哪个方向更好”,代价函数只需能算出一个数值即可。TrajOpt 虽然用梯度构造线性项,但实践中也能用数值差分近似梯度。就像下山时,有人用精密坡度仪(CHOMP),有人闭眼撒豆子试方向(STOMP),工具不同,但都能下山。
为何常见:教材通常先讲梯度下降,容易让人以为所有优化方法都以梯度为前提,忽略了随机优化这一大分支。
误区梯度下降不断朝低处走,再多迭代几次总是能找到最好的那条轨迹吧?所以 CHOMP 是全局优化。
正解CHOMP 是典型的局部优化方法。它沿负梯度迭代,只会收敛到初始轨迹附近的一个局部极小值。如果初始直线轨迹恰好穿过一个 U 形障碍物,梯度会把路径卡在 U 型底部,永远绕不出去。实践中通常先用快速探索随机树(RRT)等采样方法生成一条无碰撞粗轨迹,再交给 CHOMP 精修。
为何常见:“下坡”的直觉让人误以为一直下坡就能到最低点,忽略了山谷里还有无数小坑。
误区TrajOpt 的每个子问题是凸优化,那它整体就是只能处理凸问题,遇到凹障碍物就没辙。
正解TrajOpt 属于序列凸优化,它处理的是非凸问题——通过把原问题在当前轨迹附近做局部凸近似,求解凸子问题后更新轨迹,再在新位置重新近似。它真正要求的是“局部可近似”,而不是“全局为凸”。就像你用望远镜看脚下的一小段坡,无论整座山多崎岖,眼前一小段总可以近似成碗形。
为何常见:把“子问题是凸的”误解成“原问题必须是凸的”,忽略了迭代外部还有一层非凸探索。
误区平滑代价让轨迹更顺滑,那我把平滑权重调到最大,让轨迹完全变成一条直线,岂不是最优?
正解平滑度只是目标函数的一部分,碰撞代价同样重要。过度强调平滑会削掉所有避障余量,轨迹贴着障碍物边缘甚至直接穿过障碍物。并且,过度平滑会让轨迹丧失在狭窄通道中绕行的灵活性。好轨迹是“足够平滑且留有安全距离”,需要靠权重 $\lambda$ 调平衡,而不是一味追求顺滑。
为何常见:初学优化规划时容易被“平滑”这个直观的美好词汇吸引,忽略了代价函数是多目标权衡。
误区CHOMP、STOMP、TrajOpt 是三种独立框架,选了一个就不能再用另一个,它们之间没有关系。
正解工程实践中,它们常被组合进同一条规划流水线。比如先用 RRT 采样出一条无碰撞初值,再用 CHOMP 精修;或者用 TrajOpt 快速生成局部轨迹,遇到失败再切换到 STOMP 做全局重规划。三种方法共享“目标函数 + 迭代优化”的底层思想,只是搜索策略不同,完全可以优势互补。
为何常见:教材为了讲清原理会分章叙述,容易让人误以为它们是割裂的黑盒。
面试题
Q1. 基于优化的轨迹规划,核心是把路径规划看成什么?
- A.图搜索问题
- B.数值优化问题← 正确答案
- C.随机采样问题
- D.逻辑推理问题
答案:B。优化规划把轨迹写成变量,用目标函数评价好坏,再通过迭代求解最优轨迹,本质是数值优化。类比修路:不是漫山遍野撒石头(采样),也不是在一张图上枚举路径(搜索),而是不断修正一条已有的路。
Q2. CHOMP 中用于引导轨迹改进的信息是?
- A.随机噪声
- B.代价函数的梯度← 正确答案
- C.人工反馈
- D.强化学习奖励
答案:B。CHOMP 是协变梯度下降方法,依靠目标函数(光滑代价 + 碰撞代价)的梯度来更新轨迹。随机噪声是 STOMP 的机制,人工反馈和强化学习奖励与 CHOMP 无直接关系。
Q3. 与 CHOMP 相比,STOMP 的主要特点是?
- A.完全不需要代价函数
- B.用随机扰动评估代价← 正确答案
- C.一定收敛更快
- D.只适用二维路径
答案:B。STOMP 通过随机扰动生成多条候选轨迹,再按代价好坏加权更新,不需要解析梯度。它仍然需要代价函数来评估候选轨迹,收敛速度不一定更快,也不仅限于二维问题。
Q4. TrajOpt 在每个迭代步中求解的子问题是?
- A.线性规划
- B.凸二次规划← 正确答案
- C.整数规划
- D.动态规划
答案:B。TrajOpt 把目标函数局部二阶展开,加上线性化的约束,得到凸二次规划子问题。线性规划、整数规划和动态规划都不是 TrajOpt 的局部子问题形式。
Q5. 以下哪些属于 CHOMP 目标函数中的组成部分?
- A.光滑代价← 正确答案
- B.碰撞代价← 正确答案
- C.协变梯度← 正确答案
- D.蒙特卡洛树
答案:ABC。CHOMP 的目标函数由光滑代价和碰撞代价组成,协变梯度下降是其求解手段,三者共同构成方法的核心。蒙特卡洛树搜索是另一类规划算法,与 CHOMP 无关。
Q6. CHOMP 使用 A 逆矩阵的作用是?
- A.加速程序运行
- B.在轨迹度量空间下降← 正确答案
- C.替代障碍物梯度
- D.保证全局最优
答案:B。A 是光滑度矩阵,编码路径点之间的耦合关系。A^{-1} 将普通梯度投影到轨迹的度量空间中,使更新时相邻点像弹簧串联般联动,避免单点乱跳。它不加速代码、不替代碰撞梯度,也无法保证全局最优。
Q7. 关于 STOMP 的正确说法有?
- A.并行采样多条扰动轨迹← 正确答案
- B.必须计算解析梯度
- C.可用任意平滑代价← 正确答案
- D.只适合二维空间
答案:AC。STOMP 并行生成多条随机扰动轨迹并评估代价,对代价函数只要求可计算数值,因此可以使用任意平滑代价函数。它不计算解析梯度,也适用于高维关节空间,不只限二维。
Q8. TrajOpt 处理非凸代价的主要策略是?
- A.全局随机搜索
- B.局部凸近似← 正确答案
- C.遗传算法
- D.模拟退火
答案:B。TrajOpt 在当前轨迹附近对代价函数做二阶泰勒展开,把非凸问题局部近似成凸二次规划,迭代求解。它不是全局搜索或元启发式方法。
Q9. 障碍物密集且代价不平滑时,最可能选哪种方法?
- A.CHOMP
- B.STOMP← 正确答案
- C.TrajOpt
- D.迪杰斯特拉
答案:B。障碍物密集时代价函数往往不平滑甚至不可导,CHOMP 的梯度方法容易失效,TrajOpt 的局部凸近似也容易失真。STOMP 只要求能评估代价,随机探索更适合这类复杂约束场景。
Q10. 选择优化规划方法时应考虑?
- A.是否要求实时响应← 正确答案
- B.能否获得解析梯度← 正确答案
- C.约束的数量类型← 正确答案
- D.三种方法无区别
答案:ABC。实时性决定你是否能用较慢的 STOMP;能否获得解析梯度决定 CHOMP 是否适用;约束的类型和数量影响 TrajOpt 的凸近似难度。三种方法在搜索策略、收敛速度和适用场景上差异明显,不能认为无区别。