4. 语音关键词识别(KWS)
任务目标
在STM32嵌入式平台上实现一个轻量级关键词识别(KWS)系统,通过麦克风实时采集音频,识别“是”(Yes)和“否”(No)两个关键词,并在识别成功后通过串口输出结果。本案例聚焦于模型推理部分,使用STM32Cube.AI将训练好的KWS模型部署到MCU上。
环境与依赖
- 硬件:STM32F746G-Discovery开发板(板载数字麦克风MP34DT01)
- 软件:STM32CubeIDE 1.13+、STM32Cube.AI 8.1+、X-CUBE-AI扩展包
- 模型:预训练KWS模型(.tflite格式),输入为40×49的Mel频谱特征,输出为3类(Yes/No/静音)
- 音频前端:16kHz采样,25ms帧长,10ms帧移,40维Mel滤波器组
实现方案
系统分为音频采集、特征提取和模型推理三部分。音频采集使用STM32的SAI接口配合DMA读取PDM麦克风数据,经硬件抽取滤波后得到16kHz PCM数据。特征提取在MCU上完成:每帧数据加Hamming窗后做512点FFT,经40维Mel滤波器组得到能量谱,累积49帧形成40×49的二维特征图,作为模型输入。模型推理使用STM32Cube.AI生成的C代码库,输入为量化后的int8特征数据,输出为3个类别的置信度分数。
关键完整代码
/* main.c - KWS推理主逻辑 */
#include "ai_platform.h"
#include "network.h"
#include "kws_features.h"
/* 模型输入输出缓冲区 */
static ai_handle network = AI_HANDLE_NULL;
static ai_network_report network_info;
static ai_buffer ai_input[1];
static ai_buffer ai_output[1];
/* 音频特征累积缓冲区:40维×49帧 */
static int8_t feature_buffer[40 * 49];
/* 识别结果回调 */
static volatile uint8_t detection_result = 0;
/* 模型初始化 */
void KWS_ModelInit(void) {
ai_error err;
const ai_network_params params = {
AI_NETWORK_DATA_WEIGHTS,
AI_NETWORK_DATA_ACTIVATIONS
};
err = ai_network_create(&network, ¶ms);
if (err.type != AI_ERROR_NONE) {
Error_Handler();
}
ai_network_get_info(network, &network_info);
/* 配置输入输出缓冲区 */
ai_input[0].data = feature_buffer;
ai_input[0].fmt = AI_BUFFER_FMT_Q7;
ai_input[0].size = network_info.n_inputs[0].size;
ai_output[0].data = (void*)&detection_result;
ai_output[0].fmt = AI_BUFFER_FMT_Q7;
ai_output[0].size = network_info.n_outputs[0].size;
}
/* 推理执行:输入40×49特征图,输出3类置信度 */
uint8_t KWS_RunInference(int8_t* features) {
ai_i32 batch_size = 1;
ai_error err;
memcpy(feature_buffer, features, sizeof(feature_buffer));
err = ai_network_run(network, ai_input, ai_output);
if (err.type != AI_ERROR_NONE) {
return 0;
}
/* 返回最高置信度类别:0=静音, 1=Yes, 2=No */
return detection_result;
}
/* 主循环:每采集完49帧特征后调用 */
void KWS_ProcessFrame(int8_t* mel_features) {
uint8_t result = KWS_RunInference(mel_features);
if (result == 1) {
printf("Detected: YES\r\n");
} else if (result == 2) {
printf("Detected: NO\r\n");
}
/* result==0 时静音,不输出 */
}
特征提取核心代码
/* kws_features.c - Mel频谱特征提取 */
#include "kws_features.h"
#include "arm_math.h"
#define FRAME_LEN 512 /* 25ms @16kHz */
#define NUM_MEL_BINS 40
#define NUM_FRAMES 49
static float32_t hamming_window[FRAME_LEN];
static float32_t mel_filterbank[NUM_MEL_BINS][FRAME_LEN/2+1];
/* 初始化Hamming窗和Mel滤波器组 */
void KWS_FeatureInit(void) {
/* Hamming窗 */
for (int i = 0; i < FRAME_LEN; i++) {
hamming_window[i] = 0.54 - 0.46 * cosf(2*PI*i/(FRAME_LEN-1));
}
/* Mel滤波器组(预计算,实际使用查表) */
GenerateMelFilterbank(mel_filterbank, 16000, FRAME_LEN, NUM_MEL_BINS);
}
/* 将PCM帧转换为40维Mel能量特征 */
void KWS_ExtractFeatures(int16_t* pcm_frame, float32_t* mel_out) {
static float32_t fft_input[FRAME_LEN];
static float32_t fft_output[FRAME_LEN];
static arm_rfft_fast_instance_f32 fft_instance;
/* 加窗 */
for (int i = 0; i < FRAME_LEN; i++) {
fft_input[i] = (float32_t)pcm_frame[i] * hamming_window[i];
}
/* 512点FFT */
arm_rfft_fast_init_f32(&fft_instance, FRAME_LEN);
arm_rfft_fast_f32(&fft_instance, fft_input, fft_output, 0);
/* 计算功率谱并应用Mel滤波器组 */
for (int m = 0; m < NUM_MEL_BINS; m++) {
float32_t energy = 0;
for (int k = 0; k < FRAME_LEN/2+1; k++) {
float32_t mag = fft_output[k];
energy += mag * mag * mel_filterbank[m][k];
}
mel_out[m] = logf(energy + 1e-10);
}
}
运行与验证
- 在STM32CubeIDE中创建工程,添加X-CUBE-AI生成的network.c/h文件
- 编译并烧录到STM32F746G-Discovery开发板
- 打开串口终端(115200-8-N-1),将开发板靠近麦克风
- 清晰说出“Yes”或“No”,观察串口输出
验证方法与通过标准
- 功能验证:连续说出“Yes”10次,至少8次输出“Detected: YES”;连续说出“No”10次,至少8次输出“Detected: NO”
- 性能验证:单次推理时间不超过100ms(可通过GPIO翻转测量),满足实时性要求
- 鲁棒性验证:在环境噪声约50dB的室内,识别准确率不低于80%
常见问题
- 识别率低:检查麦克风增益设置,确保输入音频幅度在-6dBFS至-1dBFS之间
- 推理时间过长:确认STM32Cube.AI配置中启用了CMSIS-NN优化,并设置CPU频率为216MHz
- 特征数据不匹配:验证输入特征量化参数(scale=0.0235, zero_point=-128)与训练时一致