上个月调一套直角坐标机械臂的视觉抓取程序,抓取轨迹是一条折线:先沿X方向进,再转Y方向出。第一版轨迹直接在拐点处拼接两个直线段,看起来也没什么问题。结果一跑起来,末端在拐角处明显顿了一下,电机还嗡嗡响。查了很久,不是伺服参数没调好,也不是负载惯量算错,真正的原因很简单:轨迹生成只保证了位置连续,速度方向却在拐点处瞬间突变。
这个问题的标准解法,就是给轨迹做空间平滑过渡。而我这次用的方案,是6点5次贝塞尔曲线。说白了,就是用一条5次贝塞尔曲线,把原来那个尖角过渡成一条光滑曲线,让机器人在拐弯时速度方向能连续变化,加速度也不会突然跳变。这篇文章就把这条曲线的原理、控制点配置、代码实现、参数调整和现场问题排查全部讲清楚。做工业机器人、移动机器人、AGV、数控路径规划的朋友,都可以直接参考。
1. 空间平滑过渡到底解决什么问题
1.1 一段让人头疼的“折线轨迹”
先回到现象本身。我们常见的机器人路径,很多都是折线:视觉引导抓取时的进出段、码垛路径的层间切换、AGV地图里的直角弯,本质上都是一堆直线段首尾相接。
如果对这些直线段不做任何处理,直接让机器人沿折线跑,从运动学角度看会出什么事?我们来拆一下。
第一,位置是连续的。机器人肯定能从一个点走到下一个点,不会凭空跳过去。第二,速度矢量不连续。假设前一段沿X正方向以200mm/s运动,后一段沿Y正方向也以200mm/s运动,那么在拐点那一瞬间,速度方向从X轴突然变成Y轴。速度大小虽然没变,但方向变了,相当于在拐点处施加了一个无穷大的速度变化率。现实中的电机当然无法承受无穷大的加速度,所以控制器会强制减速,等机器人差不多停在拐点附近,再重新加速往新方向走。这就是我们看到的“顿一下”。
用数学语言说,这样的拼接轨迹只在连接点处做到了C0连续,也就是位置连续,但C1(速度)都不连续。对机器人来说,C0连续是底线,C1连续是及格线,C2连续(加速度连续)才是大多数场景下真正能平稳运行的要求。再往上还有C3连续(加加速度连续),那是高速高精加工才追求的。
1.2 平滑过渡的数学本质
很多人一听到“平滑过渡”就以为是“加个圆弧”,其实圆弧只是一种特例。平滑过渡的数学本质,是把原来连接点处的速度方向突变,变成一段有限时间内的连续变化,同时最好让加速度也连续变化。
机器人轨迹的连续性等级和运动冲击的关系,可以这样看:
| 连续性等级 | 数学含义 | 运动学表现 | 实际影响 |
|---|---|---|---|
| C0 | 位置连续 | 轨迹无断点 | 最基本要求,但可能出现折角 |
| C1 | 速度连续 | 速度大小和方向无突变 | 无明显停顿,但加速度可能突变 |
| C2 | 加速度连续 | 加速度无跳变 | 惯性力连续,机械冲击显著降低 |
| C3 | 加加速度连续 | 加加速度无跳变 | 适合高速高精应用,轨迹更柔和 |
对大多数工业机器人场景,做到C2连续已经能解决绝大多数的抖动、顿挫、异响问题。C3以上当然更好,但实现成本和调参难度都会上升,而且对机械本体刚度不够好的设备,C2和C3的区别其实很难体感出来。
所以,空间平滑过渡的核心任务就是:在两条轨迹段之间插入一段新轨迹,使得整体在连接点处至少满足C2连续。也就是说,位置、速度、加速度三个量都要在过渡段起点和终点处与前后轨迹段对齐。
1.3 为什么选中“6点5次贝塞尔曲线”
贝塞尔曲线是图形学和轨迹规划里最经典的参数曲线。它的特点是:n次贝塞尔曲线由n+1个控制点定义。所以6个控制点对应的就是5次贝塞尔曲线,这就是标题里“6点5次”的由来。
那为什么不用更低阶的?比如3次贝塞尔曲线只有4个控制点,在端点位置和端点速度都确定的情况下,自由度已经全部用完,没有余量再去约束端点加速度。也就是说,用3次贝塞尔做过渡,能做C1连续,但做不到C2连续。而5次贝塞尔的6个控制点,刚好可以在固定端点位置、端点速度、端点加速度这6个约束之后,把曲线形状唯一的确定下来。如果用更高阶,比如7次、9次,也不是不行,但控制点多了,调参难度和过冲风险都会增加,工程上不划算。
贝塞尔曲线还有一个很重要的性质:凸包性。整条曲线一定落在所有控制点围成的凸包内。这意味着只要控制点摆得离原始轨迹不远,过渡曲线就绝对不会飞出离谱的距离。这个性质在工程上非常有用,后面调轮廓误差的时候就是靠它兜底。
2. 6点5次贝塞尔曲线:控制点怎么摆,原理是什么
2.1 从4点3次到6点5次:多出的两个点用来干什么
先看3次贝塞尔曲线,它由P0、P1、P2、P3四个控制点组成,曲线从P0出发,到P3结束。P0到P1的方向决定了曲线起点的切线方向,P3到P2的方向决定了曲线终点的切线方向。端点处的速度大小,与P1相对P0的距离成正比,具体是3倍的P0P1向量;终点是3倍的P3P2向量。
这带来一个问题:如果你已经确定了P0、P3,并且要求起点速度是某个大小和方向,终点速度是另一个大小和方向,那么P1和P2的位置就已经被锁死了。加速度怎么办?没有自由度去管了。所以3次贝塞尔曲线做两段直线之间的过渡,只能保证速度连续,加速度大概率在端点处跳变。现场表现就是,机器人过拐角不再停顿了,但会在过渡段两端各抖一下。
5次贝塞尔曲线则在P0和P3之间多了P2和P4两个控制点,六个控制点是P0、P1、P2、P3、P4、P5。多出来的这两个点的核心作用,就是用来匹配端点加速度。一个典型的配置方式是:P0、P1、P2三个点控制起点的位置、速度和加速度;P3、P4、P5三个点控制终点的位置、速度和加速度。
2.2 端点速度、加速度与控制点的对应关系
5次贝塞尔曲线的标准形式是:
B(t) = (1-t)^5 * P0 + 5t(1-t)^4 * P1 + 10t^2(1-t)^3 * P2 + 10t^3(1-t)^2 * P3 + 5t^4(1-t) * P4 + t^5 * P5
其中t是曲线参数,取值范围是0到1。对t求一阶导,得到:
B'(t) = 5(1-t)^4 * (P1-P0) + 20t(1-t)^3 * (P2-P1) + 30t^2(1-t)^2 * (P3-P2) + 20t^3(1-t) * (P4-P3) + 5t^4 * (P5-P4)
继续求二阶导:
B''(t) = 20(1-t)^3 * (P2-2P1+P0) + 60t(1-t)^2 * (P3-2P2+P1) + 60t^2(1-t) * (P4-2P3+P2) + 20t^3 * (P5-2P4+P3)
把t=0和t=1代入,会得到几个非常重要的端点性质:
- 位置:B(0) = P0,B(1) = P5
- 一阶导:B'(0) = 5(P1-P0),B'(1) = 5(P5-P4)
- 二阶导:B''(0) = 20(P2-2P1+P0),B''(1) = 20(P3-2P4+P5)
这组公式怎么看?一句话总结:起点的速度由P1相对P0的位置决定,起点的加速度由P0、P1、P2三点的相对位置决定;终点对称。
特别注意第二个式子:如果P2-2P1+P0 = 0,也就是P1正好是P0和P2的中点,那么起点加速度就是0。反过来看,当P0、P1、P2三个点等距排列在一条直线上时,曲线在起点的加速度为0,同时在起点处速度方向也正好沿着这三个点所在的直线。这对直线段过渡来说,是再理想不过的边界条件了。
因为实际机器人轨迹是有时间概念的,贝塞尔曲线本身只定义了路径形状,并不包含速度规划。假设实际时间τ从0到T,轨迹写成Q(τ) = B(τ/T),那么实际端点速度就不是B'(0),而是B'(0)/T。实际端点加速度也不是B''(0),而是B''(0)/T^2。把比例系数换算过去,用真实速度v和真实加速度a来表示,控制点公式就变成:
- 起点端:P1 = P0 + v0 * T / 5,P2 = 2P1 - P0 + a0 * T^2 / 20
- 终点端:P4 = P5 - v1 * T / 5,P3 = 2P4 - P5 + a1 * T^2 / 20
这里的v0、a0是过渡段起点的实际速度和加速度,v1、a1是终点的实际速度和加速度。这组公式是通用的,不管前后轨迹段是直线还是圆弧,只要能给出端点速度和加速度,就能算出控制点。
2.3 直线-直线过渡的控制点配置公式
最常见的机器人拐角场景,是两条直线段拼接。比如从A点直线走到B点,再直线走到C点,B是拐点。做平滑过渡时,要保证过渡段起点P0在前一段直线AB上,终点P5在后一段直线BC上。
先定义几个量:u是AB方向的单位向量,w是BC方向的单位向量,v是机器人沿直线段运动的速度大小,T是过渡段总时间,d是P0到B点的距离,同时也是P5到B点的距离。在对称设计下,P0和P5离B点的距离相同。
控制点的摆法如下:
P0 = B - d * u
P1 = P0 + (v*T/5) * u
P2 = P1 + (v*T/5) * u
P5 = B + d * w
P4 = P5 - (v*T/5) * w
P3 = P4 - (v*T/5) * w
看出来了吗?P0、P1、P2三个点在AB直线段上等距排列,P3、P4、P5在BC直线段上等距排列。这样的布置刚好满足起点和终点的加速度都为0,并且端点速度大小都等于v,方向与前后直线段一致。因此过渡段和前后直线段之间,位置连续、速度连续、加速度连续,也就是完整的C2连续。
这里有一个硬性条件:vT/5必须严格小于d。否则P1会越过B点,跑到拐角的另一侧去,控制点分布就不再落在原始路径段上了。工程上一般建议vT/5取值在d的20%到50%之间,这样曲线既能自然弯曲,又不会过于贴近拐角。
3. 实操步骤:从控制点到可执行轨迹
3.1 第一步:提取原始轨迹信息
动手写代码之前,先把过渡需要的信息整理清楚。对于一个直角拐角,需要知道的原始信息有:
- 拐点B的坐标,这是两条直线段的交点
- 进入方向单位向量u,即上一段直线末端指向B的方向
- 离开方向单位向量w,即B指向下一段直线末端的方向
- 直线段上的运动速度v,单位要和坐标一致
- 期望的过渡时间T
- 过渡距离d
这几个量里,方向单位向量通常由路径点序列的差分得到。比如路径点A、B、C是三个三维坐标,u = normalize(B-A),w = normalize(C-B)。注意路径段如果太短,归一化时会出现除零问题,后面会专门讲这个坑。
v可以直接取工艺速度,比如码垛速度200mm/s,或者视觉引导的TCP速度150mm/s。T和d则需要人工根据节拍和轮廓误差要求来定,通常是先估算一个初值,再仿真调优。
3.2 第二步:计算6个控制点
用Python写一个控制点计算函数,非常直接:
import numpy as np def build_transition_control_points(B, u, w, speed, T, d): step = speed * T / 5.0 if step >= d: raise ValueError("speed*T/5 必须小于 d,请减小速度或过渡时间,或增大过渡距离") P0 = B - d * u P1 = P0 + step * u P2 = P1 + step * u P5 = B + d * w P4 = P5 - step * w P3 = P4 - step * w return np.array([P0, P1, P2, P3, P4, P5])这里的B、u、w都是numpy数组,支持二维或三维坐标。speed是速度大小,T是过渡总时间,d是过渡距离。
实际算一个例子。假设路径是A(0,0)经过B(100,100)到达C(200,0),这是一个90度折角的路径。取速度200mm/s,过渡时间0.25秒,过渡距离40mm。
u = normalize(B-A) = (0.7071, 0.7071),w = normalize(C-B) = (0.7071, -0.7071)。
speed * T / 5 = 200 * 0.25 / 5 = 10mm。这个值是10,小于d的40mm,满足条件。
逐点计算:
P0 = (100,100) - 40*(0.7071,0.7071) = (71.7157, 71.7157)
P1 = (71.7157,71.7157) + 10*(0.7071,0.7071) = (78.7868, 78.7868)
P2 = (78.7868,78.7868) + 10*(0.7071,0.7071) = (85.8579, 85.8579)
P5 = (100,100) + 40*(0.7071,-0.7071) = (128.2843, 71.7157)
P4 = (128.2843,71.7157) - 10*(0.7071,-0.7071) = (121.2132, 78.7868)
P3 = (121.2132,78.7868) - 10*(0.7071,-0.7071) = (114.1421, 85.8579)
可以看到,六个控制点分两组,各自由等距点组成,一组在AB线段方向上,一组在BC线段方向上。把这条曲线画出来,会发现它从AB段上某个点平滑地转到BC段上某个点,拐角处不再有尖角。
3.3 第三步:采样生成轨迹点
控制点拿到之后,下一步就是按照实际控制周期采样。5次贝塞尔曲线的求值直接用标准公式即可:
def eval_bezier5(ctrl, t): P0, P1, P2, P3, P4, P5 = ctrl mt = 1.0 - t return (mt**5)*P0 + 5*t*(mt**4)*P1 + 10*(t**2)*(mt**3)*P2 + \ 10*(t**3)*(mt**2)*P3 + 5*(t**4)*mt*P4 + (t**5)*P5如果还想看速度、加速度曲线,就对t求导:
def eval_bezier5_vel(ctrl, t): P0, P1, P2, P3, P4, P5 = ctrl mt = 1.0 - t return 5*(mt**4)*(P1-P0) + 20*t*(mt**3)*(P2-P1) + \ 30*(t**2)*(mt**2)*(P3-P2) + 20*(t**3)*mt*(P4-P3) + 5*(t**4)*(P5-P4)注意,这两个函数返回的都是曲线参数t下的位置和导数。如果采样时t是等间隔的0到1,并且假设实际时间τ从0均匀增加到T,那么实际速度要除以T,实际加速度要除以T^2。这一点非常容易搞错,很多人直接在控制器里用等间隔t发送位置,结果发现实际速度完全对不上理论值。
对多数控制器来说,位置指令是按固定周期下发的。假设周期是4ms,过渡总时间T是0.25秒,那么采样点数就是0.25/0.004=62.5,取63个点。每个点的实际时间τ对应到一个t,t = τ/T。然后把对应的B(t)位置发给控制器。如果T取得不合适,和控制器周期不是整数倍关系,插补点会多一个或少一个,这个问题可以直接通过计算采样点数的取整来处理。
3.4 第四步:仿真验证连续性
控制点配置和采样都做完之后,不要急着上机,先在仿真里看三条曲线:位置曲线、速度曲线、加速度曲线。位置曲线平滑只能说明样子好看,真正反映运动平稳性的,是速度和加速度曲线。
检验点有这么几个:
- 过渡段起点和终点的速度,是否等于前后直线段的速度。如果不等,说明控制点计算和实际速度不匹配。
- 加速度曲线在起点和终点是否连续。如果端点加速度不等于前后直线段的加速度,那过渡段和直线段拼接处还是会抖。
- 加速度曲线内部有没有尖峰。贝塞尔曲线本身是光滑的,但如果控制点布局太差,加速度曲线会出现非常大的峰值,导致电机过流。
在我这个例子里,直线段运动时加速度为0,过渡段起点和终点加速度也都是0,所以加速度曲线会从0平滑地变化再回到0,中间没有跳变。这种曲线放到控制器上跑,机械冲击会比折线拼接小很多。
4. 关键参数怎么调:过渡距离、时间与轮廓误差
4.1 过渡距离d:平滑性和贴线性的天平
过渡距离d是P0点到拐点B的距离。这个参数直观上决定了过渡曲线距离原始路径“有多远”。
d越大,过渡段越长,曲线在拐角处的曲率越小,机器人转弯越从容,加速度峰值越低,运动越平稳。但代价是,曲线偏离原始路径的距离也越大。如果机器人的工作空间里正好有障碍物,或者视觉系统要求末端严格经过某个定位点,过渡距离太大就可能撞东西或者定位偏差超标。
d越小,曲线越贴近原始折线,轮廓误差小,但转弯越急促,加速度峰值高,电机负载大。实际调参时,我一般先用路径段长度的5%~15%作为初值。比如一段500mm的直线段,d取25mm到75mm左右。然后再根据加速度峰值和轮廓误差去微调。
4.2 过渡时间T与速度匹配
过渡时间T是一个容易被忽视但极其重要的参数。它直接决定了过渡段的实际速度。回到控制点公式:P1相对P0的偏移量是vT/5。如果T太小,P1、P2会非常靠近P0,曲线起点处速度方向虽然还是沿AB方向,但整段过渡会非常急促,等效于在很短时间内完成转弯,加速度自然就大。如果T太大,速度匹配出问题,甚至会让vT/5超过d,直接报错。
T的初值可以用期望的过渡段平均速度来估。简单的方法是:让T大约等于2d/v左右,也就是大致让机器人以和直线段差不多的速度走完这段过渡。比如d=40mm,v=200mm/s,T可以取0.4秒左右。这样v*T/5=16mm,小于d,同时P1点还算靠近P0,曲线不会甩得太远。
如果工艺上对节拍敏感,可以适当减小T,但要注意加速度峰值。估算加速度峰值有个粗糙的办法:把过渡段近似看成一个圆弧,半径约为d乘以某个比例系数,向心加速度约等于v^2/r。如果算出来加速度超过设备允许值,就说明T太小或者d太小,得往回调。
4.3 如何估算轮廓误差
轮廓误差是说过渡曲线偏离原始折线多远。贝塞尔曲线的凸包性质给我们一个上界:过渡曲线一定在所有控制点的凸包内。因此,控制点离原始路径越近,轮廓误差上界越小。
但实际最大误差点一般在曲线中点附近。对于对称布置的六点控制,最大误差大致发生在t=0.5附近。仿真时可以直接取t从0到1扫一遍,计算每个采样点到直线段AB或BC的最小距离,再取最大值。
如果轮廓误差超差,有两条路可以走。第一条,减小d,这是最直接的。第二条,调整step相对d的比例,也就是改变v*T/5和d的关系。step越接近d,控制点越靠近B点,曲线越“贴角”,轮廓误差越小,但转弯越急促。step越小,控制点离B越远,曲线越圆弧化,轮廓误差越大。实际调节时要反复权衡。
4.4 不同场景的推荐参数区间
不同机器人应用,对过渡参数的要求差别很大:
| 应用场景 | 过渡距离d | 过渡时间T | 主要关注点 |
|---|---|---|---|
| 工业机械臂搬运 | 5~50mm | 0.1~0.4s | TCP速度稳定,节拍 |
| 移动机器人AGV | 0.1~1m | 0.5~2s | 最小转弯半径,乘客舒适度 |
| 数控加工 | 0.05~0.5mm | 毫秒级 | 轮廓误差,进给速度 |
| 视觉引导抓取 | 几mm~几十mm | 0.1~0.3s | 定位精度,视觉拍照时序 |
数控加工对轮廓误差要求极其严苛,通常不会用单纯贝塞尔过渡,而是用NURBS样条。贝塞尔曲线更适合对轮廓要求不是极致精密的机器人运动场景。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 过渡段起点和终点速度不匹配 | 控制点step算错,或T取得不对 | 检查v*T/5是否与P1P0距离一致 |
| 过渡段轮廓误差偏大 | d太大,或step相对d太小 | 减小d,或增大step比例 |
| 过渡段末端仍抖动 | 端点加速度不匹配,前一段不是匀速 | 用通用公式代入a0、a1,而不是简单等距 |
| 控制器上轨迹速度与仿真不一致 | 采样t映射到时间时没除以T | 检查实际速度是否等于B'(t)/T |
| 速度提前减速,节拍变慢 | 对速度方向变化预估过于保守 | 放宽速度限制,优化过渡参数 |
| 实时生成时偶发位置跳变 | 相反方向控制点越过拐点B | 检查v*T/5 < d是否恒成立 |
5.2 一个真实排查案例:为什么过渡段末端总会抖一下
有次调试六轴机械臂,过渡段起点很平稳,速度曲线也光滑,但就是在过渡段快结束时,机械臂末端明显抖一下。位置曲线看不出任何问题,但加速度曲线在末端有一个微小的尖峰。
排查过程是这样的:先看控制点,终点端的P3、P4、P5看起来都落在BC直线方向上,似乎没问题。再看速度匹配,发现实际控制器是按固定插补周期发位置指令的,而我把t映射成时间时,想当然地用了等间隔t,没有做弧长参数化。贝塞尔曲线本身参数速度和实际弧长速度并不严格成正比,尽管曲线从视觉上看很均匀,参数速度在接近终点时已经有了偏差,最终导致实际速度在末端有一个很小的波动。
解决办法是在上位机先做一次弧长参数化,也就是把曲线采样成密集点序列,计算累计弧长,再按期望速度把累计弧长映射到t上。经过这一步之后,加速度曲线末端的尖峰消失了。这个案例说明,位置曲线再好看,也不代表速度曲线和加速度曲线没问题。调运动平稳性,一定要把速度曲线和加速度曲线画出来看。
5.3 在线实时生成时的几个坑
如果过渡段不是离线算好,而是在机器人运行过程中实时生成,有几个细节需要特别注意。
第一,方向单位向量归一化时,路径段长度可能很短,甚至接近零,导致u或w出现异常值。在线生成前一定要判断路径段长度,短于一定阈值就跳过过渡处理或改用直线拼接。第二,控制器插补周期是固定的,过渡时间T不一定能被周期整除,采样点数和最后一段位置可能对不上。处理办法是记录累计时间,按实际时间计算t,而不是用固定的索引步长。第三,实时计算时尽量用double类型,避免用float累积误差。过渡距离只有几十毫米的时候,float的精度在大坐标下可能不够用,位置指令会出现肉眼可见的抖动。
还有一个隐蔽的坑:多轴机器人下发位置指令时,如果每个轴走的是独立控制周期,没有统一时间戳,会出现轻微的不同步。运动控制器通常会自己处理这个问题,但如果自己做上位机,要把同一个时间戳下的六个关节角度打包一次性下发,不要分轴发送。
5.4 与控制器内置过渡功能的取舍
很多商业机器人控制器本身带拐角过渡功能,比如ABB的Corner、KUKA的Smooth、FANUC的Corner Zone。这些内置功能用起来很方便,只要设一个半径值就行。那为什么还要自己造轮子?
内置过渡的优点是省事,缺点是参数不透明。你永远不知道它在内部用的是圆弧还是样条,也不知道它实际轮廓误差是多少。对普通搬运、码垛场景,内置过渡完全够用。但如果你需要精确控制轮廓误差、需要C2连续保证、需要在开源ROS控制器里实现自定义轨迹,或者需要把路径结果导出给其他模块做碰撞检测,那自己用贝塞尔曲线生成过渡段就更可控。
我的一般建议是:快速验证用内置功能,精细控制和自己搭建运动系统时,用本文这套方法。二者可以并存,不冲突。
个人体会是,做机器人轨迹平滑这事,核心不在于把算法做得多么花哨,而在于把连续性的概念真正落实到每一个控制点、每一个采样周期上。我现在调试运动系统,第一反应已经不再是调伺服增益,而是先看轨迹的速度曲线和加速度曲线。只要轨迹本身做到了C2连续,大部分现场抖动和异响都会明显减轻。最后分享一个小技巧:上机之前,把过渡段的加速度曲线打印出来贴在示教器旁边,一旦现场异常,先对照曲线检查轨迹参数,再动伺服参数,排查效率会高很多。