4. 误差建模与参数辨识理论

好,咱们进入第四章。这一章是理论核心,也是很多工程师觉得「劝退」的地方。但我想说,搞懂误差建模和参数辨识,你才算真正入了机器人标定的门。我当年在产线上调试一台六轴机器人,重复定位精度死活提不上去,折腾了两周,最后发现就是参数辨识的模型没建对。嗯,从那以后,我再也不敢轻视这部分理论了。

4.1 运动学参数误差模型

先问一个问题:为什么我们明明用了高精度的编码器,机器人末端还是会偏?

说白了,你给控制器发的指令是「理论关节角」,但实际机器人身上有加工公差、装配间隙、减速器背隙、连杆变形……这些误差叠加起来,末端位置就跑了。我们需要一个数学模型,把这些误差和末端位姿偏差之间的关系描述清楚。

我个人习惯用 MDH(Modified Denavit-Hartenberg) 参数法来建模。每个关节有四个参数:

参数 符号 物理含义 常见误差来源
连杆长度 ai 相邻关节轴线的公垂线距离 加工误差、装配偏差
连杆扭角 αi 相邻关节轴线的空间夹角 铸造变形、热处理变形
关节距离 di 沿关节轴线的偏移量 垫片厚度、轴承游隙
关节转角 θi 绕关节轴线的旋转角度 编码器零位偏移、减速比误差

每个参数都有一个名义值和一个实际值。误差就是两者的差值:

Δa_i = a_i_real - a_i_nominal
Δα_i = α_i_real - α_i_nominal
Δd_i = d_i_real - d_i_nominal
Δθ_i = θ_i_real - θ_i_nominal

把这些误差代入正向运动学方程,做一阶泰勒展开,就得到了误差模型:

ΔP = J * Δq

其中:

  • ΔP:末端位姿误差向量(6×1,包含3个位置误差和3个姿态误差)
  • J:辨识雅可比矩阵(6×4n,n为关节数)
  • Δq:待辨识的运动学参数误差向量(4n×1)

关键点:这个模型是线性的!虽然原始运动学是非线性的,但我们在名义值附近做了一阶近似,把问题转化成了线性最小二乘问题。这是整个标定方法的数学基础。

4.2 最小二乘法原理

有了误差模型,下一步就是求解 Δq。你想想看,我们测量了 m 组末端位姿数据,每组都能列一个方程:

ΔP_i = J_i * Δq

把所有方程堆起来:

⎡ ΔP_1 ⎤   ⎡ J_1 ⎤
⎢ ΔP_2 ⎥   ⎢ J_2 ⎥
⎢  ... ⎥ = ⎢ ... ⎥ * Δq
⎣ ΔP_m ⎦   ⎣ J_m ⎦

即:Y = A * Δq

现在问题变成了:已知 Y 和 A,求 Δq 使得残差平方和最小。

标准最小二乘解是:

Δq = (A^T * A)^{-1} * A^T * Y

这个公式看起来简单,但我在项目中踩过坑。有一次标定结果发散,检查了半天,发现是 A^T*A 矩阵条件数太大,接近奇异。为什么会这样?因为某些测量位姿太接近,导致雅可比矩阵的列向量线性相关。

避坑指南:我曾经在产线上连续采集了50组数据,结果辨识出来的参数误差比不标定还大。后来发现,测量位姿全部集中在工作空间的一个小区域里。记住:测量位姿必须充分激励所有关节,覆盖整个工作空间。

解决病态问题的方法有两个:

  1. 增加测量数据量:m >> 4n,让方程超定
  2. 使用正则化:在 A^T*A 上加一个阻尼项

4.3 非线性优化方法(Levenberg-Marquardt)

最小二乘法虽然好用,但它假设误差模型是线性的。实际上,一阶泰勒展开的近似只在名义值附近有效。如果初始误差很大,或者机器人构型远离名义值,线性模型就不够用了。

这时候就需要 Levenberg-Marquardt(LM)算法。它结合了梯度下降法和高斯-牛顿法的优点,是机器人标定领域最常用的非线性优化方法。

LM算法的核心思想是:

Δq = -(J^T * J + λ * I)^{-1} * J^T * e

其中:

  • J:误差对参数的雅可比矩阵
  • e:当前参数下的末端位姿误差向量
  • λ:阻尼因子,控制算法在梯度下降和高斯-牛顿之间切换
  • I:单位矩阵

当 λ 很大时,算法退化为梯度下降法,收敛慢但稳定;当 λ 很小时,算法接近高斯-牛顿法,收敛快但可能发散。LM算法会在迭代过程中动态调整 λ。

实战技巧:我习惯的初始 λ 设为 0.01,然后每次迭代后检查残差是否下降。如果下降,λ 减小10倍;如果上升,λ 增大10倍。这个策略在大多数情况下都能稳定收敛。

下面是一个在STM32上实现的LM算法核心代码片段:

// LM算法单步迭代
float lm_step(float *params, float *measurements, int n_params, int n_meas) {
    // 1. 计算当前残差和雅可比矩阵
    float J[n_meas][n_params];
    float e[n_meas];
    compute_residual_and_jacobian(params, measurements, e, J);
    
    // 2. 计算 J^T * J 和 J^T * e
    float JTJ[n_params][n_params];
    float JTe[n_params];
    compute_JTJ_and_JTe(J, e, JTJ, JTe, n_meas, n_params);
    
    // 3. 添加阻尼项
    for (int i = 0; i < n_params; i++) {
        JTJ[i][i] += lambda;
    }
    
    // 4. 求解线性方程组 (JTJ + λI) * Δq = JTe
    float delta[n_params];
    solve_linear_system(JTJ, JTe, delta, n_params);
    
    // 5. 更新参数
    float new_params[n_params];
    for (int i = 0; i < n_params; i++) {
        new_params[i] = params[i] + delta[i];
    }
    
    // 6. 计算新残差,决定是否接受更新
    float new_error = compute_residual(new_params, measurements);
    if (new_error < current_error) {
        lambda *= 0.1;  // 成功,减小阻尼
        memcpy(params, new_params, sizeof(float) * n_params);
        return new_error;
    } else {
        lambda *= 10.0; // 失败,增大阻尼
        return current_error;
    }
}

注意:在STM32上跑LM算法,矩阵求逆是最大的性能瓶颈。我建议使用Cholesky分解或QR分解来求解线性方程组,而不是直接求逆。另外,float精度在迭代过程中可能不够,必要时可以切换到double。

4.4 本章知识体系

为了让你更直观地理解这三部分的关系,我画了一张流程图:

运动学参数辨识流程 MDH参数建模 一阶泰勒展开 ΔP = J * Δq 采集测量数据 构建超定方程组 最小二乘求解 LM迭代优化 动态调整λ 收敛判断 迭代反馈:更新名义参数 输出:标定后的运动学参数 说明: 线性最小二乘法提供初始解,LM算法在此基础上进行非线性优化,两者配合使用效果最佳。

从这张图可以看出,整个辨识流程是一个闭环:先建立误差模型,然后用最小二乘法得到初始解,再用LM算法迭代优化,最后把标定后的参数写回控制器。如果精度不达标,就重新采集数据再来一轮。

我个人建议,在工程实践中不要一上来就上LM算法。先用最小二乘法算一遍,看看残差的大小和分布。如果残差已经很小,就没必要折腾非线性优化了。毕竟在STM32上跑矩阵运算,算力还是很宝贵的。