数据预处理与特征工程:从原始传感器到可训练特征

在TinyML项目中,模型性能的上限往往不是由网络结构决定,而是由输入数据的质量决定。加速度计采集的原始信号包含噪声、重力分量、传感器偏置以及大量冗余信息,直接送入模型会导致两个问题:一是模型需要更深的网络来“隐式”提取特征,这超出了MCU的算力与内存预算;二是泛化能力差,换一个佩戴位置或设备,数据分布漂移就会让模型失效。本章将系统讲解从原始数据到可训练特征的标准流水线:数据清洗、去噪、滑动窗口分割、时域与频域特征提取,并给出在STM32平台上可落地的工程实现方案。

加速度计数据预处理与特征工程流水线 原始数据 Ax/Ay/Az @ 50Hz 含噪声/重力/偏置 清洗与去噪 异常值剔除 低通滤波/高通滤波 滑动窗口分割 窗口=2s, 步长=1s 重叠率50% 特征提取 时域: 均值/方差/过零率 频域: FFT/能量谱 清洗关键点 • 三轴范围校验 ±16g • 突变点检测(差分阈值) • 缺失值线性插值 • 重力分量去除(高通) 滤波方案 • 移动平均(窗口=5) • 一阶IIR低通 α=0.2 • 截止频率≈5Hz • 相位延迟补偿 窗口参数 • 窗口长度: 100样本 • 步长: 50样本 • 每窗口3轴=300值 • 标签对齐: 窗口末端 特征维度 • 时域: 均值/方差/峰峰值 • 频域: FFT 64点/能量 • 统计: 过零率/偏度 • 输出: 15-30维特征 输出: 特征矩阵 [N×D] N=窗口数, D=特征维度 → 送入TinyML模型

上图展示了本章的核心流水线。在STM32嵌入式环境中,每一步都必须在有限的RAM(通常几十KB)和CPU周期内完成。下面我们将深入每个环节的工程细节。

4.1 数据清洗:剔除异常与填补缺失

核心原理:加速度计原始数据流中,异常值主要来源于三类:传感器瞬时故障(如寄存器读取出错)、外部冲击(如设备跌落)、以及通信干扰(如I2C总线毛刺)。这些异常值如果不处理,会直接污染后续的统计特征——一个幅值达到±16g的尖峰,会让窗口内的方差增大数十倍,导致特征分布严重偏移。

判断方法:工程上采用两级校验:

  1. 物理范围校验:检查三轴数值是否超出传感器量程(如±2g/±4g/±8g/±16g)。超出量程的样本直接标记为无效。
  2. 差分突变检测:计算相邻样本的差分值 diff = |x[n] - x[n-1]|,若diff超过阈值(通常设为1.5g,因为人体运动最大加速度变化率不会超过此值),则判定为突变点。

对于缺失值或无效值,采用线性插值或前值保持。在MCU上,前值保持更节省算力,只需一个变量存储上一有效值。

STM32实现要点:使用DMA双缓冲采集加速度计数据,在数据回调函数中执行清洗逻辑。注意清洗操作必须在写入环形缓冲区之前完成,避免脏数据进入后续处理链。

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约8Hz4次加法+1次除法
一阶IIR低通y[n]=y[n-1]+0.2*(x[n]-y[n-1])约5Hz2次乘法+2次加法
一阶IIR高通y[n]=0.9*(y[n-1]+x[n]-x[n-1])约0.8Hz3次乘法+3次加法
经验值:对于人体活动识别(走路、跑步、站立),采样率50Hz足够,滤波截止频率5Hz能保留主要运动能量。若做手势识别(快速动作),截止频率需提高到10Hz。

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);
    }
}
注意:窗口边界效应。如果动作跨越窗口边界,特征会被稀释。解决方法是使用重叠窗口,并确保窗口长度大于动作周期。对于跌倒检测,窗口长度建议3s,因为跌倒过程包含失重-撞击-静止三个阶段,总时长约2-3s。

4.4 特征提取:时域与频域

核心原理:特征提取将原始信号压缩为低维向量,同时保留区分不同动作的关键信息。时域特征计算简单,适合MCU;频域特征能揭示周期性,但需要FFT计算。

时域特征(每轴计算):

  • 均值:反映窗口内的平均加速度,去除重力后接近0。
  • 方差:反映信号波动强度,走路时方差大,静止时接近0。
  • 峰峰值:max-min,捕捉动作幅度。
  • 过零率:信号穿越零点的次数,反映频率特性。
  • 均方根:sqrt(mean(x²)),综合能量指标。

频域特征:

  1. 对窗口数据加汉宁窗(减少频谱泄漏),执行64点FFT。
  2. 取前32个频点的幅度谱(0-25Hz,因为50Hz采样奈奎斯特频率为25Hz)。
  3. 计算频带能量:将0-5Hz、5-10Hz、10-25Hz三个频带的能量求和。
特征类别特征名称维度(3轴)计算量物理意义
时域均值3100次加法重力偏置/静态分量
时域方差3100次乘加运动强度
时域过零率3100次比较信号频率粗略估计
时域峰峰值3100次比较动作幅度范围
频域FFT幅度谱32×3=9664点FFT×3频率成分分布
频域频带能量3×3=9FFT后累加各频段能量占比
特征选择策略:对于STM32部署,建议优先选择时域特征(均值、方差、峰峰值、过零率),共12维。若分类精度不足,再增加频域特征。总特征维度控制在20-30维以内,避免模型输入过大导致内存溢出。

4.5 实战案例:基于STM32L4的跌倒检测特征工程

背景与约束:使用STM32L476RG(192KB RAM,80MHz主频),搭载LIS3DH加速度计(±16g量程,50Hz采样)。目标:在MCU上实时提取特征,送入一个3层全连接神经网络(输入维度20),实现跌倒/走路/站立三分类。

初始现象:直接使用原始数据训练,模型在测试集上准确率仅82%,且推理时RAM占用超标(原始数据窗口100×3×4字节=1200字节,加上模型中间变量,接近RAM上限)。

分析过程:

  1. 检查数据分布:发现原始数据包含重力分量(站立时Z轴约1g),导致特征均值偏移。
  2. 频谱分析:跌倒信号的FFT显示主要能量在0-3Hz,而走路在1-4Hz,站立接近0Hz。
  3. 确定特征集:均值(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占用单次推理时间
原始数据直接输入30082%2.4KB18ms
仅时域特征1289%0.5KB3ms
时域+频域特征2094%0.8KB6ms

风险与代价:FFT计算增加了约3ms延迟,但换来了5%的准确率提升。若电池供电且对延迟敏感,可去掉频域特征,仅用时域特征。

复盘经验:特征工程的核心是“匹配任务”。跌倒检测中,方差和峰峰值是最具区分度的特征;而手势识别中,过零率和频带能量更重要。建议先用Python离线分析特征重要性(如随机森林特征重要性排序),再在MCU上实现选中的特征。

4.6 常见误区与总结

误区一:滤波越强越好。过度滤波会滤除动作的瞬态特征(如跌倒的冲击峰值),导致误检。建议保留0.5-10Hz频带。

误区二:窗口越长越准。长窗口增加延迟,且可能包含多个动作的混合信号。对于实时系统,窗口长度应小于200ms(10个样本@50Hz)才能满足实时性要求。

误区三:特征越多越好。高维特征在MCU上不仅增加计算量,还可能导致过拟合。使用PCA或特征选择将维度控制在20以内。

误区四:忽略归一化。不同特征的量纲差异大(均值约0.1g,方差约0.01g²),必须进行Z-score归一化。在MCU上,使用预先计算的均值和标准差进行在线归一化,避免存储整个训练集。

工程提醒:在STM32上实现FFT时,务必使用CMSIS-DSP库的arm_rfft_fast_f32函数,它针对Cortex-M4/M7做了优化,比手写FFT快5-10倍。同时注意FFT输入缓冲区需要2倍长度(实部+虚部),防止内存越界。

本章完整覆盖了从原始加速度计数据到可训练特征的转换链路。在实际项目中,建议先在PC端用Python完成特征工程验证,再将验证过的参数(滤波系数、窗口长度、特征列表)固化到STM32代码中。下一阶段,我们将讨论如何将这些特征输入到TinyML模型中,并完成模型在STM32上的部署与优化。