资讯动态

STM32本地词语音识别实战:从ADC采集到MFCC+DTW全流程解析

发布时间:2026/9/16 6:16:25 来源:尧图企业网站定制
简介一套基于STM32的孤立词语音识别完整工程面向嵌入式语音识别入门与进阶学习者覆盖预滤波、ADC采集、分帧、端点检测、预加重、加窗、MFCC特征提取及DTW模板匹配的完整链路。资源内含177个文件以C源码、头文件、Matlab仿真脚本和WAV测试音频为主另附Keil工程配置、链接脚本及烧录文件便于直接在STM32平台编译烧录Matlab脚本.m用于系数仿真与参数寻优.c/.h文件为移植后的嵌入式实现音频样本可用于离线验证。压缩包仅1.48MB目录结构清晰适合对照算法流程逐步学习。已有536人学习下载从中可获取从Matlab验证到嵌入式平台优化的完整思路以及端点检测、MFCC、DTW等关键环节的落地代码对理解孤立词识别系统的工程化移植很有帮助。1. 把 STM32 本地词语音识别从 40% 拉回 90%先动采集链路把“基于STM32的词语音识别”拆开看最容易被高估的是 MCU 算力最容易被低估的是麦克风进来的那一路模拟信号。同一套 MFCC 代码放在开发板上识别率能到 90%换到自己画的板子上同样是“开灯”两个字识别率可能直接掉到 40%。问题多半不在算法而在 ADC 参考、电源纹波和模板质量这三件事上。这里走的是离线词语音识别常用路线定时器触发 ADC 以 16kHz 采集驻极体麦克风在 STM32 内部做 MFCC 特征提取再用 DTW 和词模板匹配全程不依赖外挂语音芯片也不需要把音频送进云端。文章覆盖音频采集、特征计算、模板训练、拒识判断四段参数以 F103/F407 为参考国产 APM32 这类兼容型号同样适用。目标是把“开灯”“关灯”“前进”“后退”几个词在本地跑通并解决误触发和漏识别这两个最影响体验的问题。2. 词语音识别的技术选型外挂识别芯片与片上 MFCCDTW 怎么选先回答一个很实际的问题这块 STM32 到底要不要做识别本身。很多方案里 STM32 只负责把音频传给外挂识别芯片自己当控制端这样做项目速度快但词表、唤醒方式、误触发逻辑都被芯片和配套工具限制住把识别放到 MCU 内部成本低也更容易和后面的电机、继电器控制代码融合。2.1 外挂识别芯片的边界LD3320、SU-03T 一类方案常见做法是 STM32 通过 UART 或 SPI 跟识别芯片通信芯片内部完成算法识别结果以一帧协议返回。LD3320 是这类芯片里的老方案词表写入芯片后基本固定很多资料里提到它的容量在几十个词这个量级发音相近的词容易互相干扰。SU-03T 这类离线模块提供了自己的编辑工具可以生成几百条规则但每条规则的调试完全依赖厂商 IDE想在识别到“开灯”的同时把灯亮度寄存器也写进识别流程还是要回 STM32 里做。这类方案适合对识别率要求不高、词表固定且不想碰 DSP 的项目。但一旦你要支持“语音按钮同时触发”“说话时自动降低风扇噪音”“不同人说话用不同模板”这类逻辑外挂芯片就开始别扭了。下表是三种路线的常见选择依据方案词表规模开发成本定制能力适用场景LD3320 外挂芯片几十个以内低串口直接收发弱词表和发音固化固定命令、批量生产SU-03T 离线模块几十到上百条规则低厂商工具链中识别规则受限直接驱动 IO、快速出原型片上 MFCCDTW5~20 个关键词高需要调算法强模板与门限完全可控低功耗控制、词表要持续改联网云识别几乎无限中需无线模组强但依赖网络语义理解、长句识别如果你手上正好有 ESP32 接讯飞这类云识别 API 的工程识别准确率确实更高但那套东西搬到离线环境就失效了。STM32 本地词语音识别的意义恰恰是在没有网络的地方把“开灯”和“关灯”这两件事做对哪怕多花两周时间调模板也值得。2.2 片上识别为什么选 MFCCDTW不加载模型也能跑MCU 上做语音识别最早成熟、也最容易复现的组合是 MFCC 特征加 DTW 动态时间规整。MFCC 把一段 PCM 变成一帧一帧的低维向量DTW 负责把两段长度不一致的特征序列按最小代价对齐从而解决同一个词有人读得快、有人读得慢的问题。为什么不直接上 HMM 或 CNNHMM 在 ARM 上不是不能跑但需要训练 GMM 模型训练数据和工具箱都要配套F103 上还要花大量时间做浮点裁剪CNN 类关键词识别模型即使量化到 int8一次推理也需要几百 KB 权重远超 F103 的 Flash 预算。MFCCDTW 没有模型文件只有模板模板本质就是一组特征数组改动词表只需要改数组内容。DTW 对齐的好处是它对词长不敏感。比如“前进”这个词10 次录音的时长可能从 480ms 变化到 820ms直接对逐帧欧氏距离会错位DTW 用动态规划寻找一条累积距离最小的路径把 600ms 和 800ms 的两段序列压在一起比。词数少的时候DTW 在实时性上的优势很明显一个 1 秒的词匹配一次模板F103 上也就几十毫秒。2.3 一个词模板要占多少内存先算明白再写代码假设采样率 16kHz帧长 25ms 即 400 点帧移 10ms 即 160 点一个 1 秒的关键词大约产生 91 帧MFCC 取 12 维。模板如果按 int16 存一帧占 24 字节整个词模板约 2.2KB。三个词各存三份模板也就是 20KB 量级F103 的 Flash 完全放得下。运行时内存主要消耗在 FFT 缓冲、特征帧队列和 DTW 距离矩阵上。FFT 512 点需要两组 float 数组约 4KB特征帧队列按 160 帧算约 3.8KBDTW 用两行滚动数组则只需几百字节。整套下来 10KB 以内F103 的 20KB SRAM 能扛住。计算量方面CMSIS-DSP 在 72MHz 下跑一次 512 点 FFT 大约是一百微秒量级加上 24 路 Mel 滤波和 DCT一帧特征约几百微秒到 1ms。帧与帧之间隔 10msCPU 占用率并不高。真正的耗时瓶颈反而在模板训练和门限调试阶段这点放到最后一章说。3. 让 STM32 先“听清”词定时器触发 ADC 采集的完整配置识别算法再漂亮前提是 PCM 数据干净。麦克风信号从驻极体出来通常只有几十毫伏必须经过偏置、放大、隔直三件事才能进 STM32 ADC。3.1 麦克风前端把驻极体信号搬进 ADC 量程常见做法是驻极体麦克风用一个 2.2kΩ 电阻上拉到 3.3V作为内部 FET 的负载输出经过 1μF 电容隔直再接到 STM32 的 PA1。PA1 还需要一个直流偏置点最简单的分压是两只 10kΩ 电阻分别接 3.3V 和 GND中间点接 ADC 引脚偏置约 1.65V。这样麦克风输出的小交流电压叠加在 1.65V 上ADC 12 位量程就能覆盖。如果你要接 MAX9814 这类现成麦克风放大器模块输出端同样经过 1μF 电容进 ADC模块自带的 AGC 能自动压低大声、放大小声。注意这类模块的 VDD 不要直接接 STM32 的 3.3V 输出最好单独用 LDO 或磁珠隔离否则 PA 和数码管一工作识别率立刻下降。电路参数可以按下面这张表选作为起始值器件参数作用驻极体上拉电阻2.2kΩ给麦克风内部 FET 提供工作点隔直电容1μF 陶瓷电容去掉直流偏置只让交流语音信号通过ADC 偏置分压电阻两只 10kΩ把 ADC 引脚拉到 1.65V 中点采样电阻100Ω限制 ADC 输入电流做简单 RC 滤波旁路电容100nF 到 GND滤掉高频干扰3.2 用 TIM2 触发 ADC1 DMA 实现 16kHz 连续采样语音采样不能靠 while 循环轮询否则 CPU 全部被占满。常规做法是用定时器更新事件作为 ADC 外部触发ADC 完成转换后由 DMA 把结果搬进内存整个过程不打断主循环。下面的初始化代码基于 STM32F1 HAL 库目标是在 TIM2 产生 16kHz 触发频率ADC1 单通道采集 PA1DMA 循环模式。// 采样率 72MHz / 4500 16kHz #define AUDIO_SAMPLE_RATE 16000 #define ADC_BUF_SIZE 2048 static uint16_t adc_buf[ADC_BUF_SIZE]; TIM_HandleTypeDef htim2; ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; void audio_hal_init(void) { __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // PA1 模拟输入 GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_1; gpio.Mode GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, gpio); // TIM2更新事件每 4500 个时钟产生一次 htim2.Instance TIM2; htim2.Init.Prescaler 0; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 4500 - 1; HAL_TIM_Base_Init(htim2); // 外部触发选择 TIM2 TRGO必须开启主输出 TIM_MasterConfigTypeDef master_cfg {0}; master_cfg.MasterOutputTrigger TIM_TRGO_UPDATE; HAL_TIMEx_MasterConfigSynchronization(htim2, master_cfg); // ADC112 位右对齐TIM2 上升沿触发 hadc1.Instance ADC1; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; HAL_ADC_Init(hadc1); // DMA1 通道 1循环模式外设到内存 hdma_adc1.Instance DMA1_Channel1; hdma_adc1.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc DMA_PINC_DISABLE; hdma_adc1.Init.MemInc DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode DMA_CIRCULAR; HAL_DMA_Init(hdma_adc1); }代码里三个关键点要解释清楚。第一TIM2 的 Period 是 4499加上计数从 0 开始的周期正好 4500 个 72MHz 时钟也就是 16kHz。第二定时器必须配置主输出触发ADC 的 ExternalTrigConv 要跟定时器 TRGO 匹配否则 ADC 永远不启动。第三DMA 用循环模式采集完 2048 个点会自动从头覆盖这样录音可以是无限长的程序只需从缓冲区里以环形方式读取最新数据。3.3 环形缓冲把麦克风流切成可处理的帧DMA 写入 adc_buf 时主循环并不知道当前写到了哪个位置。HAL 库提供__HAL_DMA_GET_COUNTER读取 DMA 剩余传输次数用总长度减剩余次数就能得到当前写指针。配合一个读指针每次从读指针位置取 400 个点作为一帧再以 160 点滑动就构成常见的滑窗取帧。uint16_t dma_write_index(void) { return ADC_BUF_SIZE - __HAL_DMA_GET_COUNTER(hdma_adc1); } int16_t get_frame(int16_t *out) { static uint16_t read_idx 0; uint16_t write_idx dma_write_index(); // 写指针落后于读指针时代表数据被覆盖直接丢帧 if (write_idx read_idx) return 0; for (uint16_t i 0; i 400; i) out[i] (int16_t)adc_buf[(read_idx i) % ADC_BUF_SIZE] - 2048; read_idx (read_idx 160) % ADC_BUF_SIZE; return 1; }这里把 ADC 原始值减掉 2048是因为 12 位 ADC 的 1.65V 偏置大约对应数字量 2048。减掉之后静音时的信号围绕 0 波动有利于后面做能量计算和 VAD。读指针每次移动 160 点而不是 400 点是为了模拟 10ms 帧移这个滑窗关系要和 MFCC 的帧参数保持一致。4. 在 STM32 上提取 MFCC 特征从 PCM 帧到 12 维向量MFCC 是整条识别链路里最容易被抄错的一段。有人直接拿电脑上 44.1kHz 的代码改采样点有人在 FFT 之后忘了把实部虚部转成幅度还有人在 DCT 之前把滤波器组权重归一化错了。这里给一个能直接用的流程并说明每个参数为什么这么定。4.1 预加重、分帧、加窗参数不能照抄音频软件语音信号的高频部分能量偏低预加重用y[n] x[n] - 0.97*x[n-1]把高频抬起来目的是让后面的频谱更平坦。0.97 是语音识别里最常用的一阶高通系数太大容易放大噪声太小起不到补偿作用一般不推荐改。加窗用汉明窗公式是0.54 - 0.46*cos(2πn/(N-1))。汉明窗的旁瓣衰减比较大能减少帧边界处的频谱泄漏。参数 N 取 400即帧长 25ms这是兼顾频率分辨率和时间分辨率的折中值。如果 N 取 512每帧时间变成 32ms低频会更稳但响应变慢如果 N 取 25612 维系数区分度明显下降。下面的代码把预加重、加窗和 FFT 集中在同一个函数里#define FRAME_LEN 400 #define FFT_SIZE 512 #define MEL_BANK 24 #define MFCC_DIM 12 static float fft_in[FFT_SIZE]; static float fft_out[FFT_SIZE]; static float mel_energy[MEL_BANK]; // FFT 实例在 main 中通过 arm_rfft_fast_init_f32(fft_inst, FFT_SIZE) 初始化 extern arm_rfft_fast_instance_f32 fft_inst; void mfcc_from_frame(const float *frame, float *mfcc) { // 1. 预加重 汉明窗 for (int i 0; i FRAME_LEN; i) { float x frame[i]; if (i 0) x frame[i] - 0.97f * frame[i - 1]; float w 0.54f - 0.46f * cosf(2.0f * (float)PI * i / (FRAME_LEN - 1)); fft_in[i] x * w; } for (int i FRAME_LEN; i FFT_SIZE; i) fft_in[i] 0.0f; // 2. 实数 FFT输出按 Re/Im 交替排列 arm_rfft_fast_f32(fft_inst, fft_in, fft_out, 0); // 3. 24 路三角 Mel 滤波器输出对数能量 for (int m 0; m MEL_BANK; m) { float sum 0.0f; const float *w mel_filter_bank[m]; // 预计算后存 Flash for (int k 0; k FFT_SIZE / 2; k) { float re fft_out[2 * k]; float im fft_out[2 * k 1]; sum w[k] * (re * re im * im); } mel_energy[m] logf(sum 1e-6f); } // 4. DCT-II取前 12 个系数c0 能量项丢弃 for (int j 0; j MFCC_DIM; j) { float c 0.0f; for (int m 0; m MEL_BANK; m) c mel_energy[m] * cosf((float)PI * j * (m 0.5f) / MEL_BANK); mfcc[j] c; } }这段代码里 FFT 输出布局需要提醒CMSIS-DSP 的 arm_rfft_fast_f32 输出是[Re0, Im0, Re1, Im1, ...]所以用fft_out[2*k]和fft_out[2*k1]取实部虚部。Mel 滤波器权重数组可以预先在 PC 上算好生成 C 语言头文件直接烧进 Flash24 路乘 256 个频点float 存储约 24KBF103 的 Flash 能接受如果空间紧张把频点上界从 4kHz 截到 3400Hz数组能减少近一半。4.2 Mel 滤波器组的频率映射300Hz 到 3400Hz 就够用人说话的能量主要落在 300Hz 到 3400HzMel 滤波器组不需要覆盖全部 8kHz 带宽。频率到 Mel 的映射公式是mel 2595 * log10(1 f/700)反过来用逆公式把 24 个中心频率换算回 Hz在 FFT 频点上找对应的索引再画成三角滤波器。Mel 滤波的目的是把 FFT 的高分辨率谱压缩成人耳感知的粗糙谱。每个三角滤波器的输出是把频谱加权求和中心频率处的权重最大两端权重线性降为 0。这样低频段的滤波器带宽窄高频段带宽宽正好模拟人耳对低频更敏感的特性。参数取值影响帧长400 点 / 25ms太短频率分辨率差太长实时性差帧移160 点 / 10ms控制特征序列长度10ms 是常用值采样率16kHz语音带限 4kHz满足奈奎斯特Mel 通道数24少于 16 区分度变差多于 32 计算量上升MFCC 系数12丢弃 c0 能量剩余 12 维做匹配4.3 DCT 之后的量化存模板时用 Q8 定点DTW 模板如果直接按 float 存一个词 91 帧乘 12 维乘 4 字节约占 4.3KB还能接受但如果词表到 10 个词Flash 和匹配耗时都会明显上升。常见做法是把 MFCC 放大到定点整数再存用 Q8 格式也就是把浮点值乘 256 后取整为 int16。// 把浮点 MFCC 转成 int16 Q8 格式 int16_t mfcc_q8[MFCC_DIM]; for (int d 0; d MFCC_DIM; d) mfcc_q8[d] (int16_t)(mfcc[d] * 256.0f);Q8 的意义是每个系数只占 2 字节且整数运算比浮点快得多。DTW 距离计算时用 int16 做差差值也不会溢出。以后如果要移植到没有 FPU 的型号整套特征提取仍然可以用浮点完成只有模板匹配部分全部走整数。5. 用 DTW 做词模板匹配把词库可靠地存进 STM32MFCC 提取完特征接下来就是训练模板和匹配两部分。训练阶段的输出是词模板识别阶段把实时特征序列和所有模板做 DTW选最近的那个词。词库管理还包括多模板存法和拒识门限这部分直接决定误触发率。5.1 模板训练流程按键录音10 次选最好的训练模板不要只录一次因为同一个人说“开灯”的时长和音量每次都不一样。常见做法是每个词录 8 到 10 遍每一遍在识别时都会得到一个 DTW 距离距离越小说明该样本越接近其他样本被选为模板的优先级越高。实际操作可以用一个按键进入训练模式串口输入词编号按下按键开始录音VAD 检测到语音结束后自动截取特征存进临时的样本列表。录完 10 遍之后程序计算两两之间的 DTW 距离矩阵取与所有其他样本距离之和最小的那一条作为主模板再取距离第二小和第三小的各一条作为辅助模板。模板存储建议放在 AT24C04 这类外接 EEPROM 里不要直接反复擦写 STM32 内部 Flash。内部 Flash 擦写次数有限而且擦写时最好把擦写代码放到 RAM 执行对于刚接触 STM32 的开发者容易因为 Keil 分散加载文件配置不对直接 HardFault。5.2 DTW 距离计算两行滚动数组F103 上跑得动DTW 的递推公式是每个格子取左、上、左上三个方向的最小累积距离再加上当前帧的距离。这里给出一个适合 STM32 的滚动数组版本内存占用固定为2 x MAX_FRAMES#define MAX_FRAMES 160 #define MFCC_DIM 12 static uint32_t dtw_buf[2][MAX_FRAMES]; static uint32_t dtw_dist(const int16_t *tmpl, uint16_t t_idx, const int16_t *feat, uint16_t f_idx) { uint64_t s 0; for (int d 0; d MFCC_DIM; d) { int32_t diff (int32_t)tmpl[t_idx * MFCC_DIM d] - (int32_t)feat[f_idx * MFCC_DIM d]; s (uint32_t)(diff * diff); } if (s 0xFFFFFFFFUL) s 0xFFFFFFFFUL; return (uint32_t)s; } uint32_t dtw_score(const int16_t *tmpl, uint16_t n_tmpl, const int16_t *feat, uint16_t n_feat) { // 第一列只能从上方累加 dtw_buf[0][0] dtw_dist(tmpl, 0, feat, 0); for (uint16_t j 1; j n_feat; j) dtw_buf[0][j] dtw_dist(tmpl, 0, feat, j) dtw_buf[0][j - 1]; // 逐行更新只需要保留上一行和当前行 for (uint16_t i 1; i n_tmpl; i) { uint16_t cur_row i 1; uint16_t pre_row 1 - cur_row; dtw_buf[cur_row][0] dtw_dist(tmpl, i, feat, 0) dtw_buf[pre_row][0]; for (uint16_t j 1; j n_feat; j) { uint32_t base dtw_dist(tmpl, i, feat, j); uint32_t min_prev dtw_buf[pre_row][j]; if (dtw_buf[pre_row][j - 1] min_prev) min_prev dtw_buf[pre_row][j - 1]; if (dtw_buf[cur_row][j - 1] min_prev) min_prev dtw_buf[cur_row][j - 1]; dtw_buf[cur_row][j] base min_prev; } } return dtw_buf[(n_tmpl - 1) 1][n_feat - 1]; }滚动数组的原理很直接因为递推只依赖上一行和当前行前一列不需要把整个二维矩阵都存下来。160 帧乘 160 帧的完整矩阵是 25600 个 4 字节约 100KBF103 放不下滚动数组只需2 x 160 x 4 1280字节远低于 SRAM 限制。匹配一帧特征需要 12 维差平方求和一个 100 帧模板和一个 90 帧特征做 DTW大约执行 9000 次递推。F103 在 72MHz 下跑完整匹配加三个词的比选耗时约几十毫秒人耳几乎察觉不到。识别时机放在 VAD 判定一句话结束后执行不会影响实时性。5.3 拒识门限与多词库管理让“没说话”不出结果DTW 找最近模板还不够因为随便一句“你好”也会匹配到“开灯”模板上。正确做法是在所有匹配分数里找出最小值和次小值再根据归一化后的最小分数决定是否输出结果。归一化这一笔必须做DTW 分数会随语音长度增长1 秒音频比 0.5 秒音频总分更高。把dtw_score除以min(n_tmpl, n_feat)得到每帧平均距离这个值才能和模板训练时统计的基准对比。判定逻辑大致是uint32_t best 0xFFFFFFFF, second 0xFFFFFFFF; uint16_t best_id 0; for (uint16_t t 0; t template_count; t) { uint32_t s dtw_score(template_list t, ...); if (s best) { second best; best s; best_id t; } else if (s second) { second s; } } uint32_t norm_best best / best_frame_count; if (norm_best reject_threshold) return SPEECH_UNKNOWN; if (second - best second * 0.15f) return SPEECH_UNKNOWN; return best_id;reject_threshold是训练阶段统计出的值。每个词的主模板跟辅助模板做 DTW把 10 个训练样本两两比较的平均距离乘 1.2 到 1.5作为拒识上界。次小值和最小值的差值比例 15% 是一个经验起点它用来防止“开灯”和“关灯”两个词得分太接近时误判实际项目中把语音特征矩阵导出来看大概率要把这个比例调到 20% 以上才能压制误触发。6. 用 UART 把 MFCC 特征导出到 PC直接看算法输出来调参前面章节把识别链路搭完了但真正让项目从“能跑”变成“稳定”的是最后这一组调试技巧。不要靠人耳去猜为什么误识别把特征从板子上搬出来看。6.1 串口打印每帧 MFCC用 Python 画热力图在 VAD 判定为语音的时段内把每帧的 12 个 MFCC 系数用逗号分隔通过串口发出来。为了方便解析每帧结束后加一个换行。串口波特率用 11520016kHz 采样下每帧间隔 10ms12 个数值加换行大约 40 字节满足带宽要求。import numpy as np import matplotlib.pyplot as plt feat np.loadtxt(mel13.csv, delimiter,) plt.matshow(feat[:, 1:].T, aspectauto, cmapjet) plt.xlabel(frame index) plt.ylabel(MFCC dimension) plt.colorbar() plt.show()画出来的热力图能立刻暴露问题如果整张图全是纯色通常是 ADC 偏置不对信号根本没进来如果特征在时间轴上呈条纹状规律变化说明采集链路正常如果同一词两次录音的图形差异极大先查模板训练流程而不是继续调 DTW。6.2 把拒识门限存进 EEPROM免去反复烧录每次调阈值都重新编译下载半天就耗在点 Keil 按钮上。正确的做法是在设备里保留一个阈值配置区通过串口命令直接修改修改后的值写入 EEPROM下次上电自动加载。本文这套 MFCCDTW 方案需要现场调的参数其实是三个VAD 静音能量阈值、DTW 拒识阈值、次小值比例。每次改动后用同样的录音文件回放验证记录“喊 50 次能对几次”比随机试参数可靠得多。提示回放验证时不要用电脑音箱对着麦克风放尽量用手机耳机口输出固定音量的录音否则房间混响和喇叭频率响应会干扰你的判断。最后再强调一个容易漏掉的细节换一块板子、换一个麦克风静音能量阈值和拒识阈值都要重新测一遍不能一劳永逸地沿用上一份参数。把训练和测试脚本固化成串口命令项目从 40% 识别率拉到 90% 以上只差这一步。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价