3、数据采集与预处理:传感器数据同步采集、滑动窗口与数据切片、归一化与标准化方法、数据标注与存储(CSV格式)
大家好,我是你们的嵌入式AI讲师。今天咱们聊聊数据采集与预处理。说实话,很多同学把精力都花在模型训练上,却忽略了数据预处理这个环节。我踩过的坑告诉我——数据预处理做不好,后面全是白忙活。
3.1 传感器数据同步采集
先说说同步采集。STM32上挂多个传感器,比如加速度计、陀螺仪、磁力计,每个传感器都有自己的采样率。有的能跑到1kHz,有的只能到100Hz。你想想看,如果不同步,时间戳对不上,后面做融合分析就全乱套了。
我个人的习惯是:用一个统一的定时器触发所有传感器的读取。比如用TIM2产生一个1ms的中断,在中断里依次读取各个传感器的数据。这样能保证所有数据在同一个时间基准上。
核心要点:同步采集的关键是统一时间基准,而不是同时读取。因为不同传感器的转换时间不同,同时读取反而可能造成数据错位。
代码示例(基于STM32CubeMX生成的HAL库):
// 定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
// 读取加速度计数据
accel_data = read_accel();
// 读取陀螺仪数据
gyro_data = read_gyro();
// 读取磁力计数据
mag_data = read_mag();
// 将数据存入缓冲区
data_buffer[write_index].accel = accel_data;
data_buffer[write_index].gyro = gyro_data;
data_buffer[write_index].mag = mag_data;
data_buffer[write_index].timestamp = HAL_GetTick();
write_index = (write_index + 1) % BUFFER_SIZE;
}
}
小技巧:如果传感器支持FIFO模式,建议开启。比如MPU6050的FIFO可以缓存多组数据,这样即使主循环被其他任务阻塞,数据也不会丢失。我在做无人机姿态解算时就用过这个功能,效果很好。
3.2 滑动窗口与数据切片
数据采集完了,接下来就是切片。为什么要切片?因为我们的AI模型通常需要固定长度的输入。比如你要做手势识别,一个手势动作可能持续0.5秒到2秒,你不能把整个序列都丢进模型吧?
滑动窗口就是解决这个问题的。我一般用50%重叠的滑动窗口。举个例子,窗口大小是100个采样点,步长是50个点。这样第一个窗口是0-99,第二个窗口是50-149,以此类推。
为什么会用50%重叠?说白了,是为了增加训练样本的数量。你想想看,如果数据量不够,模型很容易过拟合。重叠采样相当于把有限的数据用到了极致。
// 滑动窗口切片函数
void sliding_window(float *data, int data_len, int window_size, int step)
{
int num_windows = (data_len - window_size) / step + 1;
for (int i = 0; i < num_windows; i++)
{
int start = i * step;
// 提取当前窗口的数据
for (int j = 0; j < window_size; j++)
{
window_data[i][j] = data[start + j];
}
// 这里可以保存窗口数据到文件或送入模型
}
}
注意:窗口大小和步长的选择直接影响模型性能。窗口太小,信息不完整;窗口太大,计算量暴增。我建议根据你的应用场景来定。比如做步态识别,窗口大小设为1秒的采样点数比较合适。
3.3 归一化与标准化方法
数据切片完了,接下来就是归一化。为什么要归一化?因为不同传感器的量纲不一样。加速度计输出的是m/s²,陀螺仪输出的是°/s,磁力计输出的是μT。如果不归一化,数值大的特征会主导模型训练,数值小的特征就被淹没了。
常用的方法有两种:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Min-Max归一化 | x' = (x - min) / (max - min) | 数据分布有明确边界,比如加速度计输出范围已知 |
| Z-score标准化 | x' = (x - μ) / σ | 数据分布近似正态分布,或者有异常值 |
我个人更倾向于用Min-Max归一化,因为简单直观。但要注意,归一化的参数(min和max)必须从训练集计算,然后应用到验证集和测试集。千万别用整个数据集计算,否则会造成数据泄露。
避坑指南:我曾经犯过一个错误——在归一化时用了全局的min和max。结果模型在部署时,遇到新的数据超出了这个范围,输出直接崩了。后来我改用Z-score标准化,并且定期更新均值和标准差,问题才解决。
代码示例(Min-Max归一化):
// Min-Max归一化函数
void min_max_normalize(float *data, int len, float min_val, float max_val)
{
for (int i = 0; i < len; i++)
{
data[i] = (data[i] - min_val) / (max_val - min_val);
}
}
// 使用示例
float accel_min = -16.0; // 加速度计量程 ±16g
float accel_max = 16.0;
min_max_normalize(accel_data, ACCEL_LEN, accel_min, accel_max);
3.4 数据标注与存储(CSV格式)
最后一步,标注和存储。标注是监督学习的基础。你采集了10000组数据,如果不标注,模型根本不知道这些数据对应什么动作。
我的标注策略:在采集数据的同时,通过串口或按键输入标签。比如按1代表“走路”,按2代表“跑步”,按3代表“静止”。这样数据采集和标注同步进行,省去了后期手动标注的麻烦。
存储格式我推荐CSV。为什么?因为CSV是纯文本,通用性强,Python、MATLAB、Excel都能直接打开。而且STM32的SD卡驱动对CSV的支持很好。
CSV文件格式示例:
timestamp,accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z,mag_x,mag_y,mag_z,label
1000,0.12,-0.34,9.81,0.01,0.02,-0.01,12.3,-5.6,45.2,1
1001,0.13,-0.35,9.80,0.02,0.01,-0.02,12.4,-5.7,45.1,1
1002,0.11,-0.33,9.82,0.01,0.03,-0.01,12.2,-5.5,45.3,1
代码示例(将数据写入CSV文件):
// 将数据写入CSV文件
void write_csv(FIL *file, sensor_data_t *data, int label)
{
char buffer[128];
sprintf(buffer, "%lu,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d\r\n",
data->timestamp,
data->accel.x, data->accel.y, data->accel.z,
data->gyro.x, data->gyro.y, data->gyro.z,
data->mag.x, data->mag.y, data->mag.z,
label);
UINT bytes_written;
f_write(file, buffer, strlen(buffer), &bytes_written);
}
小建议:在CSV文件的第一行写入列名,这样后续用Python的pandas读取时,可以直接用列名访问数据,非常方便。另外,建议每采集100组数据就flush一次文件缓冲区,防止突然断电导致数据丢失。
好了,数据采集与预处理的内容就这些。总结一下:同步采集保证时间对齐,滑动窗口解决变长输入问题,归一化消除量纲影响,CSV存储方便后续处理。下一章我们聊聊特征工程,到时候会用到今天处理好的数据。
嗯,今天就到这里。有什么问题欢迎在评论区留言,或者直接找我交流。咱们下期见!
公众号:蓝海资料掘金营,微信deep3321