☰
足部IMU步态追踪:去除重力与零速修正实战
2026/10/22 9:01:24 网站建设 项目流程

简介:这份资源围绕基于x-IMU的步态追踪与重力校正技术展开,面向从事生物力学、康复医学、运动科学研究的开发者与学习者,帮助解决加速度计数据中重力分量干扰动态运动分析的问题。包内共52个文件,以40个MATLAB脚本(.m)为核心,辅以CSV数据集、BIN二进制数据、PNG效果截图及DOCX说明文档,压缩包约3.21MB,涵盖AHRS姿态解算、六自由度动画、四元数处理等模块,目录结构清晰便于按功能检索。目前已有653人学习下载。读者可从中获取去除重力与零点校准的实现思路、低通与卡尔曼滤波等信号处理代码,以及足部触地离地识别、髋关节摆动支撑相分析等步态周期解析方法,适合用于临床步态异常评估与运动表现优化的实践参考。

1. 从一段晃动的加速度曲线说起:Gait Tracking 到底在追什么

把一枚六轴 IMU 绑在脚踝上,让人走十步,你拿到的三轴加速度曲线大概率是一团糟:抬脚瞬间冲到 2g,落地瞬间又砸到 -3g,中间还夹着各种抖动。很多人第一次做 Gait Tracking 就卡在这里——明明传感器精度不差,为什么步态相位、步长、着地时刻全都算不准?答案往往不在滤波算法,而在一个被忽略的前置步骤:去除重力。

Gait Tracking With x-IMU 这套思路,核心就是围绕足部 IMU 做惯性步态追踪:用加速度计和陀螺仪估计足部姿态,把重力分量从加速度里剥掉,剩下的才是真正由运动产生的线性加速度,再对它做积分和事件检测,才能得到步态周期、支撑相/摆动相、甚至粗略的位移。它适合做可穿戴步态分析、康复评估、运动监测的工程师,也适合手里只有消费级 IMU、想把数据用起来的开发者。这一篇不讲空泛概念,只讲怎么把这条链路跑通、参数怎么设、哪里最容易翻车。

2. 去除重力:姿态估计与重力补偿的最小闭环

2.1 为什么不能直接对加速度积分

加速度计测的是比力,不是纯运动加速度。静止平放时,Z 轴读数约 1g,这不是运动,是重力反作用。如果你直接对三轴加速度做二次积分求位移,重力这个常量会随时间累积成一条抛物线漂移,几秒钟就能把位移算到几米开外,这就是典型的“积分发散”。

所以 Gait Tracking 的第一步永远是姿态解算:先知道传感器当前相对地理坐标系的朝向,才能把重力向量投影到传感器坐标系里减掉。常见做法是用陀螺仪积分得到姿态,再用加速度计在低频段修正陀螺漂移——这就是互补滤波或 Madgwick/Mahony 这类姿态融合算法的用武之地。

提示:去除重力和姿态估计是同一件事的两面。姿态不准,重力方向就投不准,减完还剩残差,后续积分照样漂。

2.2 用四元数做重力补偿的核心代码

下面这段是姿态更新加去重力的最小实现,用四元数表示姿态,输入是陀螺仪角速度和加速度计读数,输出是去掉重力后的线性加速度。参数按常见消费级 IMU 的 100Hz 采样设定。

import numpy as np def quat_multiply(q, r): w1, x1, y1, z1 = q w2, x2, y2, z2 = r return np.array([ w1*w2 - x1*x2 - y1*y2 - z1*z2, w1*x2 + x1*w2 + y1*z2 - z1*y2, w1*y2 - x1*z2 + y1*w2 + z1*x2, w1*z2 + x1*y2 - y1*x2 + z1*w2 ]) def update_quaternion(q, gyro, dt): # gyro 单位 rad/s,dt 单位秒 omega = np.array([0, gyro[0], gyro[1], gyro[2]]) dq = 0.5 * quat_multiply(q, omega) * dt q = q + dq return q / np.linalg.norm(q) # 必须归一化,否则数值漂移 def remove_gravity(q, acc): # 地理坐标系下重力方向为 [0, 0, 1](单位向量,g 归一化) # 把地理系重力旋转到传感器坐标系:q 的共轭作用 q_conj = np.array([q[0], -q[1], -q[2], -q[3]]) g_geo = np.array([0, 0, 0, 1]) g_sensor = quat_multiply(quat_multiply(q_conj, g_geo), q) gravity = g_sensor[1:4] # 取向量部分 linear_acc = acc - gravity return linear_acc

逻辑说明:update_quaternion用一阶龙格库塔对四元数微分方程做离散积分,dt是采样间隔,100Hz 对应 0.01 秒。remove_gravity先把地理系的重力单位向量通过姿态四元数旋转到传感器坐标系,再从原始加速度里减掉。注意这里加速度和重力都做了归一化处理,实际使用时如果加速度计输出是 m/s²,重力向量要乘 9.81。

参数说明:dt必须和真实采样周期一致,用固定值而不是实测时间差,否则抖动会引入额外噪声。四元数每步都要归一化,这是防止数值发散的硬性要求,漏掉这一步跑几十秒姿态就废了。

2.3 互补滤波修正陀螺漂移

纯陀螺积分几分钟就会漂,必须用加速度计的低频信息拉回来。互补滤波是最省算力的做法:高频信陀螺,低频信加速度。

def complementary_filter(q, gyro, acc, dt, alpha=0.98): # alpha 越大越信陀螺,越小越信加速度计 q_gyro = update_quaternion(q, gyro, dt) # 用加速度计估计的重力方向做误差修正(简化版) acc_norm = acc / np.linalg.norm(acc) # 期望重力方向在传感器系下的投影 q_conj = np.array([q[0], -q[1], -q[2], -q[3]]) g_pred = quat_multiply(quat_multiply(q_conj, np.array([0,0,0,1])), q)[1:4] error = np.cross(acc_norm, g_pred) # 把误差反馈到角速度上 gyro_corrected = gyro + 0.5 * error return update_quaternion(q, gyro_corrected, dt)

alpha取 0.98 是 100Hz 下的经验值,对应约 0.5 秒的时间常数。误差项用叉积构造,是因为叉积方向正好指向需要旋转的轴。这个修正量系数 0.5 需要根据实际抖动调,太大姿态会跟着加速度噪声跳,太小漂移压不住。

3. 从线性加速度到步态事件:峰值检测与相位划分

3.1 步态周期的事件定义

去掉重力后,足部线性加速度的竖直分量会呈现明显的周期性:摆动中期有一个正向峰,脚跟着地时有一个负向冲击峰。Gait Tracking 通常关心四个事件:脚跟着地、脚尖离地、摆动中期、支撑中期。工程上最稳的是先检测着地冲击,再往前推摆动相。

常见做法是对竖直线性加速度做低通滤波(截止 10~15Hz),然后找超过阈值的负峰。阈值不能写死,因为不同人步速差异大,我一般用滑动窗口内的动态阈值:取最近 1 秒数据的标准差乘以系数。

3.2 峰值检测与步态参数计算

from scipy.signal import find_peaks, butter, filtfilt def lowpass(sig, fs=100, cutoff=12): b, a = butter(2, cutoff / (fs / 2), btype='low') return filtfilt(b, a, sig) # 零相位滤波,避免事件时刻偏移 def detect_gait_events(linear_acc_z, fs=100): sig = lowpass(linear_acc_z, fs) # 动态阈值:负峰检测,取反后找峰 win = int(0.5 * fs) thr = -np.std(sig[:win]) * 1.5 peaks, props = find_peaks(-sig, height=-thr, distance=int(0.3*fs)) # distance 限制最小步间隔 0.3s,防止一步多检 heel_strike = peaks return heel_strike, sig

逻辑说明:filtfilt做双向滤波,相位零延迟,这对事件时刻精度很关键,普通lfilter会引入群延迟导致着地时刻系统性偏移。find_peaks里distance参数限制两个峰之间至少 0.3 秒,对应最快约 200 步/分的步频,正常人不会超过。height用负阈值是因为着地是负向冲击。

参数说明:截止频率 12Hz 是经验值,跑步场景可以放到 20Hz,慢走可以降到 8Hz。动态阈值系数 1.5 偏保守,漏检多就降到 1.0,误检多就升到 2.0。这些参数没有万能值,必须拿自己的数据调。

3.3 步长估计的坑:别信二次积分

很多人想直接对线性加速度二次积分算步长,我劝你早点放弃。消费级 IMU 的零偏哪怕只有 0.01 m/s²,积分 1 秒位移误差就有 5mm,10 秒就是半米。工程上更靠谱的是用经验模型:步长和步频、身高的回归关系,或者用摆动相时长乘以一个系数。Gait Tracking 里位移估计通常只做相对参考,不做绝对精度承诺。

4. 避坑与排查:去重力链路里最容易翻车的五件事

4.1 现象:静止时线性加速度不为零,缓慢漂移

原因:姿态四元数没有归一化,或者陀螺零偏没标定。陀螺静止时输出不是严格零,几度每秒的零偏积分几十秒就是大角度误差。

解决:上电后先静置 2 秒采集陀螺均值做零偏补偿,每步更新后强制归一化四元数。如果还漂,检查加速度计是否做了尺度校准。

4.2 现象:着地时刻检测总是偏早或偏晚

原因:用了lfilter而不是filtfilt,滤波器群延迟让信号整体后移;或者低通截止频率设太低,冲击峰被削平。

解决:换零相位滤波,截止频率不低于 10Hz。用高速相机或测力台做一次对照,把系统偏移量标出来再补偿。

4.3 现象:不同人用同一套阈值,有人漏检有人多检

原因:写死阈值对步速和体重敏感。体重大的人冲击峰大,步速慢的人周期长。

解决:改成滑动窗口动态阈值,窗口长度取 1~2 个步态周期。或者先做步频估计,再按步频查表设阈值。

4.4 现象:四元数更新后姿态抖动剧烈

原因:加速度计噪声直接进了误差修正项,修正系数太大。

解决:先对加速度计做低通再算误差,或者降低修正系数。互补滤波的 alpha 本质就是信噪比权衡,没有免费午餐。

4.5 现象:积分几秒后位移完全不可用

原因:线性加速度里还有残余重力分量和零偏,二次积分放大一切误差。

解决:接受现实,位移只用短时相对值,或者引入零速修正——检测到脚完全着地时把速度强制归零,这是抑制漂移最有效的手段。

5. 零速修正与在线校准:让 Gait Tracking 撑过一分钟

前面所有步骤跑通后,你会发现一个残酷事实:纯积分撑不过十几秒。真正让 Gait Tracking 能用的,是零速修正(ZUPT)。原理很简单:脚在支撑相完全着地的那段时间,足部速度理论上为零。检测到这段区间,就把速度积分强制清零,同时用这段静止数据反过来估计陀螺零偏和姿态误差。

实现上,先靠线性加速度的方差判断静止:滑动窗口内三轴加速度方差都低于阈值,就认为是零速区间。然后在零速区间内做两件事:速度归零、用加速度计重估姿态。这一步做完,位移漂移能从米级压到分米级。

def zupt_update(velocity, acc_window, gyro_bias, threshold=0.05): # acc_window: 最近 N 帧线性加速度,N 约 10 var = np.var(acc_window, axis=0) if np.all(var < threshold): velocity[:] = 0.0 # 速度清零 # 静止时陀螺输出即为零偏估计 gyro_bias = 0.99 * gyro_bias + 0.01 * np.mean(acc_window, axis=0) return velocity, gyro_bias

阈值 0.05 是线性加速度方差的经验值,单位是 (m/s²)²,实际要按传感器噪声水平调。零偏估计用一阶低通缓慢更新,系数 0.01 对应约 100 帧的时间常数,避免被单帧噪声带偏。

我自己的习惯是:每拿到一批新数据,先跑一遍纯姿态去重力,看静止段线性加速度是否收敛到零附近;不收敛就先查标定,别急着调后面的检测参数。步态追踪这活儿,前面脏了后面全白搭。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询