运动分类与异常检测
概述
运输业是全球商业的基石。每天有数百万个集装箱通过船舶、卡车和火车等多种方式运往世界各地。确保这些集装箱安全高效地运输是一项巨大的挑战,需要借助现代技术,而 TinyML 无疑是关键解决方案之一。
在本实验中,我们将聚焦于运输领域的实际问题。你将使用 XIAOML Kit、Arduino IDE 和 Edge Impulse Studio,开发一个运动分类与异常检测系统。通过本项目,你将深入理解集装箱在陆运、海运、叉车垂直搬运及仓库存储等不同阶段所经历的各种力与运动。
学习目标
- XIAOML Kit 硬件环境搭建
- 数据采集与预处理
- 构建运动分类模型
- 实现异常检测
- 实际测试与分析
完成本实验后,你将拥有一个可用于集装箱运输过程中运动分类与异常检测的原型系统。这将为你后续深入 TinyML 尤其是振动相关项目打下坚实基础。
IMU 传感器安装
XIAOML Kit 的扩展板上集成了 LSM6DS3TR-C 六轴 IMU 传感器,无需外接传感器,极大简化了硬件连接,适合运动类机器学习应用。
LSM6DS3TR-C 集成了三轴加速度计和三轴陀螺仪,通过 I2C 总线(地址 0x6A)与 XIAO ESP32S3 连接,主要参数如下:
- 加速度计量程:±2/±4/±8/±16 g(默认使用 ±2g)
- 陀螺仪量程:±125/±250/±500/±1000/±2000 dps(默认使用 ±250 dps)
- 分辨率:16 位 ADC
- 通信方式:I2C,地址 0x6A
- 供电:超低功耗设计

坐标系说明: 传感器采用右手坐标系。从扩展板底部(可见 IMU 芯片及点标记)观察:
- X 轴:指向右侧
- Y 轴:指向前方(远离你)
- Z 轴:指向上方(垂直于电路板)
硬件准备
XIAOML Kit 已预装扩展板,无需额外硬件连接,IMU 已通过 I2C 正确连接。
已连接部分:
- LSM6DS3TR-C IMU → I2C(SDA/SCL)→ XIAO ESP32S3
- I2C 地址:0x6A
- 供电:3.3V(由 XIAO ESP32S3 提供)
所需库: 推荐在环境搭建时已安装。如未安装,请按以下步骤安装 Seeed Arduino LSM6DS3 库:
- 打开 Arduino IDE 库管理器
- 搜索 “LSM6DS3”
- 安装 “Seeed Arduino LSM6DS3”(作者 Seeed Studio)
- 注意:不要安装 “Arduino_LSM6DS3 by Arduino”,该库适用于其他开发板!
IMU 传感器测试
先上传如下代码,验证 IMU 是否正常工作:
#include <LSM6DS3.h>
#include <Wire.h>
// 创建 IMU 对象,使用 I2C 接口
LSM6DS3 myIMU(I2C_MODE, 0x6A);
float accelX, accelY, accelZ;
float gyroX, gyroY, gyroZ;
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("XIAOML Kit IMU 测试");
Serial.println("LSM6DS3TR-C 六轴 IMU");
Serial.println("====================");
// 初始化 IMU
if (myIMU.begin() != 0) {
Serial.println("错误:IMU 初始化失败!");
while(1) delay(1000);
} else {
Serial.println("✓ IMU 初始化成功");
Serial.println("数据格式:AccelX,AccelY,AccelZ,GyroX,GyroY,GyroZ");
Serial.println("单位:g-force, 度/秒");
Serial.println();
}
}
void loop() {
// 读取加速度(g)
accelX = myIMU.readFloatAccelX();
accelY = myIMU.readFloatAccelY();
accelZ = myIMU.readFloatAccelZ();
// 读取陀螺仪(度/秒)
gyroX = myIMU.readFloatGyroX();
gyroY = myIMU.readFloatGyroY();
gyroZ = myIMU.readFloatGyroZ();
// 打印数据
Serial.print("Accel (g): X="); Serial.print(accelX, 3);
Serial.print(" Y="); Serial.print(accelY, 3);
Serial.print(" Z="); Serial.print(accelZ, 3);
Serial.print(" | Gyro (°/s): X="); Serial.print(gyroX, 2);
Serial.print(" Y="); Serial.print(gyroY, 2);
Serial.print(" Z="); Serial.println(gyroZ, 2);
delay(100); // 10 Hz 更新率
}
当开发板平放时,你应看到:
- Z 轴加速度约为 +1.0g(重力方向)
- X、Y 轴加速度接近 0.0g
- 陀螺仪各轴接近 0.0°/s
移动开发板,数值会随之变化。
TinyML 运动分类项目
我们将通过手动模拟不同运输场景,采集运动数据,使教程更贴近实际。

利用 XIAOML Kit 的加速度计,采集以下场景的运动数据:
- 海运(船上托盘):三轴均有波浪式运动
- 陆运(卡车/火车托盘):主要为水平运动
- 升降(叉车搬运):主要为垂直运动
- 静止(仓库存储):几乎无运动
如上图所示,我们定义:主要水平运动($x$ 或 $y$ 轴)归为“陆运类”,垂直运动($z$ 轴)归为“升降类”,无运动归为“静止类”,三轴均有运动归为“海运类”。 参考

数据采集
数据采集有多种方式。实际应用中,可将设备固定于集装箱,采集的数据存储为 CSV 文件(如存储于 SD 卡),也可通过 Wi-Fi 或蓝牙远程发送至手机等设备(参考项目: Sensor DataLogger )。采集到的 .CSV 文件可通过 CSV Wizard 工具 上传至 Edge Impulse Studio。
你也可以通过 本视频 了解更多数据上传方式。
数据采集代码准备
本实验中,我们将开发板直接连接 Edge Impulse Studio,利用 Studio 进行数据预处理、模型训练、测试与部署。
由于 XIAOML Kit 目前尚未被 Edge Impulse 官方完全支持,我们需使用 CLI Data Forwarder 将串口数据转发至 Studio,流程如下图:

请将测试代码修改为 Edge Impulse 兼容格式:
#include <LSM6DS3.h>
#include <Wire.h>
#define FREQUENCY_HZ 50
#define INTERVAL_MS (1000 / (FREQUENCY_HZ + 1))
LSM6DS3 myIMU(I2C_MODE, 0x6A);
static unsigned long last_interval_ms = 0;
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("XIAOML Kit - 运动数据采集");
Serial.println("LSM6DS3TR-C IMU 传感器");
// 初始化 IMU
if (myIMU.begin() != 0) {
Serial.println("错误:IMU 初始化失败!");
while(1) delay(1000);
}
delay(2000);
Serial.println("3 秒后开始采集数据...");
delay(3000);
}
void loop() {
if (millis() > last_interval_ms + INTERVAL_MS) {
last_interval_ms = millis();
// 读取加速度
float ax = myIMU.readFloatAccelX();
float ay = myIMU.readFloatAccelY();
float az = myIMU.readFloatAccelZ();
// 转换为 m/s²(乘以 9.81)
float ax_ms2 = ax * 9.81;
float ay_ms2 = ay * 9.81;
float az_ms2 = az * 9.81;
// 按 Edge Impulse 格式输出
Serial.print(ax_ms2);
Serial.print("\t");
Serial.print(ay_ms2);
Serial.print("\t");
Serial.println(az_ms2);
}
}
上传代码后,在 Arduino IDE 的串口监视器中可看到加速度(m/s²)数据:

保持代码运行,但关闭串口监视器,否则数据无法被 CLI 正确转发至 Edge Impulse Studio。
连接 Edge Impulse 进行数据采集
创建 Edge Impulse 项目
- 访问 Edge Impulse Studio,创建新项目
- 命名建议简洁明了(不超过 63 字符,便于后续 Arduino 库兼容)

配置 CLI Data Forwarder
- 在电脑上安装 Edge Impulse CLI
- 确认 XIAOML Kit 已连接且代码运行,关闭串口监视器
- 终端运行:
edge-impulse-data-forwarder --clean
- 输入 Edge Impulse 账号信息
- 选择项目并配置设备

- 在 Studio 的
Device
页面,若设备已连接,状态点为绿色

Studio 端数据采集
如前所述,需采集四类运输场景的数据。假设你手持带加速度计的集装箱(即 XIAOML Kit),模拟如下场景:

或在卡车、叉车等场景下采集。
运动模拟
海运类:
- 手持开发板,模拟船舶运动
- 三轴均做波浪式、起伏运动
- 包含轻微滚转和俯仰
陆运类:
- 水平直线移动开发板(左右来回)
- 模拟卡车/火车振动(小幅水平抖动)
- 偶尔轻微颠簸和转向
升降类:
- 主要做垂直方向(上下)运动
- 模拟叉车操作:上升、暂停、下降
- 可包含少量水平定位动作
静止类:
- 将开发板平放于稳定表面
- 基本无运动
- 采集环境振动与传感器噪声
数据采集流程
在 Data Acquisition
页面,确认设备 [xiaoml-kit]
已连接,传感器为 [sensor with 3 axes (accX, accY, accZ)]
,采样频率 [50 Hz]
,建议采样时长 [10000]
ms(10 秒)。设置样本标签(如 [terrestrial]
),点击 [Start Sample]
,水平移动开发板,采集 10 秒数据,数据自动上传至 Studio。
下图为 10 秒陆运数据样本。可见 Y 轴(左右)为主,X 轴接近零,Z 轴约为 9.8 ms²(重力)。

建议每类采集约 2 分钟(10~12 个 10 秒样本),每类选 2 个样本移至 Test set。也可在 Dashboard
页 Danger Zone
区使用自动分割工具。下图为数据集示例:

数据预处理
加速度计采集的原始数据为“时间序列”,需通过滑动窗口转为“表格数据”。如下图所示:

以 50 Hz 采样 10 秒数据,2 秒窗口可采集 300 个数据点(3 轴 × 2 秒 × 50 次)。每 200ms 滑动一次窗口,生成更多样本,每个样本含 300 个原始特征。
采样频率建议遵循奈奎斯特定理:采样频率需大于信号最高频率的两倍。
嵌入式机器学习中的数据预处理较为复杂,Edge Impulse 提供了 DSP 预处理模块,尤其是频谱特征(Spectral Features)。
在 Studio 中,数据将输入 Spectral Analysis 块,适合分析加速度计等周期性运动数据。该块会提取如 FFT、Wavelets 等特征。常见情况下,FFT 的时域统计特征包括:
- 均方根(RMS)
- 偏度(Skewness)
- 峰度(Kurtosis)

频域特征包括:
- 频谱功率(Spectral Power)
- 偏度
- 峰度

以 FFT 长度 32 为例,Spectral Analysis 块每轴输出 21 个特征,共 63 个特征。
这 63 个特征将作为神经网络分类器和异常检测(K-Means)模型的输入。
详细可参考实验 DSP Spectral Features
模型设计
分类器为全连接神经网络(DNN),输入层 63 个神经元,两个隐藏层分别为 20 和 10 个神经元,输出层 4 个神经元(对应四类),结构如下:

Impulse 设计
Impulse 流程为:原始数据 → 信号处理提取特征 → 学习模块(Dense 模型)分类。
同时,使用第二个模型 K-means 进行异常检测。假设已知类别为若干聚类,无法归类的样本即为异常(如集装箱在海上翻滚或倒置)。

例如 XIAOML Kit 出现训练集中未见的异常运动(如翻滚、倒置)。

最终 Impulse 设计如下:

特征生成
此时已确定预处理方法和模型结构,接下来需将原始时间序列转为表格特征。在 Spectral Features
页点击 [Save Parameters]
,或使用 [Autotune parameters]
自动调参。

顶部菜单进入 Generate features
,勾选 Calculate feature importance
、Normalize features
,点击 [Generate features]
。每个 2 秒窗口(300 点)转为 63 维特征。
特征可视化采用 UMAP ,类似 t-SNE 的降维方法,便于验证类别分离效果。
下图为特征可视化,类别分离良好,模型分类效果预期较好。

可进一步分析各特征对不同类别的重要性。
训练
分类器为全连接神经网络(DNN),输入 63,隐藏层 20/10,输出 4。结构如下:

超参数:学习率 0.005,验证集占 20%,训练 30 轮。训练后准确率达 100%。

异常检测建议选择最重要的特征,聚类数 32(Studio 推荐)。训练后可用海运数据测试,异常分数范围为 min: -0.1642, max: 0.0738, avg: -0.0867
。
更换数据时,分数小或为负表明数据正常。

测试
使用采集阶段预留的 20% 数据测试模型泛化能力,准确率接近 100%,表现优异。

还可用开发板在 Studio 进行实时分类测试。例如模拟“陆运”运动:

注意:此时数据由设备实时采集并上传 Studio,推理在云端完成,模型尚未部署到设备。
部署
现在见证“魔法时刻”!Studio 会自动打包所需库、预处理函数和训练好的模型,下载到本地。选择 Arduino Library,底部选择 Quantized (Int8),点击 [Build]
,生成 ZIP 文件。

在 Arduino IDE,菜单栏选择“添加 .ZIP 库”,导入刚下载的 ZIP 文件:

推理(Inference)
现在,是时候进行真正的测试了。我们将进行完全脱离 Studio 的本地推理。下面以部署 Arduino 库后生成的示例代码为例,进行必要的修改。
在 Arduino IDE 中,依次点击 文件/示例
,找到你的项目,在示例中选择 nano_ble_sense_accelerometer
:
当然,这不是你的开发板,但只需做少量修改即可兼容。
例如,代码开头原本引用 Arduino Sense IMU 相关库:
/* Includes -------------------------------------------- */
#include <XIAOML_Kit_Motion_Class_-_AD_inferencing.h>
#include <Arduino_LSM9DS1.h>
请将“includes”部分替换为 IMU 相关代码:
#include <XIAOML_Kit_Motion_Class_-_AD_inferencing.h>
#include <LSM6DS3.h>
#include <Wire.h>
修改常量定义:
// IMU 初始化
LSM6DS3 myIMU(I2C_MODE, 0x6A);
// 推理参数
#define CONVERT_G_TO_MS2 9.81f
#define MAX_ACCEPTED_RANGE 2.0f * CONVERT_G_TO_MS2
在 setup 函数中,初始化 IMU:
// 初始化 IMU
if (myIMU.begin() != 0) {
Serial.println("ERROR: IMU initialization failed!");
return;
}
在 loop 函数中,原代码通过如下方式获取三轴加速度:
IMU.readAcceleration(buffer[ix], buffer[ix + 1], buffer[ix + 2]);
请替换为:
// 读取 IMU 数据
float x = myIMU.readFloatAccelX();
float y = myIMU.readFloatAccelY();
float z = myIMU.readFloatAccelZ();
接下来需调整两个代码块的顺序。先将原始数据转换为“米每二次方秒(m/s²)”,再进行最大范围判断(此处单位为 m/s²,原 Arduino 代码为 G):
// 转换为 m/s²
buffer[i + 0] = x * CONVERT_G_TO_MS2;
buffer[i + 1] = y * CONVERT_G_TO_MS2;
buffer[i + 2] = z * CONVERT_G_TO_MS2;
// 限幅处理
for (int j = 0; j < 3; j++) {
if (fabs(buffer[i + j]) > MAX_ACCEPTED_RANGE) {
buffer[i + j] = copysign(MAX_ACCEPTED_RANGE, buffer[i + j]);
}
}
这样就可以了。你还可以根据需要调整串口监视器的推理结果显示。现在可以将下方完整代码上传到设备,进行推理测试。
// 使用 LSM6DS3TR-C IMU 进行运动分类
#include <XIAOML_Kit_Motion_Class_-_AD_inferencing.h>
#include <LSM6DS3.h>
#include <Wire.h>
// IMU 初始化
LSM6DS3 myIMU(I2C_MODE, 0x6A);
// 推理参数
#define CONVERT_G_TO_MS2 9.81f
#define MAX_ACCEPTED_RANGE 2.0f * CONVERT_G_TO_MS2
static bool debug_nn = false;
static float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
static float inference_buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("XIAOML Kit - 运动分类");
Serial.println("LSM6DS3TR-C IMU 推理");
// 初始化 IMU
if (myIMU.begin() != 0) {
Serial.println("ERROR: IMU initialization failed!");
return;
}
Serial.println("✓ IMU 初始化成功");
if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) {
Serial.println("ERROR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME"
"应为 3");
return;
}
Serial.println("✓ 模型加载完成");
Serial.println("开始运动分类...");
}
void loop() {
ei_printf("\n2 秒后开始推理...\n");
delay(2000);
ei_printf("采样中...\n");
// 清空缓冲区
for (size_t i = 0; i < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; i++) {
buffer[i] = 0.0f;
}
// 采集加速度数据
for (int i = 0; i < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; i += 3) {
uint64_t next_tick = micros() +
(EI_CLASSIFIER_INTERVAL_MS * 1000);
// 读取 IMU 数据
float x = myIMU.readFloatAccelX();
float y = myIMU.readFloatAccelY();
float z = myIMU.readFloatAccelZ();
// 转换为 m/s²
buffer[i + 0] = x * CONVERT_G_TO_MS2;
buffer[i + 1] = y * CONVERT_G_TO_MS2;
buffer[i + 2] = z * CONVERT_G_TO_MS2;
// 限幅处理
for (int j = 0; j < 3; j++) {
if (fabs(buffer[i + j]) > MAX_ACCEPTED_RANGE) {
buffer[i + j] = copysign(MAX_ACCEPTED_RANGE,
buffer[i + j]);
}
}
delayMicroseconds(next_tick - micros());
}
// 拷贝到推理缓冲区
for (int i = 0; i < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; i++) {
inference_buffer[i] = buffer[i];
}
// 从缓冲区创建信号
signal_t signal;
int err = numpy::signal_from_buffer(inference_buffer,
EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
if (err != 0) {
ei_printf("ERROR: 创建信号失败 (%d)\n", err);
return;
}
// 运行分类器
ei_impulse_result_t result = { 0 };
err = run_classifier(&signal, &result, debug_nn);
if (err != EI_IMPULSE_OK) {
ei_printf("ERROR: 分类器运行失败 (%d)\n", err);
return;
}
// 打印预测结果
ei_printf("预测结果 (DSP: %d ms, 分类:%d ms, 异常:%d ms):\n",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label,
result.classification[ix].value);
}
// 打印异常分数
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf("异常分数:%.3f\n", result.anomaly);
#endif
// 判断预测类别
float max_confidence = 0.0;
String predicted_class = "unknown";
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
if (result.classification[ix].value > max_confidence) {
max_confidence = result.classification[ix].value;
predicted_class = String(result.classification[ix].label);
}
}
// 置信度阈值显示结果
if (max_confidence > 0.6) {
ei_printf("\n🎯 预测类别:%s (置信度 %.1f%%)\n",
predicted_class.c_str(), max_confidence * 100);
} else {
ei_printf("\n❓ 不确定:最高置信度为 %s (%.1f%%)\n",
predicted_class.c_str(), max_confidence * 100);
}
// 检查异常
#if EI_CLASSIFIER_HAS_ANOMALY == 1
if (result.anomaly > 0.5) {
ei_printf("⚠️ 检测到异常!分数:%.3f\n", result.anomaly);
}
#endif
delay(1000);
}
void ei_printf(const char *format, ...) {
static char print_buf[1024] = { 0 };
va_list args;
va_start(args, format);
int r = vsnprintf(print_buf, sizeof(print_buf), format, args);
va_end(args);
if (r > 0) {
Serial.write(print_buf);
}
}
完整代码可在 实验 GitHub 仓库 获取。
现在你可以尝试不同的运动,观察每类推理结果:




当然,也可以模拟“异常”场景,例如将 XIAO 反转,异常分数会大于 0.5:

后处理(Post-Prossessing)
在确认模型正常工作后,建议进一步修改代码,实现完全离线(脱离 PC,使用电池、充电宝或独立 5V 电源供电)运行。
思路是:当检测到特定运动时,在 OLED 屏幕上显示对应信息。

带 OLED 显示的推理代码可在 实验 GitHub 仓库 获取。
总结
本实验演示了如何利用 XIAOML Kit 内置的 LSM6DS3TR-C IMU 传感器,构建完整的运动分类系统。主要成果包括:
技术实现:
- 利用集成六轴 IMU 进行运动感知
- 采集四类运输场景的标注训练数据
- 实现基于频谱特征的时序分析
- 部署适用于微控制器推理的神经网络分类器
- 增加异常检测,识别异常运动
机器学习流程:
- 直接从嵌入式传感器采集数据
- 频域特征工程
- 在 Edge Impulse 平台训练与优化模型
- 在资源受限硬件上实现实时推理
- 性能监控与验证
实际应用场景:
- 资产追踪与物流监控
- 设备预测性维护
- 人体活动识别
- 车辆与设备状态监测
- 智慧城市物联网传感网络
核心收获:
- 熟悉 IMU 坐标系与传感器融合
- 平衡模型精度与边缘设备推理速度
- 构建健壮的数据采集与预处理流程
- 将机器学习模型部署到嵌入式系统
- 多传感器(IMU + 显示屏)集成的完整方案
XIAOML Kit 运动分类的集成展示了现代嵌入式系统本地执行 AI 推理的能力,实现了无需云端的实时决策。这一范式对于工业物联网、自动化系统和智能设备应用具有重要意义,是边缘 AI 未来发展的基础。
参考资料
- XIAOML KIT 代码仓库
- DSP 频谱特征实验
- Edge Impulse 项目
- Edge Impulse 频谱特征 Colab Notebook
- Edge Impulse 官方文档
- Edge Impulse 频谱特征说明
- Seeed Studio LSM6DS3 库