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;
}

运行与验证

  1. 训练与导出:执行 python train_activity_classifier.py,脚本将合成示例数据、训练决策树并生成 model.h。输出训练集准确率(通常≥0.95),并确认 model.h 已生成。
  2. MCU端编译:将 activity_classifier.c 与 model.h 置于同一工程,使用 gcc -o classifier activity_classifier.c -lm 编译(桌面验证),或交叉编译至目标MCU。
  3. 功能验证:运行编译后的程序,输入站立数据应输出“分类结果: 站立”。替换窗口数据为行走/跑步模式(修改main中数组值),分别验证输出对应类别。
  4. 通过条件:三种活动输入均得到正确分类结果,且单次推理时间小于20ms(MCU主频≥64MHz时)。

关键说明

  • 示例数据为合成数据,用于验证流程正确性;实际部署需采集真实传感器数据替换 generate_sample_data(),并重新训练导出。
  • 窗口长度固定为1秒(50Hz采样),可根据实际采样率调整 window_size 参数,但需同步修改Python和C代码中的窗口长度。
  • 决策树深度限制为5层,模型大小约1KB RAM,适合资源受限设备。