第6章 STM32Cube.AI开发实例

本章导读:本章通过四个完整的实战案例,展示STM32Cube.AI在不同应用场景下的开发流程。从手势识别、图像分类、语音关键词检测到工业预测性维护,每个案例都涵盖传感器数据采集、模型设计、训练优化、量化部署到实时推理的完整链路。通过本章学习,你将掌握在资源受限的MCU上实现端到端AI推理的核心技能。

6.1 手势识别(IMU传感器)

手势识别是边缘AI最典型的应用之一。本案例使用STM32开发板上的LSM6DSO六轴IMU传感器(3轴加速度计+3轴陀螺仪),采集手部运动数据,通过深度神经网络(DNN)实现实时手势分类。整个系统从传感器采样到推理输出,完全在MCU上运行,推理延迟低于5ms。

6.1.1 系统架构

手势识别系统的数据流水线如下:

  1. 数据采集 — LSM6DSO以104Hz采样率采集6轴数据(加速度ax/ay/az + 角速度gx/gy/gz),I2C接口传输,ODR配置为104Hz,量程加速度±4g、陀螺仪±500dps。
  2. 预处理 — 对200个采样点(约2秒窗口)进行归一化处理,减去重力分量,滑动窗口步长100点(50%重叠)。
  3. 模型推理 — 预处理后的200×6数据送入DNN(输入层200×6=1200特征),经过两个隐藏层(128→64),输出6类手势的概率分布。
  4. 后处理 — 对输出概率进行argmax得到分类结果,加入滑动窗口投票机制消除抖动。

6.1.2 模型结构与训练

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)

6.1.3 部署参数与性能

参数浮点模型INT8量化模型
Flash占用340 KB92 KB
RAM占用(推理)18.4 KB6.2 KB
推理时间(H743)4.8 ms2.1 ms
推理时间(F401)12.3 ms5.6 ms
测试集准确率96.2%95.1%
模型文件大小1.36 MB94 KB
手势类别描述训练样本数识别准确率
0 - 静止手臂自然下垂48097.3%
1 - 挥手左右挥手52096.1%
2 - 画圈手臂画圆50094.8%
3 - 上举手臂向上伸展46096.5%
4 - 前推手臂向前推出49093.7%
5 - 下压手臂向下按压47094.2%

6.1.4 STM32端推理代码

/* 手势识别推理主循环 */
#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;
    }
}
注意:量化后模型准确率可能下降1-2个百分点。如果精度损失过大,可尝试混合精度量化策略——对第一层和最后一层保持float16,中间层使用int8。

IMU数据模拟器 — 6轴波形实时显示

6.2 图像分类(摄像头)

本案例使用STM32H743搭配OV7670摄像头模块,实现手写数字(MNIST)的实时识别。系统通过DCMI硬件接口采集图像,经过预处理后送入轻量级CNN进行推理,单次推理时间约30ms,可实现接近实时的数字识别体验。

6.2.1 硬件配置

组件型号/配置说明
MCUSTM32H743VIT6Cortex-M7, 480MHz, 1MB RAM
摄像头OV7670VGA CMOS, 支持QVGA/VGA
接口DCMI 8-bit硬件数字摄像头接口
分辨率28×28 灰度预处理后输入尺寸
帧率15 FPS(推理)采集30FPS, 隔帧推理
显示ILI9341 LCD320×240 TFT, SPI接口

6.2.2 轻量CNN模型设计

针对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

6.2.3 图像预处理流水线

从摄像头原始数据到模型输入的预处理流程:

/* 图像预处理: 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);
    }
}

6.2.4 性能基准

指标STM32H743STM32F401STM32L4+系列
推理时间28 ms95 ms142 ms
Flash占用6.2 KB6.2 KB6.2 KB
RAM占用4.8 KB4.8 KB4.8 KB
准确率(MNIST)98.1%98.1%98.1%
功耗(推理时)186 mW72 mW38 mW
优化技巧:使用STM32H743的ART Accelerator和L1 Cache可显著减少Flash访问时间。将模型权重数据放在Flash ITCM区域(64位宽总线),推理速度可提升约15%。

CNN推理可视化 — 逐层特征图传播

6.3 语音关键词识别

语音关键词检测(Keyword Spotting, KWS)是MCU上最实用的语音AI应用。系统通过MEMS麦克风采集音频,提取MFCC特征,使用深度可分离卷积网络(DS-CNN)进行关键词分类。模型仅占20KB Flash,可在电池供电设备上持续运行。

6.3.1 音频采集与预处理

参数配置值说明
采样率16 kHz语音频段标准采样率
位深度16-bit PCMPDM麦克风经DFSDM抽取
帧长30 ms (480点)MFCC分析帧
帧移20 ms (320点)相邻帧重叠
FFT点数512频率分辨率31.25Hz
MFCC系数10取前10个MFCC
Mel滤波器组26Mel尺度三角滤波器
输入窗口49帧 × 10 MFCC约1秒音频

6.3.2 DS-CNN模型结构

深度可分离卷积(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

6.3.3 VAD与关键词检测流水线

实际部署时,系统使用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

音频关键词检测流水线可视化

6.4 工业预测性维护

工业预测性维护(Predictive Maintenance, PdM)是边缘AI最具商业价值的应用之一。通过在电机、泵、轴承等设备上安装振动传感器,实时采集振动数据,利用AI模型检测设备异常,可在故障发生前数小时甚至数天发出预警,避免非计划停机。

6.4.1 系统架构

  1. 振动采集 — ADXL356加速度计(±40g,低噪声密度25μg/√Hz),SPI接口,采样率25.6kHz,覆盖电机故障特征频率范围(通常<10kHz)。
  2. FFT频谱分析 — 对1024点时域信号做FFT,得到512个频率bin,频率分辨率25Hz。使用STM32 CMSIS-DSP库的arm_rfft_fast_f32()函数,执行时间<0.5ms。
  3. 异常检测模型 — 使用AutoEncoder重建正常振动模式,当重建误差超过阈值时判定异常。或使用轻量CNN直接在频谱上做故障分类。
  4. 报警与通信 — 检测到异常后通过GPIO驱动LED/蜂鸣器报警,同时通过Modbus-RTU或MQTT上报至工业网关。

6.4.2 AutoEncoder异常检测模型

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

6.4.3 CNN故障分类模型

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

6.4.4 部署性能与资源占用

模型FlashRAM推理时间准确率
AutoEncoder (INT8)12 KB3.8 KB1.8 msAUC 0.94
CNN分类器 (INT8)18 KB5.2 KB3.2 ms93.6%
FFT (CMSIS-DSP)8 KB (库)4.0 KB0.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%谐波能量 > 阈值
工程实践:预测性维护系统应同时部署AutoEncoder(异常检测)和CNN分类器(故障诊断)。AutoEncoder负责持续监控,当检测到异常时再启动CNN进行故障分类,这样可以在保证检测灵敏度的同时降低整体功耗。

预测性维护实时仪表板

本章小结

本章通过四个实战案例全面展示了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模型的输入维度是多少?

A. 100×3
B. 200×6
C. 256×6
D. 200×3

2. LSM6DSO传感器在本案例中采集的数据类型是?

A. 3轴磁力计 + 3轴气压计
B. 3轴加速度 + 温度
C. 3轴加速度计 + 3轴陀螺仪
D. 6轴力传感器

3. 6.2图像分类案例中,CNN模型的总参数量约为?

A. 50,000
B. 5,834
C. 120,000
D. 1,200

4. STM32H743上MNIST推理的单次推理时间约为?

A. 5 ms
B. 100 ms
C. 28 ms
D. 200 ms

5. 6.3语音关键词识别中,MFCC特征提取使用的Mel滤波器组数量是?

A. 13
B. 26
C. 40
D. 10

6. DS-CNN相比标准CNN的主要优势是?

A. 更高的准确率
B. 更快的训练速度
C. 更少的参数量和计算量
D. 更大的感受野

7. 在预测性维护案例中,AutoEncoder的异常检测原理是?

A. 直接分类故障类型
B. 正常数据重建误差小,异常数据重建误差大
C. 检测振动信号的频率成分
D. 预测设备剩余寿命

8. 6.4案例中FFT的频率分辨率是多少?(采样率25.6kHz,1024点FFT)

A. 25 Hz
B. 50 Hz
C. 12.5 Hz
D. 100 Hz

9. 语音关键词检测中VAD的作用是?

A. 增强语音信号质量
B. 将语音转换为文字
C. 检测语音活动,减少模型无效推理以降低功耗
D. 消除背景噪声

10. INT8量化后,手势识别模型的Flash占用从340KB降低到约多少?

A. 170 KB
B. 45 KB
C. 92 KB
D. 200 KB