☰
机械臂六维力传感器标定与重力补偿实战指南
2026/10/6 5:58:27 网站建设 项目流程

1. 传感器装上机械臂之后,为什么裸数据没法直接用

1.1 一次真实的"翻车"现场

我记得第一次把六维力传感器装到机械臂末端时,心里想得挺简单:传感器买回来不就是读数吗?上电、读串口、写个函数把六个分量拿到,然后直接用来做接触检测。结果第一组数据差点让我怀疑传感器是坏的——末端什么都没碰,机械臂保持一个固定姿势静止不动,按道理受力应该是"零加一个工具重力",但屏幕上的 Fx、Fy、Fz、Mx、My、Mz 全在十几二十的范围内来回飘,而且换个姿态之后,数值变化的规律完全看不懂。

更尴尬的是,我把机械臂摆成工具朝下的姿态,这时候理论上末端工具重力应该全部落在 Z 轴负方向,但实际读到的 Fz 跟手算的理论值差了将近 8N。Mx、My 也不是零,明明工具重心就在法兰中心附近,怎么会有这么大的力矩?

这就是六维力传感器标定的典型场景:传感器本身是准的,装到机械臂上之后,因为安装应力、零点漂移、工具重力耦合、温度变化等原因,读数和你想要的"接触力"之间隔着一大截需要扣除的量。不做标定就直接拿数据做力控,结果就是误触发、漏触发、力控震荡,甚至把零件压坏。

这篇博文我就把当时解决这个问题的完整思路写下来,核心就三件事:六维力传感器为什么要标定、怎么用最小二乘法把标定问题变成数学问题、标定完之后重力补偿怎么用。适合正在做机械臂力控、遥操作力反馈、装配打磨应用,以及拿六维力传感器做毕业设计的朋友参考。

1.2 读数里面到底混了几类东西

要搞清楚标定标的是什么,先得知道传感器读数里混杂了哪些成分。以我实际遇到的场景为例,传感器安装在机械臂法兰和末端工具之间,此时读数 D 可以拆成下面几块:

  • 传感器自身的零点偏移:应变片桥路不可能绝对平衡,上电之后会有一个固定的偏置,这个偏置受温度影响还会缓慢漂移。这是六维力传感器最基础也是最先要处理的问题。
  • 安装预紧力:法兰螺丝拧紧时,强力矩会让传感器弹性体产生变形,反映为读数中的一个固定偏置。拧螺丝的力度不同,这个偏置也不同。
  • 末端工具重力:只要是装在机械臂上的载荷,它的重力就会持续压在传感器上,而且重力在传感器坐标系中的投影方向会随着机械臂姿态变化而变化。这是重力补偿要处理的核心对象。
  • 重力产生的力矩:工具重力不经过传感器测量中心时,会额外产生一个随姿态变化的力矩分量。这也是为什么明明没接触任何东西,力矩读数却不为零。
  • 动态力/惯性力:机械臂运动过程中,加速度会让工具产生惯性力,叠加在传感器输出上。这部分严格来说属于动态补偿的范畴。
  • 温度漂移和零漂:传感器上电后温度缓慢升高,或者环境温度变化,都会让零点缓慢移动,量级通常不大,但对于需要精细力控的场景很致命。

在静态或准静态场景下(机械臂运动得很慢,或到位后停稳),最后一项动态力可以忽略,前几项是主要矛盾。而前几项虽然来源不同,但它们的共同特点是:都跟"当前末端姿态"有确定关系。既然每换一个姿态读数就变,那就把读数和姿态建立方程,用一批姿态下的数据反推出系统参数——这就是标定的本质。

1.3 六维力标定和重力补偿的边界在哪儿

很多刚接触这个方向的人会把"标定"和"重力补偿"混在一起,甚至以为标定完了就不用做重力补偿了。我自己的理解是:标定是一个"求参数"的过程——求传感器的零偏、工具的重力向量、重心位置;重力补偿是一个"用参数"的过程——标定求出这些参数之后,在运行阶段根据实时姿态把由工具重量导致的力和力矩从传感器读数里扣掉。

而且还要注意,标定这一步解决的只是"传感链路中的工具自重分量",它不同于手眼标定。手眼标定求的是相机坐标系与机械臂末端坐标系之间的变换关系,六维力传感器标定求的是传感器零偏与载荷参数,两者解决的问题完全不是一个维度。很多做 ROS 机械臂开发的朋友容易在这里绕晕。

另外还有一个常见误区:以为六维力传感器的出厂静态校准证书就足够了。出厂校准保证的是"传感器输出电压到物理力的换算精度",但它无法替你解决"装上工具之后这个工具多重、重心在哪里、安装偏置是多少"这个问题。后者恰恰是使用环节必须自己做的。

2. 从物理模型到最小二乘,标定问题是怎么变成数学题的

2.1 传感器的输出方程

标定第一步,是把读数跟姿态之间的关系写成数学表达式。先做几个约定:机械臂基座坐标系定义为世界系,重力方向在世界系下取单位向量 g_world = [0, 0, -1](假设 z 轴朝上);传感器坐标系固定安装在机械臂法兰上,当前姿态由旋转矩阵 R_world_to_tool 表示,这个矩阵通常能直接从机械臂控制器或运动学接口拿到。

重力方向在传感器坐标系下的单位向量记为 c,它的计算方法是:

c = R_world_to_tool * g_world

c 的物理含义是:载荷重力往哪边压。读六个分量时,力和力矩可以分别建立方程。令工具重量为 w(标量,单位 N),传感器力零点为 b_f = [b_fx, b_fy, b_fz],那么在第 i 个姿态下,力通道读数满足:

f_i = w * c_i + b_f + 噪声

力矩通道稍微绕一点。设工具重心在传感器坐标系中的位置为 r = [rx, ry, rz],它是重力在传感器上的作用点偏移。力矩的物理关系是力乘以力臂,所以力矩公式为:

τ_i = w * (r × c_i) + b_τ + 噪声

其中 × 表示向量叉乘,b_τ = [b_τx, b_τy, b_τz] 是力矩通道的零点。

这就是整个标定问题的基础模型。你会发现力通道是线性的,未知量为 w 和 b_f 共 4 个;力矩通道里 r 和 c 做叉乘,看起来是非线性的,但 w 和 r 是乘积关系,可以合并成一个新的未知向量 q = w * r,这样力矩方程同样变成线性方程:

τ_i = q × c_i + b_τ

于是所有未知量合并起来是 4 + 6 = 10 个参数。每一个姿态能提供 3 个力方程和 3 个力矩方程,也就是说只要采 2 个不同姿态就有 12 个方程,数量上已经超过未知数。实际操作中我们当然不会只采 2 组,会采几十组,这就是典型的超定方程组。

2.2 为什么是非用最小二乘不可

超定方程组看似简单,难点在于:由于测量噪声、姿态误差、传感器分辨率限制等影响,你无法找到一组参数让所有方程同时精确成立。最自然的思路就是让"预测值和实测值的总误差最小"。

这就是最小二乘法的核心思想:找一个参数向量 x,使得残差平方和最小。写成数学形式就是:

min ||A x - b||^2

其中 A 是由姿态信息构成的系数矩阵,b 是传感器读数。把矩阵写出来会更直观。力部分的系数矩阵,每个姿态贡献三行:

A_f 第 i 个姿态的三行 = [ c_i, I_3 ] = [ cx, 1, 0, 0 ] [ cy, 0, 1, 0 ] [ cz, 0, 0, 1 ]

对应的参数向量是 x_f = [w, b_fx, b_fy, b_fz]。力矩部分的参数向量是 x_τ = [q_x, q_y, q_z, b_τx, b_τy, b_τz],每个姿态的三行是 [ -skew(c_i), I_3 ],其中 skew(c) 是 c 的反对称矩阵:

skew(c) = [ 0, -cz, cy ] [ cz, 0, -cx ] [ -cy, cx, 0 ]

代进去就是:

τ_i = -skew(c_i) * q + b_τ

这里 q = w * r,标定完成后用 q 除以 w 就能还原出重心位置 r。

那为什么最小二乘解在这种问题里效果这么好?最直接的原因是:当测量噪声接近高斯白噪声时,最小二乘解是线性无偏估计中最优的,它给出的参数估计的方差最小。另一个实际原因是实现简单,直接把矩阵堆起来,调用现成求解器就行。高斯-马尔可夫定理保证了"在噪声是零均值、等方差、不相关的前提下,最小二乘估计是所有线性无偏估计中方差最小的",这正是量产工程中想要的性质。

2.3 标定过程中可选的两种线性化处理

我上面写的方案是把 10 个参数拆成力、力矩两个最小二乘问题分别求解。用力的数据先求出 w 和 b_f,再用力矩数据求 q 和 b_τ。这种做法优点是数值稳定、实现简单,缺点是严格来说忽略了力方程和力矩方程之间的噪声相关特性。

另一种方案是把力方程和力矩方程合并成一个大矩阵联合求解。合并成一个 6N × 10 的矩阵,一次求出 w、b_f、q、b_τ。从理论上讲,联合估计能更好地利用全部信息,但因为力和力矩的量纲不同(N 和 N·m),如果通道噪声差异很大,还得考虑加权。我自己实际对比过两种方案,在传感器本身噪声水平正常的情况下,结果相差很小。所以我更推荐先做分步估计,至少调试阶段方便定位问题——如果力参数明显异常,先排查力通道数据;如果力矩参数异常,再排查力矩通道数据,两件事互不干扰。

3. 采集样本前的准备:姿态怎么摆,数据怎么记才算合格

3.1 样本覆盖性设计

标定效果一半靠算法,另一半靠数据。数据质量的核心指标是姿态覆盖性。

很多第一次做标定的人会偷懒,让机械臂随便动几个位置就采了。我踩过这个坑:第一次采了 12 组姿态,全是工具水平或者接近水平的位置。结果最小二乘解出来,w 的值跟实际重量差了 0.8N 以上,重心位置 r 更是完全没法看,离理论值十万八千里。后来分析了原因:当所有姿态的 c 向量都集中在同一个方向附近时,系数矩阵的列与列之间近似线性相关,病态程度很高,任何一个微小噪声都会被放大。

正确做法是让重力方向在传感器坐标系下尽量覆盖整个球面。具体来说:

  • 工具分别朝上、朝下,让 c 接近 [0,0,1] 和 [0,0,-1];
  • 工具水平朝向 X 正、X 负方向,让 c 接近 [1,0,0] 和 [-1,0,0];
  • 工具水平朝向 Y 正、Y 负方向,让 c 接近 [0,1,0] 和 [0,-1,0];
  • 在这个基础上,让工具绕自身 Z 轴旋转不同角度再采若干组,比如每 45 度采一组;
  • 再混合一些倾斜 45 度左右的姿态,把中间区域补上。

我实际做的标定数据通常是 40 到 60 组姿态,覆盖上面这些位置。采集时间大概半小时到一小时,但换来的是参数的稳定性。至于姿态之间的过度速度:机械臂在姿态间移动时可以快速运动,但到达目标姿态后必须停稳再采样,这个在后面踩坑部分还会细说。

3.2 采样过程中的数据判废标准

每组姿态到位之后,不能拿单帧数据直接用。最稳的做法是让机械臂在该姿态静止 1 到 2 秒,然后取这段时间内所有帧的均值。这样能有效滤除振动和电路噪声。

记录数据时我还会同时记录当前姿态的旋转矩阵或四元数,以及传感器原始的六个分量。这里有一个特别容易忽略的点:如果机械臂控制器输出的旋转矩阵是"世界到工具"还是"工具到世界",不同品牌定义不一样。我建议在代码里先打印一段旋转矩阵,拿一个已知的简单姿态(比如工具朝下)去验证方向是否正确,确认之后再往矩阵里填数据。不要相信文档,要相信实测。

另外,如果传感器支持"上电清零"功能,我建议在标定前做一次上电清零,这样可以先把大数目的零偏消掉一部分,让后面最小二乘求解的数值条件更好。清零之后采集时,需要注意:清零只是把当前读数置为零参考点,并不等于完成了标定。这一步仅仅是预处理。

3.3 一份可复用的数据记录表

下面是我当时用的一张记录表示例,结构很简单,但信息很全:

序号姿态描述R矩阵(9个值)FxFyFzMxMyMz备注
1工具竖直朝上.....................静置2s
2工具竖直朝下.....................静置2s
3工具水平+X.....................静置2s
..............................

实际采集时我直接把 R 矩阵和传感器读数按 CSV 行追加写文件,等采集完成统一读进来处理。不要用 Excel 手动录入,容易抄错。另外建议数据文件名带上日期和工具名称,同一个末端工具换过螺丝或换过安装方式之后,参数会有变化,需要重新标定。这个细节等你在现场被"标定忽然不准"坑一次就明白了。

4. 最小二乘求解的 Python 实现与结果解读

4.1 核心代码实现

用 Python 做这件事非常直接,NumPy 的 lstsq 函数就是标准最小二乘求解器。下面是我实际用的代码框架,去掉了一些工程包装,保留核心步骤。

import numpy as np def skew(v): return np.array([ [0.0, -v[2], v[1]], [v[2], 0.0, -v[0]], [-v[1], v[0], 0.0] ]) def calibrate(rot_mats, forces, torques, g_world=np.array([0.0, 0.0, -1.0])): n = len(rot_mats) A_f_list = [] b_f_list = [] A_t_list = [] b_t_list = [] for i in range(n): R_w2t = rot_mats[i] c = R_w2t @ g_world # 重力方向单位向量在传感器坐标系下的表示 # 力方程: f = w * c + b_f A_f_block = np.hstack([c.reshape(3, 1), np.eye(3)]) A_f_list.append(A_f_block) b_f_list.append(forces[i]) # 力矩方程: tau = -skew(c) * q + b_tau, q = w * r A_t_block = np.hstack([-skew(c), np.eye(3)]) A_t_list.append(A_t_block) b_t_list.append(torques[i]) A_f = np.vstack(A_f_list) b_f = np.concatenate(b_f_list) x_f, res_f, rank_f, sv_f = np.linalg.lstsq(A_f, b_f, rcond=None) w = x_f[0] # 工具重量 b_f_est = x_f[1:4] # 力零点偏置 A_t = np.vstack(A_t_list) b_t = np.concatenate(b_t_list) x_t, res_t, rank_t, sv_t = np.linalg.lstsq(A_t, b_t, rcond=None) q = x_t[0:3] # q = w * r b_t_est = x_t[3:6] # 力矩零点偏置 r_est = q / w # 重心位置 return { "weight": w, "force_bias": b_f_est, "torque_bias": b_t_est, "centroid": r_est, "residual_f": res_f, "residual_t": res_t, } # 使用示例(伪数据) # rot_mats 是 list of 3x3 旋转矩阵 # forces 是 list of (fx, fy, fz) # torques 是 list of (mx, my, mz) # result = calibrate(rot_mats, forces, torques)

这段代码有几个地方值得注意:

  • g_world 用 [0, 0, -1] 的前提是世界系 z 轴向上。如果你的机械臂基座定义不是这样,请按实际调整。
  • 我用的是 R_world_to_tool 左乘世界系向量,得到传感器系下的坐标。这是 ROS 里常见约定,但不同机械臂 SDK 可能不同,务必先用简单姿态验证。
  • lstsq 返回的 res 是残差平方和。如果 rank 值小于参数个数,说明你的数据覆盖不够,系数矩阵秩亏,这种情况下解不可靠。
  • q / w 求重心坐标时,如果 w 为负或者接近零,说明数据或符号有问题,要回头检查。

4.2 标定结果怎么判断好坏

拿到参数后,不能只看残差小就觉得万事大吉,一定要做交叉验证。我自己的做法是:把采集到的几十组数据随机分成两份,一份用来拟合参数,另一份用来验证。用拟合得到的参数去预测验证集里的传感器读数,算预测值和实测值之间的误差。

判断标定质量时可以看几个指标:

  • 力通道残差 RMS:正常应在 0.5N 以内(取决于传感器量程和噪声水平,量程 100N 左右的传感器做到 0.2N 以内很常见)。
  • 力矩通道残差 RMS:一般在 0.02 到 0.05 N·m 以内。
  • 估计出的重量 w 与实际工具重量偏差应小于 2% 到 3%。比如一个 5kg 的工具(约 49N),估计值应该在 48N 到 50N 这个区间,否则多半是数据覆盖或者旋转矩阵方向搞错了。
  • 重心位置 r 和实际安装位置大致符合。比如工具重心在传感器坐标系下偏 X 正方向 30mm,那么 rx 就应该是 0.03 左右。如果偏差数量级不对,优先查力矩通道的符号方向。

我当时第一次标定的 w 估计是 47.3N,实际重量 49N 左右,残差也还行,但重心位置明显不对。后面发现是力矩的符号约定理解错了,把传感器输出力矩方向当成了和坐标系定义一致,实际上在某一个通道上是反的。修正之后,w 变成 49.2N,重心位置也正常了。

4.3 残差分布藏着哪些信息

每次标定完,我会画一张残差图:横轴是样本序号,纵轴是每个样本中 6 个通道的预测误差。如果发现残差不是随机散布,而是有明显的规律性——比如某个方向总偏大,或者误差随着姿态角度变大而变大——那就要警惕了。规律性残差通常意味着模型没建对,而不是随机噪声。

最常见的规律性残差来源有三个:

一是传感器非线性。大载荷传感器在小载荷段往往有迟滞和非线性,这时候用简单的线性模型无法完全拟合,残差会表现出和载荷水平相关的趋势。这种情况在一般工业应用里可以接受,只要残差幅度小于力控阈值就行。

二是姿态不准确。如果机械臂的定位精度不高,或者工具安装有柔性变形,那么旋转矩阵 R 本身就带误差,导致 c 值不准,最终残差会随姿态变化而增大。

三是温度漂移。标定采样时间如果太长,传感器的零点在不同时间段已经发生变化,前后数据不一致,残差也会显著偏大。这也是我后来坚持尽量在短时间内采完的原因。

5. 重力补偿上机:补偿公式、切换逻辑与力控衔接

5.1 实时补偿公式

标定完成之后,重力补偿就变成了纯计算问题。在运行阶段,从机械臂控制器拿到当前旋转矩阵 R,算出 c,然后实时扣除力和力矩中的重力分量。补偿公式如下:

f_ext = f_raw - (w * c + b_f) τ_ext = τ_raw - (q × c + b_τ)

也就是用标定得到的 w、b_f、q、b_τ 作为已知常数,每帧计算一次。用 Python 写出来就是:

def compensate(raw_force, raw_torque, R_w2t, calib): c = R_w2t @ np.array([0.0, 0.0, -1.0]) force_gravity = calib["weight"] * c + calib["force_bias"] torque_gravity = np.cross(calib["centroid"] * calib["weight"], c) + calib["torque_bias"] force_ext = raw_force - force_gravity torque_ext = raw_torque - torque_gravity return force_ext, torque_ext

这里 c 的更新频率要跟得上机械臂运动。如果机械臂运动速度比较快,建议把这段计算放到实时控制线程里,使用高频运动学反馈。如果控制周期是 1kHz,这个计算量完全在负担范围内。

5.2 动态场景下重力补偿的边界

说实话,上面这个补偿公式只在"准静态"场景下严格成立。当机械臂末端有加速运动时,工具质量会给传感器带来额外的惯性力,这部分力没有被重力项覆盖。如果是做普通装配、力位混合控制,机械臂速度通常不快,这个影响可以忽略;如果是做打磨、高速抛光的恒力控制,必须把加速度项考虑进去。

处理动态项的做法一般有两种。一种是在控制器里用加速度计或运动学加速度估计值,把惯性力项加进补偿模型;另一种是给传感器读数做低通滤波,用滤波后的信号做稳定力控,牺牲瞬态响应来换取稳定。对大多数入门项目,第二种方案更简单可靠,同时注意调整控制器的力带宽,不要试图补偿高频分量。

另外要提醒一点:传感器读数在机械臂运动时还有振动噪声,尤其是六维力传感器本身固有频率不高的情况下,上机前的滤波设计很重要。我常用的是一阶低通滤波,截止频率在 5 到 20Hz 之间,具体要看你的控制周期和机械结构刚性。

5.3 力控衔接的几个具体问题

重力补偿做好之后,力控才有意义。做力控最直接的判断是:机械臂在静止状态,你用手去推末端工具,传感器补偿后的力能否准确反映你的推力。这个实验如果通过,再去做力跟踪控制就顺理成章了。

我在力控衔接时遇到过几个具体问题:

  • 补偿后的数据在低速运动时会有轻微的"残余重力"现象,即静止时读数接近零,但动起来会有 1 到 2N 的漂移。这个通常来自旋转矩阵更新滞后和传感器动态响应差异,可以用死区或者状态机去抑制。
  • 接触判定阈值设置不能只看补偿后的噪声 RMS,还要看力控方向上的动态噪声。比如 Z 轴接触检测,阈值至少设成补偿后 Z 轴读数噪声的 3 到 4 倍。
  • 在机械臂切换工具后,一定要重新标定。电爪、吸盘、气爪这些工具的重量和重心差异很大,复用旧参数轻则力控目标不准,重则接触检测完全失效。

6. 实战中踩过的坑与排查思路

6.1 数据采集时末端没有真正"停稳"

这是最隐蔽的坑。机械臂控制器报"到位",实际上末端因为减速和残余振动还在小幅度晃动,这时候读传感器,力数据里混着动态分量。表现就是你标定出的参数时好时坏,重复采集两次结果差异很大。

排查思路:在采集代码里加一个"稳定判据",比如连续 50ms 内六维力读数变化都小于阈值,才认为该姿态可以记录。不要只依赖机械臂控制器的到位信号。

6.2 姿态覆盖太集中导致系数矩阵病态

前面提到过,如果所有姿态都让工具接近水平,c 向量就都聚集在同一个平面附近,导致系数矩阵条件数很大,求解结果对噪声极其敏感。判断方法很简单:用 numpy 打印 A 矩阵的条件数(cond(A)),如果超过几千,说明姿态集设计不合理。重新采一组覆盖更广的姿态,条件数通常会降到几十以内。

6.3 温飘在做精细力控时不可忽视

传感器上电半小时和刚上电时的零点能差出 0.5N 以上,这对 1N 级别的接触力控制是致命的。我的处理办法是:长时间工作前先预热,上电后等 15 到 20 分钟再上电清零;如果现场不允许等待,至少保证标定和实际使用的温升状态一致。另外,标定用的数据也尽量在机械臂工作一段时间、温度稳定之后采集,这样标定参数和运行状态的温度条件更匹配。

6.4 旋转矩阵方向和传感器坐标系对不上

常见症状:力补偿完之后,静止读数虽然归零,但把机械臂摆到另一个姿态后,读数出现很大的"伪接触力"。这多半是 R 矩阵的坐标系约定和传感器坐标系定义不一致。排查方法很简单:让机械臂工具朝下,打印 c 向量,理论上应该是 [0, 0, 1](如果世界系 z 轴向上,重力方向朝下,在传感器坐标系下就是工具坐标系 z 轴?不同安装不同,需要看工具坐标系的定义)。如果得到的是 [0, 0, -1],说明你 R 矩阵求了转置或者用的是工具到世界的旋转矩阵。修正这个方向问题,比调任何参数都重要。

6.5 标定结果看起来很好但接触检测还是失效

这种情况往往不是标定本身的问题,而是传感器读数的量纲或符号约定在后续处理链路里出了问题。常见的有:力矩单位是 N·m 而你按 N·mm 处理;力矩正方向定义反了;或者换了一个机械臂工具之后用了旧参数。我的建议是,力控调试时先写一个简单的测试脚本,让机械臂在几个固定姿态下静止,打印补偿后的力,再手动施加力和力矩,确认六个通道的方向都正确后再开始调控制逻辑。这个习惯帮我省了至少两天的排查时间。

最后再分享一个小技巧:标定完参数,先别急着写进正式控制程序。拿一个标准砝码(比如 1kg 或 2kg 的砝码),吸在工具末端,换几个姿态验证补偿后的力读数是否接近砝码重量。这个测试不到十分钟,但能一次性发现问题,比在力控代码里做各种奇怪补偿动作有效得多。六维力传感器标定和重力补偿这个东西,原理不复杂,最难的反而是把每个细节做扎实。参数跑出来只是开始,真正可靠的数据链路,是在踩完这些坑之后才建立起来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询