ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LeRobot真机部署动作抖动?运动控制平滑优化实战指南

LeRobot真机部署动作抖动?运动控制平滑优化实战指南 前两天有一个玩PiPER的朋友在评论区问我LeRobot的训练流程都跑通了模型在评估集上看着也不错怎么一放到真机上机械臂的动作就抖得像帕金森尤其是接近目标的时候末端总是来回调整成功率直线下降。这个问题我太熟了。第七弹原本想继续写模型评估结果最近正好卡在部署阶段调这个顺手就把“运动控制平滑优化”单独拎出来写一篇也算是一个微小的系列分支。前面六篇已经把PiPER适配LeRobot、数据采集、ACT策略训练、模型部署这些内容都串过一遍了这篇专门处理动作流从模型输出到机械臂执行之间的这一段为什么动作会抖、哪些地方可以做平滑、我最终用了哪些手段、以及调参过程中踩到的一堆坑。先说结论不要一上来就换模型也不要盲目加大滤波强度。大部分抖动问题出在“策略输出频率”“底层控制频率”“训练数据本身的平滑程度”三者错位。把这一层理顺再用轻量级的滤波、重采样和限速手段做一次动作整形就能解决绝大部分问题。这篇内容完全基于我自己的实测经验参数也是从PiPER实机上一点点试出来的不保证在所有环境里通用但思路可以复用。1. 先把问题定性动作抖动不是单一原因而是三层错位叠加的结果1.1 现象模型loss明明很低PiPER的执行却很“神经质”先说我在PiPER上遇到的具体现象。用LeRobot框架训练了一个ACT策略任务是抓取桌面上的小方块。训练结束验证集上loss已经收敛得很漂亮回放录制的演示数据也看不出异常。但一部署到真机上就露馅了机械臂在接近方块的过程中末端会出现频率很高的小幅震荡大概每秒钟有好几次幅度在几毫米到一厘米之间。最明显的是在目标点附近机械臂不是干脆利落地停下来而是先冲过头一点再退回来一点来回两三次才稳住偶尔还会把方块碰倒。如果只看关节角度的实时曲线会发现这些抖动不是单一频率而是混着两种形态一种是类似白噪声的高频小锯齿另一种是像阶梯一样的“平台-跳变-平台”结构。高频小锯齿多见于模型预测出的动作序列在时间维度上不够连续相邻两个动作帧之间可能一下子多出几度阶梯结构则更接近执行层面的问题策略输出动作的频率和底层控制频率没有对齐导致机械臂在两次新目标之间反复执行同一个旧目标。1.2 三层错位策略输出频率、控制周期、数据平滑习惯这类现象背后其实是三层错位叠加的结果。第一层是策略输出频率和PiPER底层控制频率不一致。LeRobot策略在推理时通常以一定频率输出动作常见的是20到50Hz而PiPER这类机械臂的底层力矩/位置控制环通常是100Hz甚至更高。如果策略输出频率是30Hz控制环是100Hz那么在一个30Hz周期内控制环会连续3到4次执行同一个目标位置。目标一直不变机械臂自然处于“等待”状态直到下一个动作帧到达才突然更新目标。这个“突然更新”在数据上就是阶梯跳变当目标位置变化量比较大的时候表现出来的就是冲击和抖动。第二层是模型输出动作本身缺少时间连续性约束。ACT策略会一次性预测未来一段时间的动作块但因为部署时我们往往只取了当前帧的动作或者按固定间隔逐帧消费这个动作块相邻两个时间片的预测结果并不是同一个序列里的连续帧而是模型在重规划时重新生成的新目标。模型预测的小误差在没有连续性约束的情况下会被放大成轨迹的不规则跳变。Diffusion Policy这类方法更明显它的生成过程本身带随机性虽然平均效果不错但单次采样结果可能包含高频噪声。第三层是训练数据本身不够平滑。LeRobot支持通过遥操作采集演示数据如果操作者手不够稳或者采样设备本身有噪声那么模型学到的根本不是“平稳到达目标”这个技能而是包含了人手抖动特征的“伪技能”。这种情况下你无论怎么在部署侧做平滑效果都有限因为模型已经把人手的颤振当作任务的一部分来模仿了。1.3 为什么一开始在仿真评估里没发现还有一个很容易忽略的点我们最初在仿真环境里评估策略成功率很高动作看起来也正常。原因是仿真里的位置控制是理想的你给一个目标位置仿真模型瞬间就过去了不存在真实硬件上的摩擦、阻尼、传动间隙和延迟。LeRobot自带的评估环境又是以“是否到达目标点”为判断依据的它不会惩罚轨迹有多毛糙。所以模型在仿真里表现得再好也不代表真机能平滑执行。这也是我后来建议所有做真机部署的朋友在训练完成之后先把动作流打点记录下来用末端位置曲线去看一看而不是只盯着成功率数字。2. 加一层“动作整形”在推理输出与PiPER控制指令之间做缓冲2.1 接入点主循环里多一行 smoother.step()明确了问题出在模型输出和执行控制之间之后我做的第一件事是在部署代码里加了一个动作整形层而不是去改模型更不是去改PiPER的底层固件。这个层的位置很靠前就在主循环拿到策略输出之后、把目标位置发送给机械臂之前。大致结构是这样obs get_observation() action policy.select_action(obs) # 可能是未来多步的动作块 cmd smoother.step(action, dt1.0 / control_freq) robot.send_target(cmd)smoother就是自己写的一个类负责对动作流做处理然后把平滑后的目标作为PiPER的新控制指令。之所以加在这里是因为改模型意味着重新训练周期太长而且你并不确定模型是不是真的错了改底层控制环的安全风险高而且会对所有控制命令生效影响范围太大在推理后处理阶段做人畜无害的整形可以快速迭代参数一个下午就能试出效果。这个动作整形层虽然代码量不大但它承担的任务不轻。它需要解决三件事把高频噪声滤掉、把低频目标的刷新频率对齐到底层控制周期、把不合理的速度和加速度限制在硬件可接受范围内。这三件事互相有耦合所以不能简单套一个滑动平均就完事。2.2 动作整形层该做什么不该做什么在设计动作整形层之前我先给自己划了一条边界不能过度改变动作的意图。平滑只能处理“执行层面的瑕疵”不能改变模型规划出来的路径目标和时间语义。比如模型决定先向右移动再向下抓取这个行为的顺序和大致轨迹应该被保留平滑只是在每个微小的控制周期里生成一个更合理、更连贯的中间目标。这些是整形层该做的对动作序列做低通滤波削减高频噪声利用前后两个目标做插值生成高频控制周期需要的中间目标对单步位移做限速对相邻两步速度差做限加速度防止硬件因为瞬时冲击而抖动或报警。这些是不该做的不要在这里做复杂的路径规划比如绕过障碍物、重新搜索轨迹那是规划器的事不是平滑器的事不要对动作做大幅度的延迟补偿因为机械臂系统本身的时延很难精确建模补偿过头会变成振荡不要试图用一个滤波器同时处理所有维度的动作因为不同维度的物理意义不一样放在一起处理会互相干扰。我见过有人把卡尔曼滤波、低通滤波、轨迹重规划全塞进这一步结果延迟暴涨机械臂反而更不稳定。这个层应该轻、快、可解释每一行代码都清楚自己是对付哪一种错位的。3. 我最终采用的平滑手段滤波、重采样、限速限加速度3.1 Savitzky-Golay滤波对关节位置流做低通降噪处理动作噪声我第一个想到的并不是普通的滑动平均。滑动平均虽然实现简单但它会削弱真实运动趋势尤其是在抓取这种需要快速接近目标的任务里滑动平均会让末端动作变得迟钝就像隔着一层水在操作机械臂。我最终选的是Savitzky-Golay滤波器。它的思路是在一个滑动窗口内对数据做局部多项式拟合取拟合曲线在当前时刻的值作为输出。和滑动平均相比它能在降噪的同时更好地保留信号的趋势和峰值对机械臂这类需要保持动作“锐度”的场景比滑动平均更适合。在Python里实现非常方便import numpy as np from scipy.signal import savgol_filter class ActionSmoother: def __init__(self, window15, poly3): self.window window self.poly poly self.history [] def step(self, action): self.history.append(action) if len(self.history) self.window: self.history.pop(0) if len(self.history) self.window: return action arr np.array(self.history) return savgol_filter( arr, window_lengthself.window, polyorderself.poly, axis0, modenearest )[-1]这段代码里要注意两个参数window_length和polyorder。window_length决定滤波器看多长一段历史越长越平滑但延迟越大polyorder是多项式阶数太大会跟着噪声跑太小又会削平真实动作。我在PiPER上用下来window_length在9到15之间、polyorder取3是比较稳的起始区间。还有一个容易忽略的点modenearest。SavitZy-Golay默认的边界模式是用多项式外插在动作刚开始的时候会产生明显的甩尾也就是机械臂还没移动目标末端先自己抖一下。改成nearest模式边界处的行为会稳很多。3.2 时间重采样把30Hz的目标流“拆”成100Hz微步滤波处理的是噪声但前面提到的“阶梯结构”还得靠重采样来解决。PiPER的底层控制频率比策略输出频率高我需要让机械臂在每两个动作输出之间收到多个平滑过渡的中间目标而不是一直盯着旧目标。最简单有效的方法就是线性插值。假设策略输出频率是30HzPiPER控制频率是100Hz那么每收到一个新动作我需要在这个动作和上一个动作之间生成大约3个中间目标。代码大致是这样def interpolate_targets(prev_action, next_action, steps): return [ prev_action (next_action - prev_action) * (i / steps) for i in range(1, steps 1) ]这样PiPER在每个控制周期都能拿到一个逼近目标但又不跳变的位置。实际跑下来仅仅加上这一步机械臂运行时的阶梯感就明显减少了。如果你觉得线性插值还是不够柔顺可以换成Catmull-Rom样条它能在插值时考虑前后两个点的一阶导数让目标轨迹保留一定的加速度连续性。但我实测下来在线性插值之后再配合限速效果已经足够而且线性插值代码量小、好调试。对于大多数桌面级抓取任务不必一上来就上复杂样条。3.3 速度与加速度钳位给物理系统留出反应时间滤波和重采样只负责“看起来平滑”但机械臂执行时还要考虑物理极限。模型输出可能突然从一个很小的目标位置跳到一个很远的位置线性插值会让速度瞬间拉满触发电机或减速器的瞬时冲击。这种情况下就算位置曲线是连续的加速度却不连续机械臂一样会抖。所以我加了限速和限加速度两个保险。限速比较容易理解在单个控制周期内任何关节的目标变化量不能超过最大速度乘以周期时间如果超了就把这一步的增量按比例压缩到限制范围内。def clip_velocity(cmd, prev_cmd, dt, max_vel): delta cmd - prev_cmd speed np.abs(delta) / dt if np.any(speed max_vel): scale np.min(max_vel / np.maximum(speed, 1e-6)) delta * scale return prev_cmd delta限加速度稍微复杂一点我在代码里用当前这一步速度与上一步速度之差来判断。如果单位时间内的速度变化超过了设定阈值就逐步逼近目标而不是一次到位。这两个保险的数值设置很关键。PiPER这类机械臂虽然有硬件限位但用户层的速度上限往往要保守一些我通常把最大速度设为硬件标称值的60%到70%最大加速度再减半。这样既能保证执行效率又给机械臂留出足够的反应时间避免频繁触发驱动器保护。4. 调参顺序与实测对比哪些参数先调哪些别乱动4.1 推荐参数表经过几轮实机测试我把不同任务下的推荐参数整理成了表格。参数之间不是独立的所以这份表更多是当起点而不是最终答案任务场景滤波窗口多项式阶数插值步数速度上限比例加速度限制快速推动物块153370%中精准抓取小件93350%低精细插拔动作72430%低大范围转移213280%高这里面的逻辑是任务越精细越不能依赖大窗口滤波因为窗口太大会带来延迟导致机械臂对目标位置的反应跟不上出现“人在现场看它慢慢蹭”的感觉。大范围转移这种对精度要求不高的场景反而可以放开速度限制用较大窗口把路径走得更顺。调参顺序也很重要。我的建议是先做频率对齐和重采样再做限速限加速度最后调滤波器参数。如果顺序反了先把滤波拉到很大你会发现机械臂确实不抖了但反应变慢这时候你很难判断到底是滤波延迟还是限速限制造成的。先把底层执行节奏理顺再一点点加平滑强度每一层的影响都看得清清楚楚。4.2 实测对比抖动指数、成功率和任务耗时为了验证效果我在同一台PiPER上用同一份演示数据训练出的ACT模型分别测了优化前后的指标。每个配置跑20次抓取记录三种数据末端轨迹抖动RMS、抓取成功率、单次任务平均耗时。优化前末端轨迹抖动RMS约0.8mm抓取成功率65%单次平均耗时5.2秒优化后末端轨迹抖动RMS约0.15mm抓取成功率85%单次平均耗时4.8秒。抖动RMS的计算方法是记录机械臂运行过程中末端位置与理想轨迹之间的偏差然后把所有时间点的偏差平方取平均再开方。0.8mm看上去不大但在抓取小方块这个场景里已经足以让手爪在接近目标时不稳定。优化后降到0.15mm机械臂的动作性格完全不一样从“毛手毛脚”变成了“稳准狠”。单次耗时缩短也很符合直觉之前机械臂在目标点附近来回调整白白浪费了不少时间平滑之后它一次到位无效动作少了整体效率反而更高。这说明运动控制平滑优化不只是提升表现质量还能顺带提升任务完成速度。4.3 “平滑过度”是什么体验我也特意试过把平滑调过头的状态好让自己知道边界在哪。当我把滤波窗口拉到25、插值步数加到6之后机械臂确实一点不抖了但动作变得非常“肉”它明明已经接近目标位置却像在黏稠的液体里前进迟迟停不下来抓取方块的时候手爪经常还没完全到位就张开了因为延迟累计下来实际到达位置的时间比模型规划的时间晚了将近半秒。这个体验给我提了个醒平滑优化的最终目标不是让曲线最漂亮而是在“抑制高频抖动”和“保留真实动作的敏捷性”之间找一个平衡点。判断方法也很粗暴看任务中最关键的那几帧比如抓取前的手指开合、插入前的位置逼近在这些时刻动作是否干脆利落。如果关键动作都被平滑拖累了说明参数已经过火了。5. 踩坑记录平滑优化最容易翻车的三个细节5.1 滤波器的边界效应每次启动和停止都会“甩”一下第一次接入Savitzky-Golay滤波之后机械臂在运动启动的瞬间会莫名其妙地小甩一下这个动作不是模型规划出来的完全是我滤波代码引入的。原因是滤波窗口不满时我直接返回了原始动作但一旦窗口满了滤波器开始工作时会突然把历史数据里的趋势引入进来产生一个不该有的初始修正。解决办法有两个我都用上了一是把savgol_filter的边界模式设为nearest避免外插二是在机械臂真正开始执行之前留出几帧“warmup”也就是让动作整形层先跑上若干次空循环把历史缓存填充起来再让机械臂开始动作。这样启动阶段不会出现滤波器突然介入的跳变。回程阶段也一样尽量让任务结束时目标保持稳定一小会儿不要任务完成后立刻切断控制信号。5.2 数据采集阶段不能完全依赖部署后滤波我在前面提过训练数据本身的平滑程度会直接决定模型学到的技能属性。这里再展开讲一下。有段时间我修改了遥操作设备的采样参数采集到的演示轨迹肉眼看着很正常但速度曲线里有很多细小的尖峰。模型训练完之后的动作输出就带着这些尖峰的特征部署侧滤波再怎么努力也只是把尖峰削掉一部分无法完全消除因为模型已经把高频分量内化到了策略里。后来我在每个演示样本保存之前会先算一下末端位置的一阶差分快速扫一眼有没有异常尖刺。如果发现某条轨迹的尖刺明显多于其他样本就直接删掉重采。这个方法笨但很有效。LeRobot整理数据集的流程里其实没有强制做这一步所以如果你用的别人的训练数据也建议先做一次平滑度体检再决定要不要花时间调部署侧参数。5.3 关节空间和笛卡尔空间不要用同一套参数最后一个坑比较隐蔽。之前我把平滑参数统一作用在所有动作维度上包括关节角度和末端位姿。后来发现这样做在大范围运动时没问题但一旦执行直线插拔这种任务机械臂末端轨迹会偏离预期方向因为各个关节因为滤波程度不同而出现“步调不一致”。我分开处理之后才想明白关节空间和笛卡尔空间的平滑是两种物理意义。关节空间平滑适合大范围转移能有效降低每个关节的瞬时冲击笛卡尔空间平滑更适合保持末端轨迹的直线性比如擦桌子、跟踪目标等。我的做法是在末端位姿上做一次轻量滤波和限速得到平滑后的目标位姿再通过逆解换算成关节指令最后对关节指令也做一次很小的滤波来消除逆解带来的高频分量。这样两层各有侧重不会互相打架。不过要注意末端位姿的表示方式欧拉角在接近奇异位形时会跳变此时笛卡尔平滑会放大扰动。我后来改成用旋转向量再转成四元数来避坑效果更稳。这个细节虽然和运动控制平滑不直接相关但在PiPER这类近奇异位形频繁出现的机构上它直接影响平滑的效果是否可信。调完再回头看运动控制平滑是LeRobot落地的必选项在PiPER上做LeRobot落地运动控制平滑优化不是可有可无的锦上添花而是直接影响任务成功率的关键环节。很多朋友在跑通LeRobot后把大量精力花在调模型上却忽略了动作流在真实硬件上会遇到频率错位和物理约束的问题导致效果不佳却找不到原因。我个人的经验是遇到动作抖先别急着上各种高级滤波算法。第一步把策略输出频率和底层控制频率打点打出来看看有没有阶梯结构第二步把模型输出的动作序列画出来看看有没有高频噪声第三步再去考虑用滤波、重采样和限速来整形。整个排查链路下来往往一个小时就能定位问题比自己凭感觉调参要高效得多。如果你也在这套组合上折腾建议先把我前面给的参数表作为起点再根据你的具体任务微调。等你把“平滑过度”和“平滑不足”两种状态都亲手试过一遍就自然能找到最适合你的那一组参数。
返回列表