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);
}
运行与验证
- 模型转换:在STM32CubeIDE中启用Cube.AI插件,导入
gesture_cnn.h5,选择STM32F407目标,生成C代码(量化后RAM占用约15KB,Flash约25KB)。 - 工程集成:将生成的
gesture_cnn.c/.h加入工程,在main.c中调用ai_model_init(),配置DCMI和OV2640后启动采集。 - 编译烧录:使用STM32CubeIDE编译并烧录到F407开发板。
- 验证方法:准备6类手势各20张测试图像(共120张),逐一手动展示给摄像头,记录识别结果。判定标准:正确识别≥108张(准确率≥90%)。
常见问题
- 内存不足:若激活区超过F407的192KB RAM,可减小输入分辨率至24×24或减少卷积核数量。
- 准确率不达标:增加训练数据量、做数据增强(平移/旋转±10°),或延长训练epochs至20。
- 帧率过低:当前方案在168MHz主频下推理约需80ms,可通过开启Cube.AI的优化级别-O3提升至50ms内。