1. 基于STM32的端侧手势识别

任务目标

在STM32F407开发板上部署轻量级CNN模型,通过板载或外接摄像头采集手势图像,完成实时手势识别(识别0~5数字手势),要求测试集准确率不低于90%。

环境与依赖

  • 硬件:STM32F407VET6开发板(带摄像头接口)、OV2640摄像头模块、TFT-LCD屏(可选,用于显示)
  • 软件:STM32CubeMX(初始化)、STM32CubeIDE(编译烧录)、STM32Cube.AI(模型转换)
  • 模型训练:Python 3.8+、TensorFlow 2.10、Keras

实现方案

整体流程分为三步:PC端训练轻量CNN → STM32Cube.AI转换为C代码 → 嵌入STM32工程完成推理。模型输入为32×32灰度图像,输出为6类手势概率。STM32端通过DCMI接口采集OV2640图像,经缩放和二值化预处理后送入CNN推理,最终在LCD或串口输出识别结果。

关键完整代码

1. PC端模型训练(Python)

import tensorflow as tf
from tensorflow.keras import layers, models

# 轻量CNN:3层卷积+2层全连接,参数量约12K
model = models.Sequential([
    layers.Input(shape=(32, 32, 1)),
    layers.Conv2D(8, 3, activation='relu', padding='same'),
    layers.MaxPooling2D(2),
    layers.Conv2D(16, 3, activation='relu', padding='same'),
    layers.MaxPooling2D(2),
    layers.Conv2D(32, 3, activation='relu', padding='same'),
    layers.Flatten(),
    layers.Dense(32, activation='relu'),
    layers.Dense(6, activation='softmax')
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# 假设已有手势数据集:X_train(6000,32,32,1)归一化到[0,1],y_train(6000,)标签0-5
# 训练并保存
model.fit(X_train, y_train, epochs=10, validation_split=0.2)
model.save('gesture_cnn.h5')
print('训练完成,参数量:', model.count_params())

2. STM32端推理代码(C语言核心片段)

/* 包含Cube.AI生成的头文件 */
#include "gesture_cnn.h"
#include "ai_platform.h"

/* 输入输出缓冲区 */
static ai_handle network = AI_HANDLE_NULL;
static ai_buffer ai_input[1], ai_output[1];
static ai_i8 input_data[32*32];   /* 量化后输入 */
static ai_i8 output_data[6];      /* 量化后输出 */

/* 模型初始化 */
void ai_model_init(void) {
    ai_network_create(&network);
    ai_network_get_input(network, 0, &ai_input[0]);
    ai_network_get_output(network, 0, &ai_output[0]);
    ai_input[0].data = input_data;
    ai_output[0].data = output_data;
}

/* 手势识别:img为32x32灰度图像(0-255) */
int gesture_predict(uint8_t *img) {
    /* 图像归一化到[-128,127]适配int8量化 */
    for (int i = 0; i < 1024; i++) {
        input_data[i] = (ai_i8)(img[i] - 128);
    }
    ai_network_run(network, ai_input, ai_output);
    
    /* 找最大概率类别 */
    int max_idx = 0;
    for (int i = 1; i < 6; i++) {
        if (output_data[i] > output_data[max_idx]) max_idx = i;
    }
    return max_idx;  /* 返回0-5手势类别 */
}

3. 图像采集与预处理(DCMI中断回调)

/* 在DCMI帧完成中断中调用 */
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) {
    static uint8_t frame_buf[32*32];
    /* 将OV2640输出的RGB565缩小为32x32灰度 */
    for (int y = 0; y < 32; y++) {
        for (int x = 0; x < 32; x++) {
            int sx = x * 160 / 32;   /* 假设原始分辨率160x120 */
            int sy = y * 120 / 32;
            uint16_t pixel = camera_buffer[sy*160 + sx];
            uint8_t r = (pixel >> 11) & 0x1F;
            uint8_t g = (pixel >> 5) & 0x3F;
            uint8_t b = pixel & 0x1F;
            frame_buf[y*32 + x] = (r*77 + g*150 + b*29) >> 8;
        }
    }
    int result = gesture_predict(frame_buf);
    /* 通过串口或LCD显示result */
    printf("Gesture: %d\r\n", result);
}

运行与验证

  1. 模型转换:在STM32CubeIDE中启用Cube.AI插件,导入gesture_cnn.h5,选择STM32F407目标,生成C代码(量化后RAM占用约15KB,Flash约25KB)。
  2. 工程集成:将生成的gesture_cnn.c/.h加入工程,在main.c中调用ai_model_init(),配置DCMI和OV2640后启动采集。
  3. 编译烧录:使用STM32CubeIDE编译并烧录到F407开发板。
  4. 验证方法:准备6类手势各20张测试图像(共120张),逐一手动展示给摄像头,记录识别结果。判定标准:正确识别≥108张(准确率≥90%)。

常见问题

  • 内存不足:若激活区超过F407的192KB RAM,可减小输入分辨率至24×24或减少卷积核数量。
  • 准确率不达标:增加训练数据量、做数据增强(平移/旋转±10°),或延长训练epochs至20。
  • 帧率过低:当前方案在168MHz主频下推理约需80ms,可通过开启Cube.AI的优化级别-O3提升至50ms内。