手势识别是边缘AI最典型的应用之一。本案例使用STM32开发板上的LSM6DSO六轴IMU传感器(3轴加速度计+3轴陀螺仪),采集手部运动数据,通过深度神经网络(DNN)实现实时手势分类。整个系统从传感器采样到推理输出,完全在MCU上运行,推理延迟低于5ms。
手势识别系统的数据流水线如下:
DNN网络使用TensorFlow/Keras构建,结构如下:
import tensorflow as tf
from tensorflow import keras
model = keras.Sequential([
keras.layers.Input(shape=(200, 6)),
keras.layers.Flatten(), # 200*6 = 1200
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.3),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(6, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(X_train, y_train, epochs=50,
batch_size=32,
validation_split=0.2)
训练完成后,将模型导出为TFLite格式并进行INT8全量化:
# 转换为TFLite并量化
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = lambda: [
[X_train[i:i+1].astype(np.float32)] for i in range(100)
]
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS_INT8
]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
tflite_quant_model = converter.convert()
with open('gesture_model.tflite', 'wb') as f:
f.write(tflite_quant_model)
| 参数 | 浮点模型 | INT8量化模型 |
|---|---|---|
| Flash占用 | 340 KB | 92 KB |
| RAM占用(推理) | 18.4 KB | 6.2 KB |
| 推理时间(H743) | 4.8 ms | 2.1 ms |
| 推理时间(F401) | 12.3 ms | 5.6 ms |
| 测试集准确率 | 96.2% | 95.1% |
| 模型文件大小 | 1.36 MB | 94 KB |
| 手势类别 | 描述 | 训练样本数 | 识别准确率 |
|---|---|---|---|
| 0 - 静止 | 手臂自然下垂 | 480 | 97.3% |
| 1 - 挥手 | 左右挥手 | 520 | 96.1% |
| 2 - 画圈 | 手臂画圆 | 500 | 94.8% |
| 3 - 上举 | 手臂向上伸展 | 460 | 96.5% |
| 4 - 前推 | 手臂向前推出 | 490 | 93.7% |
| 5 - 下压 | 手臂向下按压 | 470 | 94.2% |
/* 手势识别推理主循环 */
#include "ai_gesture.h"
#include "lsm6dso_driver.h"
#define WINDOW_SIZE 200
#define NUM_AXES 6
#define NUM_CLASSES 6
static ai_float input_data[WINDOW_SIZE * NUM_AXES];
static ai_float output_data[NUM_CLASSES];
static int sample_idx = 0;
static int gesture_result = -1;
void gesture_init(void) {
/* 初始化LSM6DSO: 104Hz, +/-4g, +/-500dps */
lsm6dso_init(104, LSM6DSO_ACC_4G, LSM6DSO_GYRO_500DPS);
/* 初始化AI推理引擎 */
ai_handle handle = ai_gesture_init();
if (handle == AI_HANDLE_NULL) {
Error_Handler();
}
}
void gesture_process_sample(float acc[3], float gyro[3]) {
/* 填充输入缓冲区 */
input_data[sample_idx * 6 + 0] = acc[0];
input_data[sample_idx * 6 + 1] = acc[1];
input_data[sample_idx * 6 + 2] = acc[2];
input_data[sample_idx * 6 + 3] = gyro[0];
input_data[sample_idx * 6 + 4] = gyro[1];
input_data[sample_idx * 6 + 5] = gyro[2];
sample_idx++;
if (sample_idx >= WINDOW_SIZE) {
/* 执行推理 */
ai_gesture_run(input_data, output_data);
/* 寻找最大概率 */
float max_prob = 0;
for (int i = 0; i < NUM_CLASSES; i++) {
if (output_data[i] > max_prob) {
max_prob = output_data[i];
gesture_result = i;
}
}
/* 滑动窗口前进100步(50%重叠) */
memmove(input_data, input_data + 100 * 6,
100 * 6 * sizeof(ai_float));
sample_idx = 100;
}
}
本案例使用STM32H743搭配OV7670摄像头模块,实现手写数字(MNIST)的实时识别。系统通过DCMI硬件接口采集图像,经过预处理后送入轻量级CNN进行推理,单次推理时间约30ms,可实现接近实时的数字识别体验。
| 组件 | 型号/配置 | 说明 |
|---|---|---|
| MCU | STM32H743VIT6 | Cortex-M7, 480MHz, 1MB RAM |
| 摄像头 | OV7670 | VGA CMOS, 支持QVGA/VGA |
| 接口 | DCMI 8-bit | 硬件数字摄像头接口 |
| 分辨率 | 28×28 灰度 | 预处理后输入尺寸 |
| 帧率 | 15 FPS(推理) | 采集30FPS, 隔帧推理 |
| 显示 | ILI9341 LCD | 320×240 TFT, SPI接口 |
针对MCU资源限制,设计3层卷积的轻量网络:
import tensorflow as tf
model = tf.keras.Sequential([
# 输入: 28x28x1 灰度图
tf.keras.layers.Input(shape=(28, 28, 1)),
# Conv1: 3x3, 8 filters, stride=1
tf.keras.layers.Conv2D(8, (3,3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2,2)), # 14x14x8
# Conv2: 3x3, 16 filters, stride=1
tf.keras.layers.Conv2D(16, (3,3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2,2)), # 7x7x16
# Conv3: 3x3, 32 filters, stride=1
tf.keras.layers.Conv2D(32, (3,3), activation='relu', padding='same'),
# 分类头
tf.keras.layers.GlobalAveragePooling2D(), # 32
tf.keras.layers.Dense(10, activation='softmax')
])
model.summary()
# Total params: 5,834
# Flash (INT8): ~6.2 KB
# RAM (推理): ~4.8 KB
从摄像头原始数据到模型输入的预处理流程:
/* 图像预处理: RGB565 → 灰度 → 缩放 → 归一化 */
void preprocess_image(uint16_t *rgb565_buf, int8_t *model_input) {
uint8_t gray[640 * 480]; // VGA灰度缓冲
// Step 1: RGB565转灰度
for (int i = 0; i < 640 * 480; i++) {
uint16_t px = rgb565_buf[i];
uint8_t r = (px >> 11) & 0x1F;
uint8_t g = (px >> 5) & 0x3F;
uint8_t b = px & 0x1F;
gray[i] = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
}
// Step 2: ROI裁剪 (中心区域) + 双线性缩放到28x28
uint8_t resized[28 * 28];
bilinear_resize(gray, 640, 480,
resized, 28, 28,
200, 140, 280, 280); // ROI
// Step 3: 归一化到 [-1, 1] 并量化为int8
for (int i = 0; i < 28 * 28; i++) {
float norm = (resized[i] / 127.5f) - 1.0f;
model_input[i] = (int8_t)(norm * 128.0f);
}
}
| 指标 | STM32H743 | STM32F401 | STM32L4+系列 |
|---|---|---|---|
| 推理时间 | 28 ms | 95 ms | 142 ms |
| Flash占用 | 6.2 KB | 6.2 KB | 6.2 KB |
| RAM占用 | 4.8 KB | 4.8 KB | 4.8 KB |
| 准确率(MNIST) | 98.1% | 98.1% | 98.1% |
| 功耗(推理时) | 186 mW | 72 mW | 38 mW |
语音关键词检测(Keyword Spotting, KWS)是MCU上最实用的语音AI应用。系统通过MEMS麦克风采集音频,提取MFCC特征,使用深度可分离卷积网络(DS-CNN)进行关键词分类。模型仅占20KB Flash,可在电池供电设备上持续运行。
| 参数 | 配置值 | 说明 |
|---|---|---|
| 采样率 | 16 kHz | 语音频段标准采样率 |
| 位深度 | 16-bit PCM | PDM麦克风经DFSDM抽取 |
| 帧长 | 30 ms (480点) | MFCC分析帧 |
| 帧移 | 20 ms (320点) | 相邻帧重叠 |
| FFT点数 | 512 | 频率分辨率31.25Hz |
| MFCC系数 | 10 | 取前10个MFCC |
| Mel滤波器组 | 26 | Mel尺度三角滤波器 |
| 输入窗口 | 49帧 × 10 MFCC | 约1秒音频 |
深度可分离卷积(Depthwise Separable Convolution)将标准卷积分解为逐通道卷积和逐点卷积,大幅减少参数量和计算量:
import tensorflow as tf
def ds_cnn(input_shape=(49, 10, 1), num_classes=12):
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=input_shape),
# 标准卷积第一层
tf.keras.layers.Conv2D(64, (4,3), strides=(2,1),
activation='relu', padding='same'),
tf.keras.layers.BatchNormalization(),
# 4个DS-Conv块
# DS-Conv1
tf.keras.layers.DepthwiseConv2D((3,3), activation='relu',
padding='same'),
tf.keras.layers.Conv2D(64, (1,1), activation='relu'),
tf.keras.layers.BatchNormalization(),
# DS-Conv2
tf.keras.layers.DepthwiseConv2D((3,3), activation='relu',
padding='same'),
tf.keras.layers.Conv2D(64, (1,1), activation='relu'),
tf.keras.layers.BatchNormalization(),
# DS-Conv3
tf.keras.layers.DepthwiseConv2D((3,3), activation='relu',
padding='same'),
tf.keras.layers.Conv2D(64, (1,1), activation='relu'),
tf.keras.layers.BatchNormalization(),
# DS-Conv4
tf.keras.layers.DepthwiseConv2D((3,3), activation='relu',
padding='same'),
tf.keras.layers.Conv2D(64, (1,1), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(num_classes, activation='softmax')
])
return model
# 关键词列表: "silence", "unknown", "yes", "no",
# "up", "down", "left", "right", "on", "off",
# "stop", "go"
model = ds_cnn(num_classes=12)
# 模型大小(INT8): ~20 KB
# MACs: ~300K per inference
实际部署时,系统使用VAD(Voice Activity Detection)进行前端语音活动检测,仅在检测到语音活动时才启动关键词识别模型,大幅降低功耗:
/* 关键词检测主循环 */
void keyword_spotting_task(void) {
int16_t audio_buf[AUDIO_FRAME_SIZE]; // 480 samples
float mfcc_features[49][10];
int8_t model_input[49 * 10];
int frame_count = 0;
while (1) {
/* 等待音频数据就绪 */
osSemaphoreAcquire(audioSem, osWaitForever);
audio_capture_get(audio_buf, AUDIO_FRAME_SIZE);
/* VAD检测: 基于短时能量+过零率 */
if (vad_detect(audio_buf, AUDIO_FRAME_SIZE)) {
/* 提取MFCC特征 */
mfcc_extract(audio_buf, mfcc_features);
/* 填充特征缓冲区 */
store_mfcc_frame(mfcc_features, frame_count);
frame_count++;
if (frame_count >= 49) {
/* 量化并送入模型 */
quantize_mfcc(model_input);
ai_keyword_run(model_input, output);
/* 后处理: 滑动窗口平滑 */
int keyword = postprocess_output(output);
if (keyword >= 0) {
keyword_callback(keyword);
}
frame_count = 0;
}
} else {
frame_count = 0; // 重置
}
}
}
| 关键词 | 识别准确率 | 误触发率 | 检测延迟 |
|---|---|---|---|
| "你好" (Hello) | 95.8% | 0.3次/小时 | 1.0 s |
| "开灯" (Light On) | 94.2% | 0.5次/小时 | 1.0 s |
| "关灯" (Light Off) | 94.6% | 0.4次/小时 | 1.0 s |
| "停止" (Stop) | 96.1% | 0.2次/小时 | 1.0 s |
| "启动" (Start) | 93.7% | 0.6次/小时 | 1.0 s |
工业预测性维护(Predictive Maintenance, PdM)是边缘AI最具商业价值的应用之一。通过在电机、泵、轴承等设备上安装振动传感器,实时采集振动数据,利用AI模型检测设备异常,可在故障发生前数小时甚至数天发出预警,避免非计划停机。
arm_rfft_fast_f32()函数,执行时间<0.5ms。import tensorflow as tf
def build_autoencoder(input_dim=512):
"""振动频谱AutoEncoder - 仅用正常数据训练"""
encoder = tf.keras.Sequential([
tf.keras.layers.Input(shape=(input_dim,)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dense(16, activation='relu'), # 瓶颈层
])
decoder = tf.keras.Sequential([
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(input_dim, activation='sigmoid'),
])
ae = tf.keras.Sequential([encoder, decoder])
ae.compile(optimizer='adam', loss='mse')
return ae
# 仅用正常振动数据训练
ae = build_autoencoder()
ae.fit(normal_spectra, normal_spectra, epochs=100, batch_size=64)
# 异常阈值: 正常数据重建误差的95百分位
recon_errors = ae.predict(normal_spectra)
mse = np.mean(np.square(normal_spectra - recon_errors), axis=1)
threshold = np.percentile(mse, 95) # e.g., 0.032
def build_fault_classifier(input_dim=512, num_classes=5):
"""故障类型分类器: 正常/不平衡/不对中/轴承磨损/松动"""
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(input_dim, 1)),
tf.keras.layers.Conv1D(32, 7, activation='relu', padding='same'),
tf.keras.layers.MaxPooling1D(2),
tf.keras.layers.Conv1D(64, 5, activation='relu', padding='same'),
tf.keras.layers.MaxPooling1D(2),
tf.keras.layers.Conv1D(64, 3, activation='relu', padding='same'),
tf.keras.layers.GlobalAveragePooling1D(),
tf.keras.layers.Dense(32, activation='relu'),
tf.keras.layers.Dropout(0.3),
tf.keras.layers.Dense(num_classes, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
| 模型 | Flash | RAM | 推理时间 | 准确率 |
|---|---|---|---|---|
| AutoEncoder (INT8) | 12 KB | 3.8 KB | 1.8 ms | AUC 0.94 |
| CNN分类器 (INT8) | 18 KB | 5.2 KB | 3.2 ms | 93.6% |
| FFT (CMSIS-DSP) | 8 KB (库) | 4.0 KB | 0.45 ms | - |
| 故障类型 | 特征频率 | 分类准确率 | 报警阈值 |
|---|---|---|---|
| 正常 | 基频1X为主 | 96.2% | - |
| 转子不平衡 | 1X幅值异常 | 94.8% | 1X > 4.5 mm/s |
| 轴不对中 | 2X/3X幅值升高 | 92.3% | 2X/1X > 0.75 |
| 轴承磨损 | BPFO/BPFI频率 | 91.7% | 包络峰值 > 3σ |
| 机械松动 | 多谐波(0.5X, 1X, 2X...) | 90.5% | 谐波能量 > 阈值 |
本章通过四个实战案例全面展示了STM32Cube.AI的开发流程:
6.1 手势识别 — 掌握了IMU传感器数据采集、DNN模型设计与量化、时序数据窗口化处理。
6.2 图像分类 — 学习了DCMI摄像头接口、轻量CNN设计、图像预处理流水线优化。
6.3 语音关键词 — 理解了MFCC特征提取、DS-CNN高效架构、VAD+KWS两级检测策略。
6.4 预测性维护 — 实践了FFT频谱分析、AutoEncoder异常检测、工业级报警系统。
1. 在6.1手势识别案例中,DNN模型的输入维度是多少?
2. LSM6DSO传感器在本案例中采集的数据类型是?
3. 6.2图像分类案例中,CNN模型的总参数量约为?
4. STM32H743上MNIST推理的单次推理时间约为?
5. 6.3语音关键词识别中,MFCC特征提取使用的Mel滤波器组数量是?
6. DS-CNN相比标准CNN的主要优势是?
7. 在预测性维护案例中,AutoEncoder的异常检测原理是?
8. 6.4案例中FFT的频率分辨率是多少?(采样率25.6kHz,1024点FFT)
9. 语音关键词检测中VAD的作用是?
10. INT8量化后,手势识别模型的Flash占用从340KB降低到约多少?