
1. 动力学辨识到底在解决什么问题1.1 从一次“仿真发散”说起如果你在ROS里跑过机械臂的力矩控制大概率遇到过这种情况明明轨迹规划得很平滑PID参数也调了好几轮但一上力矩前馈机械臂就开始抖严重的时候直接仿真发散Gazebo里的模型像抽风一样乱飞。我第一次碰到这个问题的时候排查了整整两天最后发现根因是动力学模型里的惯性参数根本不准——连杆质量、质心位置、转动惯量这些值要么是从CAD里粗略估的要么是厂家给的标称值跟实际装配后的真实值差了一大截。这就是动力学辨识要解决的核心问题让仿真环境里的机械臂动力学模型尽可能逼近真实物理系统的行为。你可能会问仿真而已差不多就行了呗但如果你做的是力控、碰撞检测、拖动示教、或者强化学习训练动力学模型的精度直接决定了算法能不能收敛、策略能不能迁移到真机上。仿真发散很多时候不是控制器的问题而是模型本身就不对。1.2 最小惯性参数集是什么概念一个N自由度的串联机械臂如果用牛顿-欧拉法或者拉格朗日法建动力学方程理论上每个连杆有10个惯性参数质量、质心的三个坐标、六个独立转动惯量分量。六轴机械臂就是60个参数。但实际上由于关节约束和连杆连接方式这60个参数里有大量是冗余的——有些参数根本不会独立影响关节力矩有些参数之间线性相关可以合并。最小惯性参数集就是把这60个参数压缩到最精简的一组基参数使得每个基参数都独立可辨识且能完整描述系统的动力学行为。对于典型的六轴串联机械臂最小惯性参数集通常在30到40个参数左右具体数量取决于关节类型和连杆构型。这个压缩过程不是拍脑袋决定的而是通过动力学方程的线性化——把关节力矩写成关于惯性参数的线性回归形式然后对回归矩阵做QR分解或者SVD找出线性无关的列对应的参数组合就是基参数。我自己的理解是最小惯性参数集就像给机械臂的动力学“降维打击”把一堆看起来复杂但实际冗余的参数精简成真正起作用的那些。做辨识的时候你只需要辨识这组基参数不用去管每个连杆单独的物理参数是多少——反正它们最终都是以组合形式影响关节力矩的。1.3 为什么选择仿真实践而不是直接上真机真机辨识当然更准但成本高、风险大、周期长。一台六轴机械臂做完整的动力学辨识实验需要设计激励轨迹、采集数据、处理噪声、迭代优化中间如果轨迹设计不当导致机械臂振动过大还可能损坏设备。而仿真实践的好处在于你可以完全控制变量知道“真值”是多少可以反复验证辨识算法的收敛性和鲁棒性可以随意注入噪声看算法抗干扰能力。更重要的是仿真实践能帮你建立完整的辨识流程认知。从激励轨迹设计、数据采集、参数估计、到模型验证每一步你都能看到中间结果知道哪里出了问题该怎么调。等这套流程跑通了再迁移到真机上你心里是有底的。我见过太多人直接上真机做辨识结果数据采了一堆回归矩阵条件数爆炸辨识出来的参数物理上根本说不通最后不了了之。提示仿真实践的核心价值不是“替代真机”而是“预演流程”。你在仿真里踩过的坑真机上大概率还会踩但至少你知道坑在哪、怎么绕。2. 最小惯性参数集的推导与实现细节2.1 动力学方程的线性化形式机械臂的刚体动力学方程可以写成τ M(q)q̈ C(q,q̇)q̇ G(q)其中τ是关节力矩向量q是关节角度M是惯性矩阵C是科氏力和离心力项G是重力项。这个方程看起来是非线性的但关键洞察在于对于固定的q、q̇、q̈τ关于惯性参数是线性的。也就是说你可以把τ写成τ Y(q, q̇, q̈) · π其中Y是回归矩阵维度是n×pn是关节数p是惯性参数个数π是惯性参数向量。这个Y矩阵只跟运动状态有关跟惯性参数无关。这就是动力学辨识的数学基础——你不需要知道参数是多少只需要知道运动状态就能构造出回归矩阵。我第一次推导这个的时候觉得挺神奇的明明动力学方程里q̇的平方项、三角函数乘积项一大堆怎么就对参数线性了呢后来想明白了非线性只体现在运动变量上参数是以一次项的形式出现的。比如一个连杆的动能是½m·v² ½ωᵀIωv和ω是运动变量m和I是参数展开后参数就是一次项。2.2 回归矩阵的构造与QR分解构造回归矩阵Y的过程本质上就是把牛顿-欧拉递推或者拉格朗日方程展开把每个关节力矩写成关于所有惯性参数的线性组合。对于六轴机械臂如果每个连杆10个参数Y就是6×60的矩阵。但前面说了这60个参数里有冗余需要压缩。压缩的方法是对Y矩阵做QR分解。具体来说把Y矩阵按列进行QR分解得到上三角矩阵R。R矩阵的对角线元素反映了对应列向量的“独立性”——如果某个对角线元素接近零说明这一列跟前面的列线性相关对应的参数就是冗余的可以去掉。剩下的线性无关的列对应的参数组合就是最小惯性参数集。实际操作中QR分解的结果跟参数的排列顺序有关。不同的排列顺序会得到不同的基参数组合但基参数的个数是固定的。我一般习惯把质量参数排在前面质心参数次之转动惯量参数最后这样分解出来的基参数物理意义比较清晰。import numpy as np from scipy.linalg import qr # 假设Y是回归矩阵shape为(n_samples*n_joints, n_params) # 这里用随机数据演示 n_samples 1000 n_joints 6 n_params 60 Y np.random.randn(n_samples * n_joints, n_params) # QR分解带列主元 Q, R, P qr(Y, modeeconomic, pivotingTrue) # 找出对角线元素的绝对值大于阈值的列 threshold 1e-6 diag_R np.abs(np.diag(R)) independent_cols np.where(diag_R threshold)[0] # 基参数对应的原始参数索引 base_param_indices P[independent_cols] print(f最小惯性参数集大小: {len(base_param_indices)}) print(f基参数索引: {base_param_indices})这段代码是核心逻辑的简化版。实际用的时候Y矩阵的构造要复杂得多需要根据具体的机械臂DH参数和运动状态来生成。我建议先用符号计算工具比如SymPy推导出回归矩阵的解析形式再用数值方法做QR分解这样既保证了准确性又方便调试。2.3 基参数的物理意义与可辨识性QR分解得到的基参数很多是原始参数的线性组合物理意义不像单个连杆的质量或转动惯量那么直观。比如某个基参数可能是“连杆2的质量加上连杆3质量的一半”这种组合参数在物理上没法直接对应到某个零件。但这不影响辨识——你只需要知道这组基参数的值就能完整描述系统的动力学行为。可辨识性方面有个经验判断如果某个基参数对应的回归矩阵列向量范数很小说明这个参数对关节力矩的影响很弱辨识出来的值会很不稳定。这时候要么调整激励轨迹让这个参数的影响放大要么直接把这个参数固定为估计值不参与辨识。我一般会看回归矩阵的条件数如果条件数超过1000就说明有些参数的可辨识性很差需要处理。注意最小惯性参数集不是唯一的。不同的分解方法、不同的参数排列顺序会得到不同的基参数组合。关键是保证基参数之间线性无关且能完整描述动力学行为。3. 仿真环境搭建与激励轨迹设计3.1 仿真平台选型与配置做动力学辨识仿真平台选择很关键。我试过几种方案Gazebo、MuJoCo、PyBullet各有优劣。Gazebo跟ROS集成好适合做ROS2机械臂仿真但物理引擎的精度一般接触模型和摩擦模型比较复杂做纯刚体动力学辨识反而容易引入额外误差。MuJoCo的物理精度高接触和摩擦模型更成熟但跟ROS的集成需要额外工作。PyBullet轻量、Python接口友好适合快速验证算法但大规模场景下性能不如前两者。我最终选的是PyBullet做算法验证Gazebo做ROS集成测试。PyBullet的好处是你可以直接拿到每个连杆的质量、质心、转动惯量方便对比辨识结果。而且PyBullet的动力学计算是确定性的没有随机噪声适合做算法收敛性分析。配置方面你需要一个URDF或者MJCF格式的机械臂模型文件。URDF里要包含每个连杆的惯性参数mass、origin、inertia这些就是你的“真值”。如果你用的是厂家提供的URDF惯性参数可能是粗略估计的这时候你可以手动修改这些值模拟“真实但未知”的惯性参数然后看辨识算法能不能收敛到这些值。import pybullet as p import pybullet_data # 连接物理引擎 physicsClient p.connect(p.DIRECT) # 用DIRECT模式不显示GUI速度快 p.setAdditionalSearchPath(pybullet_data.getDataPath()) p.setGravity(0, 0, -9.81) # 加载机械臂模型 robot_id p.loadURDF(kuka_iiwa/model.urdf, useFixedBaseTrue) # 获取关节数量 num_joints p.getNumJoints(robot_id) print(f关节数量: {num_joints}) # 获取每个连杆的惯性参数 for i in range(num_joints): link_info p.getDynamicsInfo(robot_id, i) mass link_info[0] local_inertia_diag link_info[2] local_inertial_pos link_info[3] print(f连杆{i}: 质量{mass:.4f}, 惯量对角{local_inertia_diag}, 质心{local_inertial_pos})这段代码能帮你快速拿到仿真模型里每个连杆的惯性参数。我建议把这些值记录下来作为后续辨识结果对比的基准。3.2 激励轨迹的设计原则激励轨迹的设计直接决定了辨识效果。好的激励轨迹应该满足几个条件充分激励所有基参数、避免关节超限、避免奇异位形、对噪声不敏感。常用的激励轨迹有傅里叶级数轨迹、五次多项式轨迹、最优轨迹等。傅里叶级数轨迹是最常用的因为它的频率成分丰富能激励出系统的多阶动态特性。一般每个关节用5阶傅里叶级数基频选0.1到0.5Hz具体看机械臂的带宽。频率太高惯性和科氏力的影响混在一起不好分离频率太低激励不充分参数辨识不准。我自己的经验是先确定机械臂的关节限位和速度限位然后在这个范围内随机生成一批傅里叶级数轨迹用回归矩阵的条件数作为评价指标选条件数最小的那条。这个过程可以自动化用优化算法比如遗传算法或者粒子群来搜索最优的傅里叶系数。import numpy as np def fourier_trajectory(t, q0, A, B, omega, N5): 生成傅里叶级数激励轨迹 t: 时间向量 q0: 关节偏置 A, B: 傅里叶系数矩阵shape为(n_joints, N) omega: 基频 N: 谐波阶数 n_joints len(q0) q np.zeros((len(t), n_joints)) qd np.zeros((len(t), n_joints)) qdd np.zeros((len(t), n_joints)) for j in range(n_joints): for k in range(1, N1): q[:, j] A[j, k-1] / (k * omega) * np.sin(k * omega * t) \ - B[j, k-1] / (k * omega) * np.cos(k * omega * t) qd[:, j] A[j, k-1] * np.cos(k * omega * t) \ B[j, k-1] * np.sin(k * omega * t) qdd[:, j] -A[j, k-1] * k * omega * np.sin(k * omega * t) \ B[j, k-1] * k * omega * np.cos(k * omega * t) q[:, j] q0[j] return q, qd, qdd这个函数生成的是位置、速度、加速度的时间序列。实际用的时候你需要根据关节限位来约束A和B的取值范围确保轨迹不超限。我一般会先跑一遍轨迹检查每个关节的位置、速度、加速度是否在允许范围内如果超了就缩小A和B的幅值或者降低基频。3.3 数据采集与预处理仿真里采集数据比真机简单得多——你不需要考虑编码器噪声、力矩传感器漂移、采样同步这些问题。但仿真里也有坑如果你用的是力矩控制模式PyBullet的力矩输入和关节加速度输出之间可能存在一步延迟这个延迟会影响回归矩阵的构造。我的做法是用位置控制模式给定关节位置轨迹让PyBullet自己算逆动力学得到力矩这样力矩和运动状态是严格对应的。数据采集的频率也很关键。太高了数据量大计算慢太低了丢失高频动态信息。一般采样频率取激励轨迹最高频率的10倍以上。比如你的傅里叶级数最高谐波是5阶基频0.2Hz那最高频率是1Hz采样频率至少10Hz我一般用100Hz留足余量。预处理主要是滤波和去均值。仿真数据虽然干净但如果你在PyBullet里开了关节摩擦或者阻尼力矩里会混入摩擦项这时候需要用低通滤波器把高频噪声滤掉。我一般用Butterworth低通滤波器截止频率取激励频率的2到3倍。提示仿真里做辨识建议先关掉关节摩擦和阻尼只保留刚体动力学。等刚体参数辨识准了再考虑摩擦辨识。否则摩擦项和惯性项混在一起回归矩阵的条件数会很难看。4. 参数估计与模型验证4.1 最小二乘估计与正则化有了回归矩阵Y和力矩向量τ参数估计就变成了一个线性最小二乘问题π̂ argmin ||Yπ - τ||²。理论上如果Y是满秩的直接求伪逆就行π̂ (YᵀY)⁻¹Yᵀτ。但实际中Y的条件数往往很大直接求逆会放大噪声导致估计值不稳定。这时候需要正则化。最常用的是Tikhonov正则化也叫岭回归π̂ (YᵀY λI)⁻¹Yᵀτ其中λ是正则化系数。λ的选择是个权衡λ太大估计值偏向零偏差大λ太小方差大估计值不稳定。我一般用L曲线法或者广义交叉验证GCV来选λ。还有一种方法是截断SVD对Y做SVD分解只保留奇异值大于阈值的那些分量小的奇异值对应的方向直接丢掉。这个方法比岭回归更直观你能清楚地看到哪些方向被截断了。我一般先用截断SVD看奇异值谱确定哪些参数方向可辨识性差然后再决定是用岭回归还是直接固定某些参数。import numpy as np from scipy.linalg import svd def estimate_parameters(Y, tau, methodtsvd, threshold1e-3, lambda_reg1e-4): 参数估计 Y: 回归矩阵 tau: 力矩向量 method: tsvd 或 ridge threshold: TSVD的奇异值阈值 lambda_reg: 岭回归的正则化系数 if method tsvd: U, s, Vt svd(Y, full_matricesFalse) s_inv np.zeros_like(s) s_inv[s threshold * s[0]] 1.0 / s[s threshold * s[0]] pi_hat Vt.T np.diag(s_inv) U.T tau elif method ridge: n_params Y.shape[1] pi_hat np.linalg.solve(Y.T Y lambda_reg * np.eye(n_params), Y.T tau) else: raise ValueError(method must be tsvd or ridge) return pi_hat这段代码里TSVD的阈值我一般取最大奇异值的1e-3到1e-4。岭回归的λ取1e-4到1e-2具体看数据噪声水平。仿真数据干净λ可以取小一点真机数据噪声大λ要取大一点。4.2 辨识结果的物理一致性检查辨识出来的基参数虽然不直接对应物理参数但可以通过一定的变换还原出每个连杆的质量、质心、转动惯量。还原出来的物理参数必须满足一些基本约束质量为正、转动惯量矩阵正定、三角不等式约束主惯量之和大于第三惯量等。如果辨识结果不满足这些约束说明辨识过程有问题要么是激励轨迹不够充分要么是数据预处理有问题。我一般会做几个检查第一看辨识出来的质量是否为正如果出现负质量肯定不对第二看转动惯量矩阵是否正定如果出现负特征值也不对第三看辨识的力矩预测误差如果误差在噪声水平以内说明辨识结果可用。def check_physical_consistency(pi_hat, param_mapping): 检查辨识参数的物理一致性 pi_hat: 辨识出的基参数 param_mapping: 基参数到物理参数的映射矩阵 # 还原物理参数 physical_params param_mapping pi_hat # 检查质量为正 masses physical_params[0::10] # 假设每10个参数一组第一个是质量 if np.any(masses 0): print(警告出现负质量辨识结果不可信) return False # 检查转动惯量正定 for i in range(len(masses)): I physical_params[i*104:i*1010].reshape(3, 3) I (I I.T) / 2 # 对称化 eigenvalues np.linalg.eigvalsh(I) if np.any(eigenvalues 0): print(f警告连杆{i}的转动惯量矩阵非正定) return False print(物理一致性检查通过) return True这个检查函数是简化版实际用的时候需要根据你的参数排列方式来调整索引。我建议在辨识流程里加上这一步能帮你快速发现明显的问题。4.3 模型验证力矩预测与轨迹跟踪辨识完参数怎么知道模型准不准最直接的方法是力矩预测用辨识出的参数和新的运动状态不参与辨识的验证轨迹计算预测力矩跟仿真里的实际力矩对比。如果预测误差在噪声水平以内说明模型是准的。另一个方法是轨迹跟踪把辨识出的动力学模型用到力矩控制里看机械臂能不能跟踪给定的轨迹。如果跟踪误差小说明模型精度够用。这个方法更接近实际应用场景但受控制器参数影响大需要仔细调PID。我一般两个方法都用。力矩预测看的是模型本身的精度轨迹跟踪看的是模型在实际控制任务中的表现。有时候力矩预测误差很小但轨迹跟踪效果不好那可能是控制器的问题不是模型的问题。def validate_model(robot_id, pi_hat, Y_val, tau_val): 模型验证力矩预测误差 tau_pred Y_val pi_hat error tau_val - tau_pred rmse np.sqrt(np.mean(error**2)) max_error np.max(np.abs(error)) print(f力矩预测RMSE: {rmse:.6f} Nm) print(f力矩预测最大误差: {max_error:.6f} Nm) # 计算相对误差 relative_error np.abs(error) / (np.abs(tau_val) 1e-6) print(f平均相对误差: {np.mean(relative_error)*100:.2f}%) return rmse, max_error验证轨迹的选择也很重要。不能用跟辨识轨迹一样的轨迹否则相当于“自己考自己”看不出泛化能力。我一般用不同频率、不同幅值的傅里叶级数轨迹或者用实际任务中常见的轨迹比如抓取、放置、焊接来做验证。5. 常见问题与排查技巧实录5.1 回归矩阵条件数过大怎么办条件数过大是动力学辨识里最常见的问题。表现是辨识出来的参数值很大但力矩预测误差却很小——这是因为大的参数值之间相互抵消了实际上模型并没有真正学到东西。解决方法有几个一是调整激励轨迹增加对可辨识性差的参数的激励二是用正则化方法把不可辨识的方向压下去三是直接固定某些参数比如把某个连杆的质量固定为CAD估计值不参与辨识。我自己的经验是先看奇异值谱如果奇异值衰减很快说明有些方向确实不可辨识这时候用TSVD截断是最直接的。如果奇异值衰减比较平缓但条件数还是大那可能是激励轨迹的问题需要重新设计。5.2 辨识结果对噪声敏感怎么处理仿真里噪声小这个问题不突出。但如果你在仿真里加了噪声模拟真机或者直接上真机做辨识噪声敏感就是个大事。处理方法包括增加数据量多跑几条轨迹、用低通滤波器、用正则化、用鲁棒估计方法比如Huber损失。我一般先用低通滤波如果还不够再加正则化。有个技巧把辨识数据分成多组每组单独辨识看参数估计的方差。如果方差大说明噪声影响大需要增加数据量或者加强正则化。如果方差小但偏差大说明模型有系统误差需要检查回归矩阵的构造是否正确。5.3 仿真发散与数值稳定性问题仿真发散不一定是辨识的问题也可能是仿真本身的问题。PyBullet在力矩控制模式下如果力矩太大或者太小或者时间步长太大都可能发散。我一般把时间步长设为1/240秒力矩限幅设为实际机械臂的额定力矩这样比较稳。还有一个坑PyBullet的关节阻尼和摩擦默认是零如果你在URDF里没设置仿真里机械臂会像在真空中一样一点阻尼都没有力矩稍微大一点就飞了。我一般会在URDF里加上小的关节阻尼比如0.1 Nm·s/rad让仿真更稳定。5.4 常见问题速查表问题现象可能原因排查方法解决方案辨识参数值异常大回归矩阵条件数大计算条件数看奇异值谱用TSVD或岭回归力矩预测误差大激励轨迹不充分检查回归矩阵秩重新设计激励轨迹仿真发散时间步长太大或力矩超限检查仿真参数减小步长加力矩限幅辨识结果不稳定噪声大或数据量少分组辨识看方差增加数据加强正则化物理参数不满足约束辨识过程有问题检查质量、惯量正定性重新检查回归矩阵构造轨迹跟踪效果差控制器参数不对单独调PID先调好控制器再上动力学前馈6. 从仿真到真机的迁移经验仿真里跑通了不代表真机上就能直接用。真机上有几个仿真里没有的因素关节摩擦、传动柔性、传感器噪声、控制延迟。这些因素会让辨识结果产生偏差尤其是摩擦在低速时影响很大。我的做法是先在仿真里辨识刚体参数然后把辨识出的参数作为初值在真机上做小范围的修正辨识。修正的时候只辨识那些对力矩影响大的基参数其他的固定。这样既能保证精度又能减少真机实验的时间和风险。还有一个经验真机辨识的激励轨迹频率要比仿真里低。仿真里可以用1Hz的激励真机上可能0.2Hz就够了。频率太高机械臂振动大反而影响辨识精度。而且真机的关节速度限位通常比仿真里保守轨迹设计要留足余量。最后说一个我踩过的坑真机辨识的时候一定要先检查编码器的零位和方向。如果零位不对回归矩阵里的重力项会完全错掉辨识出来的质量参数全是负的。这个坑我踩过一次排查了一整天最后发现是关节零位偏了5度。所以做辨识之前先做一遍零位标定磨刀不误砍柴工。