数据预处理与特征工程:从原始传感器到可训练特征
在TinyML项目中,模型性能的上限往往不是由网络结构决定,而是由输入数据的质量决定。加速度计采集的原始信号包含噪声、重力分量、传感器偏置以及大量冗余信息,直接送入模型会导致两个问题:一是模型需要更深的网络来“隐式”提取特征,这超出了MCU的算力与内存预算;二是泛化能力差,换一个佩戴位置或设备,数据分布漂移就会让模型失效。本章将系统讲解从原始数据到可训练特征的标准流水线:数据清洗、去噪、滑动窗口分割、时域与频域特征提取,并给出在STM32平台上可落地的工程实现方案。
上图展示了本章的核心流水线。在STM32嵌入式环境中,每一步都必须在有限的RAM(通常几十KB)和CPU周期内完成。下面我们将深入每个环节的工程细节。
4.1 数据清洗:剔除异常与填补缺失
核心原理:加速度计原始数据流中,异常值主要来源于三类:传感器瞬时故障(如寄存器读取出错)、外部冲击(如设备跌落)、以及通信干扰(如I2C总线毛刺)。这些异常值如果不处理,会直接污染后续的统计特征——一个幅值达到±16g的尖峰,会让窗口内的方差增大数十倍,导致特征分布严重偏移。
判断方法:工程上采用两级校验:
- 物理范围校验:检查三轴数值是否超出传感器量程(如±2g/±4g/±8g/±16g)。超出量程的样本直接标记为无效。
- 差分突变检测:计算相邻样本的差分值
diff = |x[n] - x[n-1]|,若diff超过阈值(通常设为1.5g,因为人体运动最大加速度变化率不会超过此值),则判定为突变点。
对于缺失值或无效值,采用线性插值或前值保持。在MCU上,前值保持更节省算力,只需一个变量存储上一有效值。
4.2 信号去噪:低通滤波与重力分离
核心原理:加速度计信号包含两个成分:动态加速度(人体运动产生,频率0.5-5Hz)和静态加速度(重力分量,直流或极低频)。此外还有高频噪声(>10Hz,来自传感器机械振动和电路噪声)。去噪的目标是保留动态成分,抑制噪声,并去除重力偏置。
操作流程:
- 高通滤波:去除重力分量。一阶IIR高通滤波器:
y[n] = α * (y[n-1] + x[n] - x[n-1]),其中α=0.9时截止频率约0.8Hz(采样率50Hz)。 - 低通滤波:抑制高频噪声。一阶IIR低通:
y[n] = y[n-1] + α * (x[n] - y[n-1]),α=0.2时截止频率约5Hz。 - 级联顺序:先高通后低通,避免重力分量在低通滤波器中产生大的瞬态响应。
| 滤波器类型 | 公式 | 截止频率(50Hz采样) | 计算量 |
|---|---|---|---|
| 移动平均(窗口=5) | y[n]=(x[n]+x[n-1]+...+x[n-4])/5 | 约8Hz | 4次加法+1次除法 |
| 一阶IIR低通 | y[n]=y[n-1]+0.2*(x[n]-y[n-1]) | 约5Hz | 2次乘法+2次加法 |
| 一阶IIR高通 | y[n]=0.9*(y[n-1]+x[n]-x[n-1]) | 约0.8Hz | 3次乘法+3次加法 |
4.3 滑动窗口分割:构建时间序列样本
核心原理:单个采样点无法表达动作的时序特征,需要将连续数据流切分为固定长度的窗口。窗口长度决定了模型看到的时间跨度,步长决定了相邻窗口的重叠率。
参数选择方法:
- 窗口长度:应覆盖一个完整动作周期。走路一步约0.5-1s,因此窗口取2s(100个样本@50Hz)能包含2-4个步态周期。
- 步长:重叠率50%(步长=窗口/2)是最常用配置,既增加样本数量(数据增强),又不会产生过多冗余。
- 标签分配:窗口的标签取窗口中点时刻或末端时刻的真实标签。对于连续动作(如走路),两者差异不大;对于瞬态动作(如跌倒),建议取中点。
// STM32环形缓冲区窗口提取伪代码
#define WINDOW_SIZE 100 // 2s @ 50Hz
#define STEP_SIZE 50 // 1s步长
float buffer[3][WINDOW_SIZE]; // 三轴数据窗口
int write_idx = 0;
int read_idx = 0;
// 每采集一个样本调用
void process_sample(float ax, float ay, float az) {
buffer[0][write_idx] = ax;
buffer[1][write_idx] = ay;
buffer[2][write_idx] = az;
write_idx = (write_idx + 1) % WINDOW_SIZE;
// 每STEP_SIZE个样本触发一次特征提取
if (write_idx % STEP_SIZE == 0) {
extract_features(buffer, WINDOW_SIZE);
}
}
4.4 特征提取:时域与频域
核心原理:特征提取将原始信号压缩为低维向量,同时保留区分不同动作的关键信息。时域特征计算简单,适合MCU;频域特征能揭示周期性,但需要FFT计算。
时域特征(每轴计算):
- 均值:反映窗口内的平均加速度,去除重力后接近0。
- 方差:反映信号波动强度,走路时方差大,静止时接近0。
- 峰峰值:max-min,捕捉动作幅度。
- 过零率:信号穿越零点的次数,反映频率特性。
- 均方根:sqrt(mean(x²)),综合能量指标。
频域特征:
- 对窗口数据加汉宁窗(减少频谱泄漏),执行64点FFT。
- 取前32个频点的幅度谱(0-25Hz,因为50Hz采样奈奎斯特频率为25Hz)。
- 计算频带能量:将0-5Hz、5-10Hz、10-25Hz三个频带的能量求和。
| 特征类别 | 特征名称 | 维度(3轴) | 计算量 | 物理意义 |
|---|---|---|---|---|
| 时域 | 均值 | 3 | 100次加法 | 重力偏置/静态分量 |
| 时域 | 方差 | 3 | 100次乘加 | 运动强度 |
| 时域 | 过零率 | 3 | 100次比较 | 信号频率粗略估计 |
| 时域 | 峰峰值 | 3 | 100次比较 | 动作幅度范围 |
| 频域 | FFT幅度谱 | 32×3=96 | 64点FFT×3 | 频率成分分布 |
| 频域 | 频带能量 | 3×3=9 | FFT后累加 | 各频段能量占比 |
4.5 实战案例:基于STM32L4的跌倒检测特征工程
背景与约束:使用STM32L476RG(192KB RAM,80MHz主频),搭载LIS3DH加速度计(±16g量程,50Hz采样)。目标:在MCU上实时提取特征,送入一个3层全连接神经网络(输入维度20),实现跌倒/走路/站立三分类。
初始现象:直接使用原始数据训练,模型在测试集上准确率仅82%,且推理时RAM占用超标(原始数据窗口100×3×4字节=1200字节,加上模型中间变量,接近RAM上限)。
分析过程:
- 检查数据分布:发现原始数据包含重力分量(站立时Z轴约1g),导致特征均值偏移。
- 频谱分析:跌倒信号的FFT显示主要能量在0-3Hz,而走路在1-4Hz,站立接近0Hz。
- 确定特征集:均值(3) + 方差(3) + 峰峰值(3) + 过零率(3) + 频带能量(3个频带×3轴=9) = 21维,取前20维。
实施步骤:
// 特征提取核心代码(伪代码)
typedef struct {
float mean[3];
float var[3];
float peak_peak[3];
float zero_cross[3];
float band_energy[9]; // 3轴 × 3频带
} Features;
void extract_features(float buffer[3][100], Features *feat) {
// 1. 计算均值
for (int axis = 0; axis < 3; axis++) {
float sum = 0;
for (int i = 0; i < 100; i++) sum += buffer[axis][i];
feat->mean[axis] = sum / 100.0f;
}
// 2. 计算方差
for (int axis = 0; axis < 3; axis++) {
float sq_sum = 0;
for (int i = 0; i < 100; i++) {
float diff = buffer[axis][i] - feat->mean[axis];
sq_sum += diff * diff;
}
feat->var[axis] = sq_sum / 100.0f;
}
// 3. 峰峰值与过零率(略)
// 4. FFT频带能量(使用CMSIS-DSP库 arm_rfft_fast_f32)
// 5. 归一化:使用预先计算的均值和标准差
}
结果对比:
| 方案 | 特征维度 | 准确率 | RAM占用 | 单次推理时间 |
|---|---|---|---|---|
| 原始数据直接输入 | 300 | 82% | 2.4KB | 18ms |
| 仅时域特征 | 12 | 89% | 0.5KB | 3ms |
| 时域+频域特征 | 20 | 94% | 0.8KB | 6ms |
风险与代价:FFT计算增加了约3ms延迟,但换来了5%的准确率提升。若电池供电且对延迟敏感,可去掉频域特征,仅用时域特征。
4.6 常见误区与总结
误区一:滤波越强越好。过度滤波会滤除动作的瞬态特征(如跌倒的冲击峰值),导致误检。建议保留0.5-10Hz频带。
误区二:窗口越长越准。长窗口增加延迟,且可能包含多个动作的混合信号。对于实时系统,窗口长度应小于200ms(10个样本@50Hz)才能满足实时性要求。
误区三:特征越多越好。高维特征在MCU上不仅增加计算量,还可能导致过拟合。使用PCA或特征选择将维度控制在20以内。
误区四:忽略归一化。不同特征的量纲差异大(均值约0.1g,方差约0.01g²),必须进行Z-score归一化。在MCU上,使用预先计算的均值和标准差进行在线归一化,避免存储整个训练集。
arm_rfft_fast_f32函数,它针对Cortex-M4/M7做了优化,比手写FFT快5-10倍。同时注意FFT输入缓冲区需要2倍长度(实部+虚部),防止内存越界。
本章完整覆盖了从原始加速度计数据到可训练特征的转换链路。在实际项目中,建议先在PC端用Python完成特征工程验证,再将验证过的参数(滤波系数、窗口长度、特征列表)固化到STM32代码中。下一阶段,我们将讨论如何将这些特征输入到TinyML模型中,并完成模型在STM32上的部署与优化。