1. 基于加速度计的活动识别
任务目标
基于穿戴设备三轴加速度计数据,实现站立、行走、跑步三类活动的实时分类。采用轻量级决策树模型,适合在MCU级设备上部署,分类延迟低于单帧采样周期。
环境与依赖
- Python 3.8+(离线训练与验证)
- NumPy、Pandas(数据处理)
- scikit-learn 1.0+(决策树训练与评估)
- 目标部署硬件:支持C语言编译的MCU(如STM32、nRF52),无需外部库
实现方案
采用滑动窗口提取时域特征(均值、方差、峰峰值、过零率),训练决策树分类器,并将模型参数导出为C数组,实现嵌入式端无依赖推理。核心代码包含三部分:特征提取与训练脚本、C语言推理实现、验证逻辑。
完整代码
# train_activity_classifier.py
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeClassifier, export_text
import json
# ========== 1. 合成示例数据(实际使用时替换为传感器采集数据) ==========
def generate_sample_data(n_per_class=200, fs=50):
"""生成三轴加速度示例数据: 站立(0), 行走(1), 跑步(2)"""
t = np.arange(fs) / fs # 1秒窗口
X, y = [], []
for label in range(3):
for _ in range(n_per_class):
if label == 0: # 站立: 仅重力分量+微小噪声
acc = np.tile([0, 0, 9.8], (fs, 1)) + np.random.normal(0, 0.05, (fs, 3))
elif label == 1: # 行走: 1-2Hz 周期性摆动
acc_x = 1.2 * np.sin(2*np.pi*1.5*t) + np.random.normal(0, 0.1, fs)
acc_y = 0.5 * np.sin(2*np.pi*1.5*t + 1) + np.random.normal(0, 0.1, fs)
acc_z = 9.8 + 0.8 * np.sin(2*np.pi*1.5*t + 2) + np.random.normal(0, 0.1, fs)
acc = np.column_stack([acc_x, acc_y, acc_z])
else: # 跑步: 2.5-4Hz 大幅摆动
acc_x = 3.5 * np.sin(2*np.pi*3.0*t) + np.random.normal(0, 0.3, fs)
acc_y = 1.8 * np.sin(2*np.pi*3.0*t + 0.8) + np.random.normal(0, 0.3, fs)
acc_z = 9.8 + 2.5 * np.sin(2*np.pi*3.0*t + 1.5) + np.random.normal(0, 0.3, fs)
acc = np.column_stack([acc_x, acc_y, acc_z])
X.append(acc)
y.append(label)
return np.array(X), np.array(y)
# ========== 2. 特征提取 ==========
def extract_features(window):
"""从三轴窗口(50x3)提取12维特征"""
feats = []
for axis in range(3):
data = window[:, axis]
feats.extend([
np.mean(data),
np.var(data),
np.max(data) - np.min(data),
np.sum(np.abs(np.diff(np.sign(data)))) / (2*len(data)) # 过零率
])
return np.array(feats)
# ========== 3. 训练与导出 ==========
X_raw, y_raw = generate_sample_data()
X_feats = np.array([extract_features(w) for w in X_raw])
clf = DecisionTreeClassifier(max_depth=5, min_samples_leaf=5, random_state=42)
clf.fit(X_feats, y_raw)
# 导出树结构为C数组(后序遍历: [feature_idx, threshold, left_child, right_child, leaf_value])
tree = clf.tree_
n_nodes = tree.node_count
children_left = tree.children_left
children_right = tree.children_right
feature = tree.feature
threshold = tree.threshold
value = np.argmax(tree.value, axis=2).flatten()
c_nodes = []
for i in range(n_nodes):
if children_left[i] == -1: # 叶节点
c_nodes.append([-1, 0.0, -1, -1, float(value[i])])
else:
c_nodes.append([float(feature[i]), float(threshold[i]),
float(children_left[i]), float(children_right[i]), -1.0])
# 保存为C头文件
with open('model.h', 'w') as f:
f.write('#ifndef MODEL_H\n#define MODEL_H\n\n')
f.write(f'#define N_NODES {n_nodes}\n')
f.write(f'#define N_FEATURES 12\n\n')
f.write('typedef struct { int feature; float threshold; int left; int right; int leaf_value; } Node;\n\n')
f.write('static const Node tree[N_NODES] = {\n')
for node in c_nodes:
f.write(f' {{{int(node[0])}, {node[1]:.4f}f, {int(node[2])}, {int(node[3])}, {int(node[4])}}},\n')
f.write('};\n\n#endif\n')
# 打印训练精度(用于验证)
train_acc = clf.score(X_feats, y_raw)
print(f"训练集准确率: {train_acc:.3f}")
print("模型已导出至 model.h")
// activity_classifier.c - MCU端推理实现
#include <stdio.h>
#include <math.h>
#include "model.h"
// 特征提取(与Python端一致)
void extract_features(const float acc[][3], int window_size, float* feats) {
for (int axis = 0; axis < 3; axis++) {
float sum = 0, sum_sq = 0, min = 1e9, max = -1e9;
int zero_cross = 0;
for (int i = 0; i < window_size; i++) {
float v = acc[i][axis];
sum += v;
sum_sq += v * v;
if (v < min) min = v;
if (v > max) max = v;
if (i > 0 && ((acc[i-1][axis] >= 0 && v < 0) || (acc[i-1][axis] < 0 && v >= 0)))
zero_cross++;
}
float mean = sum / window_size;
feats[axis*4 + 0] = mean;
feats[axis*4 + 1] = sum_sq / window_size - mean * mean;
feats[axis*4 + 2] = max - min;
feats[axis*4 + 3] = (float)zero_cross / (2 * window_size);
}
}
// 决策树推理
int predict_activity(const float* feats) {
int node = 0;
while (1) {
if (tree[node].leaf_value >= 0) // 叶节点
return tree[node].leaf_value;
if (feats[tree[node].feature] <= tree[node].threshold)
node = tree[node].left;
else
node = tree[node].right;
}
}
// 使用示例
int main() {
// 模拟1秒窗口数据(50Hz采样,3轴)
float window[50][3];
// 此处应填充真实传感器数据
// 示例:填充站立数据(仅重力)
for (int i = 0; i < 50; i++) {
window[i][0] = 0.0f;
window[i][1] = 0.0f;
window[i][2] = 9.8f;
}
float feats[12];
extract_features(window, 50, feats);
int result = predict_activity(feats);
const char* labels[] = {"站立", "行走", "跑步"};
printf("分类结果: %s\n", labels[result]);
return 0;
}
运行与验证
- 训练与导出:执行
python train_activity_classifier.py,脚本将合成示例数据、训练决策树并生成model.h。输出训练集准确率(通常≥0.95),并确认model.h已生成。 - MCU端编译:将
activity_classifier.c与model.h置于同一工程,使用gcc -o classifier activity_classifier.c -lm编译(桌面验证),或交叉编译至目标MCU。 - 功能验证:运行编译后的程序,输入站立数据应输出“分类结果: 站立”。替换窗口数据为行走/跑步模式(修改main中数组值),分别验证输出对应类别。
- 通过条件:三种活动输入均得到正确分类结果,且单次推理时间小于20ms(MCU主频≥64MHz时)。
关键说明
- 示例数据为合成数据,用于验证流程正确性;实际部署需采集真实传感器数据替换
generate_sample_data(),并重新训练导出。 - 窗口长度固定为1秒(50Hz采样),可根据实际采样率调整
window_size参数,但需同步修改Python和C代码中的窗口长度。 - 决策树深度限制为5层,模型大小约1KB RAM,适合资源受限设备。