3. 基于力矩的碰撞检测原理:阈值法、动态阈值法、机器学习法、模型预测法
聊到力矩感知,大家最关心的其实就一个问题:怎么判断机器人撞上了?
我刚开始做导航调参那会儿,用的还是最笨的办法——看电流。电机电流一飙,我就知道八成是卡住了。但后来发现,电流这东西太粗糙了,负载变化、地面摩擦、电池电压波动都能让它乱跳。真正靠谱的,还得是力矩传感器直接读出来的数据。
这一章,我把四种主流的碰撞检测方法掰开揉碎了讲。每种方法我都亲手调过、踩过坑,希望能帮你少走弯路。
核心观点:没有万能的方法。阈值法简单但僵硬,动态阈值法适应性强,机器学习法精度高但数据贵,模型预测法理论完美但计算重。选哪种,取决于你的硬件算力和场景复杂度。
3.1 阈值法:最简单,也最容易翻车
阈值法的思路很直白:设定一个力矩上限,超过就认为撞了。比如关节力矩超过 5 N·m,立刻触发急停。
听起来简单对吧?但实际用起来,问题一堆。
我举个例子。你让机器人在平地上走,力矩基线可能是 2 N·m。但换到地毯上,基线直接跳到 3.5 N·m。如果你阈值设 4 N·m,平地上撞到软物体可能检测不到;地毯上正常行走反而误报。
我曾经踩过的坑:给一台差速底盘设了固定力矩阈值,结果在瓷砖地面跑得好好的,一上斜坡就疯狂误报。后来发现是重力分量没去掉。阈值法对安装角度、地面坡度极其敏感,千万别以为设一个值就万事大吉。
阈值法的代码实现很简单,但要注意滤波:
// 阈值法碰撞检测(C++ 伪代码)
const double TORQUE_THRESHOLD = 5.0; // N·m
const double FILTER_ALPHA = 0.2; // 一阶低通系数
double filteredTorque = 0.0;
bool detectCollision(double rawTorque) {
// 一阶低通滤波,去掉高频噪声
filteredTorque = FILTER_ALPHA * rawTorque + (1 - FILTER_ALPHA) * filteredTorque;
if (filteredTorque > TORQUE_THRESHOLD) {
return true; // 碰撞!
}
return false;
}
你看,代码就几行。但实际工程里,阈值法最大的问题是:你永远找不到一个完美的固定值。负载变了、电池电压掉了、关节磨损了,阈值都得跟着调。
3.2 动态阈值法:工程中最实用的方案
既然固定阈值不行,那就让阈值自己动起来。动态阈值法的核心思想是:实时估计当前工况下的力矩基线,然后在这个基线之上加一个浮动窗口。
我个人习惯用滑动窗口均值 + 标准差的方式来做。具体来说:
- 维护一个长度为 N 的力矩历史队列
- 计算队列的均值 μ 和标准差 σ
- 阈值设为 μ + k × σ,k 通常取 3~5
这样做的好处很明显:机器人上坡时基线自然抬高,阈值也跟着抬高,不会误报;撞到障碍物时力矩突变,瞬间超出动态阈值,检测灵敏。
我的经验值:窗口长度 N 取 50~100(10Hz控制周期下对应5~10秒),k 值取 4。太小的窗口容易被瞬时噪声带偏,太大的窗口响应迟钝。k 值取 3 以下误报多,取 6 以上可能漏检软碰撞。
代码实现也不复杂:
// 动态阈值法碰撞检测(C++ 伪代码)
class DynamicThresholdDetector {
private:
std::deque<double> history;
int windowSize = 80;
double k = 4.0;
public:
bool detectCollision(double torque) {
history.push_back(torque);
if (history.size() > windowSize) {
history.pop_front();
}
// 计算均值和标准差
double sum = 0.0, sumSq = 0.0;
for (double t : history) {
sum += t;
sumSq += t * t;
}
double mean = sum / history.size();
double variance = sumSq / history.size() - mean * mean;
double stddev = std::sqrt(variance);
double threshold = mean + k * stddev;
return torque > threshold;
}
};
动态阈值法是我在项目里用得最多的方法。它不需要额外传感器,不需要训练数据,算力消耗也低。说白了,就是性价比最高的方案。
3.3 机器学习法:数据够多,效果确实好
如果你手头有大量带标签的碰撞数据,机器学习法能给你最好的检测精度。常见的做法是把力矩、电流、速度、加速度拼成一个特征向量,扔给分类器。
我试过几种模型:
| 模型 | 精度 | 推理速度 | 训练难度 | 我的评价 |
|---|---|---|---|---|
| SVM | 中等 | 快 | 低 | 小样本够用,特征工程累 |
| 随机森林 | 较高 | 中等 | 低 | 不容易过拟合,推荐入门 |
| 轻量级神经网络 | 高 | 中等 | 高 | 效果好,但部署麻烦 |
| LSTM | 最高 | 慢 | 高 | 能捕捉时序特征,算力要求高 |
注意:机器学习法最怕的是训练数据和实际部署场景不一致。我曾经在一个项目里用实验室数据训练了随机森林模型,精度 98%。结果一上产线,地面有油污、有焊缝,力矩特征全变了,精度直接掉到 60%。数据分布偏移是机器学习法的头号杀手。
如果你决定走这条路,我的建议是:先用动态阈值法做 baseline,再尝试用机器学习法提升。不要一上来就上深度学习,除非你有充足的算力和数据。
3.4 模型预测法:理论上的最优解
模型预测法的思路和前面三种完全不同。它不直接看力矩大小,而是:用动力学模型预测当前应该输出多少力矩,然后比较预测值和实际值的残差。
残差大了,说明模型和现实对不上,大概率是撞了。
具体流程是这样的:
- 建立机器人动力学模型(比如牛顿-欧拉法)
- 输入当前关节角度、角速度、角加速度
- 模型预测出理论力矩 τpred
- 传感器读回实际力矩 τactual
- 计算残差 Δτ = |τactual - τpred|
- 如果 Δτ 超过阈值,判定为碰撞
这个方法的好处是:能区分「正常负载变化」和「碰撞」。比如机器人抓了一个重物,力矩变大,但模型预测也会相应变大,残差不会跳。而碰撞是外力突然介入,模型预测不到,残差瞬间飙升。
关键点:模型预测法的精度完全取决于动力学模型的准确度。摩擦参数、惯性参数、重心位置,任何一个不准,残差基线就会漂移。我见过一个团队花了三个月标定动力学参数,就为了让残差阈值能设到 0.5 N·m 以下。
代码示意(简化版):
// 模型预测法碰撞检测(C++ 伪代码)
class ModelBasedDetector {
private:
RobotDynamicsModel model; // 动力学模型
double residualThreshold = 0.8; // N·m
public:
bool detectCollision(double q, double qd, double qdd, double tauActual) {
// 模型预测力矩
double tauPred = model.computeTorque(q, qd, qdd);
// 计算残差
double residual = std::abs(tauActual - tauPred);
// 残差超过阈值 → 碰撞
return residual > residualThreshold;
}
};
模型预测法在协作机器人上用得很多,比如 Universal Robots 的碰撞检测就基于这个原理。但说实话,对于移动机器人导航场景,这个方法有点「杀鸡用牛刀」——你得先有精确的动力学模型,而大多数移动底盘的模型精度都不够。
3.5 四种方法怎么选?一张表说清楚
| 维度 | 阈值法 | 动态阈值法 | 机器学习法 | 模型预测法 |
|---|---|---|---|---|
| 实现难度 | ★☆☆☆☆ | ★★☆☆☆ | ★★★★☆ | ★★★★★ |
| 计算开销 | ★☆☆☆☆ | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ |
| 检测灵敏度 | 低 | 中 | 高 | 最高 |
| 误报率 | 高 | 中 | 低(数据好时) | 低(模型准时) |
| 适用场景 | 快速原型验证 | 大多数工程场景 | 数据充足、算力够 | 高精度协作机器人 |
| 我的推荐指数 | ★★☆☆☆ | ★★★★★ | ★★★☆☆ | ★★★☆☆ |
嗯,看到这里你应该明白了。动态阈值法是我最推荐的入门方案,它平衡了实现难度和检测效果。如果你后续发现动态阈值法不够用,再往机器学习或模型预测方向升级也不迟。
我个人习惯的做法是:先用动态阈值法跑通整个系统,同时采集力矩数据。等数据积累到一定程度,再尝试用机器学习法做精度提升。这样既保证了项目进度,又为后续优化留了空间。