做过机器人轨迹规划的人,应该都有过这种经历:让机械臂沿一个矩形路径走,到了拐角不降速,末端就会明显“抽”一下,轻则工件表面留下刀痕,重则直接触发加速度报警。很多人会用控制器自带的拐角过渡功能,但默认的zone/CNT方案大多只是“圆角+减速”,真正把机器人空间平滑过渡做成高阶曲线、让速度曲线和加速度曲线都连续的做法,其实并不多。这篇文章想聊的就是我一直在用的方案:6点5次贝塞尔曲线,专门用来生成连接两段路径的过渡段。我会从为什么选5次、控制点怎么求、插补时有哪些坑、实测中怎么调这几个角度,把整套思路还原出来,给做运动控制、机器人集成调试,或者正在研究轨迹规划的朋友做个参考。
1. 从一段矩形路径讲起:拐角为什么不敢不降速
1.1 最原始的处理方式:到拐角先刹停
先回到最基本的问题。机械臂沿着一组示教点走,如果控制器不做任何过渡处理,就意味着它必须在每个拐角点达到速度为零,然后换个方向重新加速。这种做法的好处是简单可靠,路径精度完全贴合示教点,坏处也很明显:每过一个拐角就是一次“刹车—转向—起步”,节拍被拖得很难看。
假设一台机器人走一个300mm × 300mm的矩形,目标TCP速度500mm/s,如果四个角都要刹停再起步,光加减速过程就要花掉大量时间。对这种场景,很多人的第一反应是“那我用圆弧过渡不就行了?”确实,圆弧过渡可以让机器人不刹停直接拐过去,但真正调到高速时你会发现,圆弧方案并没有从根本上解决冲击问题。
1.2 圆弧过渡的天花板:切向连续不等于加速度连续
圆弧过渡的经典做法是:在两条直线段的交点附近取出两个切点,用一段圆弧把它们连起来。几何上,圆弧和前后直线段在切点处切线方向一致,所以位置连续、切线连续都满足,也就是常说的C0和C1连续。但问题在于,机器人实际运动的速度和加速度不是只看几何,还要看时间参数化。
如果你的规划器让机器人在直线段上跑匀速500mm/s,进入圆弧段后,切向速度虽然大小不变,但方向开始连续变化,这时会产生向心加速度。圆弧的曲率半径一旦取得太小,向心加速度就可能超出机械臂的允许范围;而如果为了安全把过渡半径取得很大,轮廓偏差又会超标。更麻烦的是,在圆弧的两个切点处,加速度从“零”突然变成“向心加速度”,这个瞬间的加速度跳变,会让机械臂硬件感受到一次冲击。
我把这种现象称为“天花板”:圆弧过渡只能保证几何平滑,很难保证运动学平滑。想要在拐角处既不掉速太多、又不产生冲击,需要的不只是“把角磨圆”,而是要构造一条在两个端点都能同时约束位置、速度、加速度的过渡曲线。
1.3 平滑的实质:想消除冲击,就得提高连续性阶数
先说结论,再解释原理。我们评估两段轨迹拼接得好不好,通常会看三个连续性指标:
- C0连续:位置连续,轨迹不会断开;
- C1连续:速度连续,端点处速度方向一致,不会“折”;
- C2连续:加速度连续,端点处加速度也一致,不会“顶”一下。
如果用生活经验类比,C0连续相当于开车压过一个“坎”,C1连续相当于方向盘突然猛打一下,C2连续相当于方向盘是逐渐打过去的。对工业机器人来说,绝大多数抖动问题都出在加速度不连续,也就是只做到C1、没有做到C2。
而5次贝塞尔曲线之所以在这种场景里特别有价值,就是因为它能同时锁定端点的位置、速度和加速度,让过渡段和前后直线段在拼接点上实现完整的C2连续。这不是玄学,而是由它的数学结构决定的,接下来我把这块拆开讲。
2. 6个控制点、5次多项式的数学必然性
2.1 五次多项式为什么是“刚好够用”的那一档
先看多项式本身。一条五次多项式曲线可以写成:
B(u) = a5*u^5 + a4*u^4 + a3*u^3 + a2*u^2 + a1*u + a0参数u从0到1,一共有6个待定系数。现在看一下我们需要多少个边界条件:
- 在u=0(过渡段起点),需要位置q0、速度v0、加速度a0;
- 在u=1(过渡段终点),需要位置q1、速度v1、加速度a1。
加起来正好6个条件。6个方程解6个未知数,五个系数全部落定。这就是“5次”的数学必然性:它是能同时约束位置、速度、加速度的最低次数。三次多项式只有4个系数,只能约束位置和速度,端点加速度没法控制;七次多项式有8个系数,就算约束jerk(加加速度)都绰绰有余,但后面会说到,盲目提高次数反而会带来新的问题。
2.2 换成贝塞尔形式:每个控制点都有运动学含义
五次贝塞尔曲线有6个控制点P0到P5,方程形式是:
B(u) = C(5,0)*(1-u)^5*P0 + C(5,1)*(1-u)^4*u*P1 + C(5,2)*(1-u)^3*u^2*P2 + C(5,3)*(1-u)^2*u^3*P3 + C(5,4)*(1-u)*u^4*P4 + C(5,5)*u^5*P5这看起来只是一串系数,但贝塞尔曲线有一个非常漂亮的特性:端点处的导数可以由相邻控制点直接算出来。对5次贝塞尔:
B(0) = P0 B(1) = P5 B'(0) = 5*(P1 - P0) B'(1) = 5*(P5 - P4) B''(0) = 20*(P2 - 2*P1 + P0) B''(1) = 20*(P5 - 2*P4 + P3)这些式子反过来解,就能直接从边界运动状态求出控制点:
P0 = q0 P1 = q0 + v0/5 P2 = q0 + 2*v0/5 + a0/20 P5 = q1 P4 = q1 - v1/5 P3 = q1 - 2*v1/5 + a1/20所以控制点不是随便拖出来的“调形手柄”,每一个都有明确含义:P0和P5定端点位置;P1和P4由端点速度方向决定;P2和P3由端点加速度决定。这也是为什么我特别喜欢这种参数化方式,在工程里调试时,你知道动哪个控制点会影响什么,而不是像某些自由曲线一样全靠试。
2.3 为什么不是3次,也不是7次
我把常用次数做了一个对比,应该能说明问题:
| 次数 | 控制点个数 | 端点可约束的运动量 | 主要短板 |
|---|---|---|---|
| 3次 | 4 | 位置、速度 | 加速度不可控,拼接处仍有冲击 |
| 5次 | 6 | 位置、速度、加速度 | 端点的jerk会有跳变,高刚性场景需注意 |
| 7次 | 8 | 位置、速度、加速度、jerk | 控制点多,容易出现龙格振荡,数值不稳定 |
这里单独说一下高次多项式的问题。很多人觉得次数越高越平滑,实际不是。高阶曲线对控制点极其敏感,某个控制点有少量数值误差,曲线在端点附近可能会产生明显的波浪状摆动,也就是计算数学里说的龙格振荡。对于实时插补系统来说,这会导致轨迹抖动,反而比5次更难调稳。5次贝塞尔处在“约束能力足够”和“数值特性稳定”的甜点区,这是我推荐它的核心原因。
补充一句:5次贝塞尔虽然在端点能做到C2连续,但在端点处的jerk(加加速度)仍然可能不连续。这一点先记着,后面实测调参部分我会专门讲它带来的振动问题。
3. 过渡段生成流程:从原路径到6个控制点
3.1 先在原路径上把过渡区间定出来
有了公式,第二步是在原始路径上选出一段“要被替换掉”的区域。假设现在有两条直线段,交于拐角点O,目标是用一段贝塞尔曲线替换掉靠近O的一小段。
实际操作中,我会在进入拐角的直线段上选一个点P_start,在离开拐角的直线段上选一个点P_end,P_start和P_end离拐角点的距离称为过渡距离d。曲线生成后,机器人实际走的路径就是从P_start开始脱离原直线,到P_end重新贴回原直线。这样做的意思是:它天然保证了过渡段起点和终点的位置都落在原路径上,和前后直线段拼接时只有速度、加速度需要额外对齐。
过渡距离d怎么定?有两个约束要同时满足。第一个是轮廓偏差。过渡段越贴近拐角,路径越“紧”,但允许通过的速度上限也越低。工程上可以先用一个粗略公式估算初始值:如果两段直线的夹角为θ,允许的最大轮廓偏差为ε,那么过渡距离近似为d ≈ ε / sin(θ/2)。这只是几何初值,实际还要考虑机器人动力学约束,后面我会再讲校验方法。
3.2 从运动状态求解控制点:可以直接抄的代码
这一节直接给可以用的Python代码。设过渡段起点位置为q0,终点位置为q1,起点速度、加速度为v0、a0,终点速度、加速度为v1、a1,六个控制点可以这样求:
from math import comb import numpy as np def solve_control_points(q0, v0, a0, q1, v1, a1): """ 根据边界运动状态求解6个控制点 q: 位置,v: 速度,a: 加速度 坐标单位要保持一致,比如都用mm和mm/s """ q0 = np.array(q0, dtype=float) v0 = np.array(v0, dtype=float) a0 = np.array(a0, dtype=float) q1 = np.array(q1, dtype=float) v1 = np.array(v1, dtype=float) a1 = np.array(a1, dtype=float) P0 = q0 P1 = q0 + v0 / 5.0 P2 = q0 + 2.0 * v0 / 5.0 + a0 / 20.0 P5 = q1 P4 = q1 - v1 / 5.0 P3 = q1 - 2.0 * v1 / 5.0 + a1 / 20.0 return np.array([P0, P1, P2, P3, P4, P5]) def bezier5(P, u): """根据控制点和参数u(0~1)求贝塞尔曲线上的点""" n = 5 result = np.zeros_like(P[0]) for i in range(n + 1): result += comb(n, i) * (1 - u) ** (n - i) * u ** i * P[i] return result给一个具体的数字例子。假设机器人以500mm/s沿X方向进入过渡段,在终点处速度方向偏转45度,速度大小还是500mm/s,起终点加速度都为0(前后都是匀速直线段)。起点q0取(0, 0, 0),终点q1取(80, 80, 0),那么v0 = (500, 0, 0),v1 = (353.55, 353.55, 0),计算出的控制点大概是:
| 控制点 | 坐标 (mm) |
|---|---|
| P0 | (0, 0, 0) |
| P1 | (100, 0, 0) |
| P2 | (200, 0, 0) |
| P3 | (-61.42, -61.42, 0) |
| P4 | (9.29, 9.29, 0) |
| P5 | (80, 80, 0) |
第一次看到这个结果的人通常会问:P3怎么跑到负坐标去了?离终点那么远,是不是算错了?其实没算错。P3是终点侧的“加速度控制点”,它的位置由末端速度方向反向推导出来,不一定落在路径附近。这种现象恰恰是5次贝塞尔为了满足端点切向条件所必需的。理解这一点之后,调参会容易很多,否则很容易把正确结果当成bug去改。
3.3 最容易犯的错误:控制点“扭头”和单位混乱
我见过不少初学贝塞尔过渡的人,在这里翻车。
第一个坑是控制点“扭头”。有些人拿到前后直线段上的几个采样点,直接把它们填进P1、P4甚至P2、P3,想着“反正控制点会拉出一条曲线来”。结果生成出来的曲线在起点先往反方向退一段,再拐向终点,看起来就像先倒车再转弯。原因很简单:位置控制点和导数控制点语义完全不同。P1必须等于P0加上v0方向上的一个偏移,P4必须等于P5减去v1方向上的一个偏移,而不是随便一个原路径上的点。用上一节公式计算,天然就能避开这个坑。
第二个坑是单位。P1 = q0 + v0/5这个式子,当坐标单位是毫米、速度单位是毫米每秒的时候,P1到P0的距离直接等于“5毫秒内走过的距离”。如果速度用了m/s,坐标用了mm,那曲线长度会直接缩水一个量级,机器人实际走的路径会严重偏离预期。所以在处理数据时务必保证位置、速度、加速度三者的单位体系一致。
4. 从几何曲线到真实运动:参数化与插补
4.1 重要认知:贝塞尔参数u不是时间
这一步是几何曲线变成真实运动的关键,很多人会在这里卡住。贝塞尔曲线的参数u只描述曲线形状:u从0均匀变到1,对应的并不是路径上的均匀位移,更不是时间上的均匀变化。如果你的控制器直接每隔固定时间间隔取一个均匀的u值去插补,末端速度会出现明显的波动。
正确的做法是做弧长参数化。步骤如下:
- 在u轴上取一组密集的采样点,比如200个,均匀分布在[0,1]区间;
- 用贝塞尔曲线公式求出这200个点,然后累加相邻点之间的距离,得到累计弧长s;
- 建立一张(u, s)映射表;
- 插补时,先由速度规划器给出当前时刻应走的弧长s(t),再在映射表里反查对应的u,最后重新代入贝塞尔公式取点。
这个过程本质上是在回答“走到第几厘米时,参数u是多少”这个问题。对绝大多数工业应用来说,用累计弦长代替真实弧长已经足够精确,因为过渡段内部曲率变化不会特别极端。只有当机器人的重复定位精度小于0.02mm,或者过渡段内部出现很大曲率时,才需要考虑用数值积分做更精确的弧长计算。
4.2 和S型速度规划对接:推荐逐段处理再校验
弧长参数化之后,过渡段就变成了一条有明确长度的曲线。接下来要解决的是速度规划怎么和它衔接。
这里有两种做法。第一种是把整条路径(直线段+过渡段)当作一条连续曲线,算出总弧长L,然后对整个L做S型速度规划,生成一条s(t)曲线。听起来很完美,但工程实现比较复杂,尤其是多条直线段和多个过渡段拼接时,计算量和边界处理的复杂度都会上升。
第二种更实用:把过渡段当成一个独立的小段,规划器预先算出过渡段允许的通过速度,然后让前后直线段在这个速度下“接进来”。具体操作是,在生成本节控制点之前,先根据过渡段的曲率分布和各轴速度加速度限制,倒推出过渡段的v_max;然后把这个v_max作为前后直线段的目标速度。这样每个过渡段端点上的v0和v1都是已知常量,控制点可以直接求,S型规划器也只需要按“直线段速度—过渡段速度—直线段速度”做一次前瞻就行。
如果你需要快速估算过渡段允许速度,可以用向心加速度的经典公式:先对贝塞尔曲线做一阶导、二阶导,计算曲率最大值κ_max,然后v_max ≈ sqrt(a_centripetal_max / κ_max)。需要说明的是,这只是初值,真实关节约束还要通过逆运动学逐点校验,这个放到下一章讲。
4.3 姿态部分:不要拿欧拉角直接做贝塞尔
到这一步,位置已经可以平滑过渡了,但还有一个容易忽略的问题:姿态。六维空间的机器人运动不仅包含TCP位置,还包含工具姿态。很多人处理姿态时直接把欧拉角当成三个分量,和位置一起塞进控制点,生成一条六维贝塞尔,这么做会出两个问题。
一是万向锁。欧拉角在某些角度附近会发生奇异性,插补结果会突然产生姿态跳变。二是欧拉角的数值空间不是度量三维旋转的自然空间,三个角度线性插值出来的中间姿态往往会“绕远路”,看起来就是在空中画了一个不自然的弧线。
实践经验是,位置和姿态要分开处理:
- 位置部分:使用本文的6点5次贝塞尔;
- 姿态部分:如果转角较小,可以先对姿态误差取对数映射成旋转矢量,对旋转矢量做5次贝塞尔插值,再指数映射回旋转矩阵或四元数;
- 如果转角较大或者要求连续多段平滑,建议用四元数Squad插值,它天然适合单位四元数流形上的平滑过渡。
我见过不少视觉引导项目,位置平滑做得很好,但姿态没有处理,末端工具在过渡段里猛地一偏,在工件表面拉出一道斜槽。排查这个问题有个简单办法:在仿真环境里给TCP画出一个坐标系,然后观察过渡段内坐标轴三个方向的角速度曲线,如果姿态角速度曲线出现尖峰,就说明姿态插值方式需要改进。
5. 实测中跑不稳的三个典型症状:振动、超差、掉速
5.1 过渡段两端有规律振动:问题多半出在jerk跳变
6点5次贝塞尔保证了端点C2连续,但前面提过,端点的jerk(加加速度)仍然可能不连续。在刚性较差、负载较重的设备上,这个jerk跳变足以激励起机械共振。
如何在现场判断这个问题?我常用的办法是录波形。让机器人以同一程序重复跑某个过渡段,同时抓取关节电机电流或者末端加速度计信号。如果波形在每个过渡段的入口或出口附近都出现固定位置的尖峰,那基本可以断定是轨迹曲线本身的问题;如果是机械共振,尖峰应该是固定频率的,和位置没有这种对应关系。
针对这类问题的处理办法有三个,按优先级排列:
- 降低过渡段的端点速度,给jerk留出缓冲空间;
- 如果设备负载大,工艺又要求高速度,可以考虑只在问题轴或关键段升到7次贝塞尔,显式约束端点jerk;
- 在控制器里加入加速度前馈补偿,减小跟踪误差带来的冲击。
我个人的原则是:能通过调整过渡半径和速度解决,就不要轻易上7次,否则调试成本会明显增加。
5.2 轮廓偏差超预期:过渡距离不能拍脑袋定
过渡距离d设得越大,速度可以跑得越高,但路径偏离原始拐角的量也越大。很多工艺对路径偏差有明确限制,比如涂胶轨迹、焊接轨迹,偏出0.5mm可能就算不合格。
一个实用的做法是把原始路径和生成后的贝塞尔过渡段都离散成点云,然后计算原路径点到贝塞尔曲线的最近距离,取出最大值作为最大偏差。这个值必须进入设计闭环。实际调参的时候,过渡距离往往是“先在纸上算一个初值,再在线验证一版,再微调”的循环过程。
经验规律是:过渡段离拐角越远,最大偏移越大;两段直线的夹角越尖锐,相同过渡距离下的偏移也越大。比如90度拐角下,过渡距离从30mm提到100mm,最大偏差可能会从2mm量级涨到5mm量级。所以不要问“过渡距离设50mm行不行”,必须结合允许偏差和机器人速度一起定。
5.3 笛卡尔速度没超,关节却报速度超限
还有一种很隐蔽的情况:过渡段的TCP速度明明没有超过设定值,但运行时某个关节报速度超限错误。原因是笛卡尔速度恒定不代表关节速度恒定。尤其在过渡段姿态变化快、或者路径靠近奇异位形时,雅可比矩阵会放大关节速度。
处理办法是在生成过渡段后、正式下载到控制器之前,做一次离线逆解校验:
- 沿贝塞尔曲线采样,每隔几毫米取一个点;
- 对每个点做逆运动学,得到六个关节角;
- 对关节角序列做数值微分,检查角速度和角加速度是否在允许范围内;
- 如果超限,优先增大过渡半径,其次降低过渡段通过速度,最后才考虑修改过渡段形状。
这里要特别提醒:不建议在实时插补循环里做临时限速,那种做法虽然能避免报警,但会引入新的加速度尖峰,反而加剧抖动。离线算好边界,是一条更稳妥的路。
6. 商业控制器里的内置过渡用什么,以及多拐角过渡怎么扩展
6.1 常见控制器的zone/CNT方案,本质是什么
接触过几类主流商业控制器的人应该知道,ABB有zone参数,发那科有CNT参数,本质上都是让机器人在到达示教点之前提前转弯。它们内部实现的过渡算法,很大一部分是抛物线混合或者圆弧过渡再加速度重规划。
这类方案的优点是参数少、实时性好、调试门槛低,非常适合产线工人快速调轨迹。缺点也明显:一是连续性通常只能保障到C1,在高速大负载场景下仍会产生冲击;二是轮廓偏差和速度上限之间的关系不够透明,调节起来基本靠经验试。对于自研运动控制器、离线编程工具或者对节拍和精度有极致要求的项目,用6点5次贝塞尔替代内置zone过渡,是可以明显提升运动质量的。
6.2 连续多拐角的过渡:关键是要做速度前瞻
前面讲的都是单个拐角的过渡,但一条实际路径往往有连续好几个拐角。如果每个拐角独立生成过渡段,问题就来了:第一个过渡段的终点速度方向和大小,可能和第二个过渡段的起点要求对不上。解决这个问题的最好方式,是先把整条路径的拐角全部识别出来,做一次速度前瞻规划。
我目前的做法可以归纳成五步:
- 预扫描整条路径,识别所有拐角,并记录相邻拐角之间直线段的长度;
- 基于各关节的速度、加速度限制,为每个拐角分配一个可行的通过速度;
- 根据每个拐角允许的通过速度、角度变化和轮廓偏差要求,逐段计算过渡区间长度和控制点;
- 把所有直线段和过渡段拼接成一条完整路径,做统一的弧长参数化;
- 进入实时插补,按规划好的s(t)取点执行。
第2步是整个方案的核心。如果跳过它,每个拐角单独生成的曲线在拼接处可能会出现速度不匹配,原本做的平滑工作就白费了。
6.3 两个控制点微调技巧
最后分享两个调参技巧,都是我从实际项目里攒出来的。
第一个技巧:如果要微调过渡段形状,优先动P2和P3,不要动P1和P4。P1和P4负责端点速度方向,改了它们等于改了和相邻直线段的衔接条件;P2和P3只影响起始加速度和结束加速度向曲线内部的延伸方式,动它们不会破坏C2连续性。实际微调时,P2、P3沿某一方向偏移的量控制在两端点距离的5%以内,一般不会引起曲线中段的异常波动。
第二个技巧:如果想让过渡更快“贴回”原直线,可以适当减小过渡段两端的速度方向变化率,也就是让P1更靠近P0、P4更靠近P5。这会让曲线在端点附近更快转向,但要注意,这同时会增大过渡段中部的曲率峰值,所以需要同步校验关节速度。我一般会在调试记录里记下控制点坐标、最大偏差、最大关节速度这三组数据,方便多组方案横向对比。
这套6点5次贝塞尔方案,我最早是在一个三轴平台上跑通的,后来才逐步移植到六轴机械臂上。回头看,最花时间的从来不是公式本身,而是单位、坐标系和姿态处理这些容易被忽略的细节。如果你正准备在自己的控制器里实现类似功能,我的建议是先在小负载低速设备上把公式链路验证一遍,把控制点坐标打印出来,对照公式手动核算一次,确认没有单位问题;再配上示波器看速度曲线和电流波形,确认过渡段边界没有尖峰。等这两步都稳了,再上高速产线,能省下大量排查时间。