【ESP32-Korvo测评】(8)语音识别用法测试
<p> ESP-Skainet 提供了一个免费的离线语音识别算法,可以对自定义的若干个关键词(短语)进行识别。离线意思就是识别过程真正在 ESP32 中处理完成,不需要连网。虽然这种语音识别的功能单一,毕竟是提供了比按键操作高级得多的交互手段,恰当运用可以极大提升作品的用户体验。<br />再回顾一下 get_started 例子中语音识别是怎么调用的:<br />
</p>
<pre>
<code class="language-cpp">while (1) {
rb_read(agc_rb, (uint8_t *)buffer, audio_chunksize * sizeof(int16_t), portMAX_DELAY);
if (detect_flag == 0) {
int r = wakenet->detect(model_data, buffer);
if (r) {
float ms = (chunks * audio_chunksize * 1000.0) / frequency;
printf("%.2f: %s DETECTED.\n", (float)ms / 1000.0, wakenet->get_word_name(model_data, r));
detect_flag = 1;
printf("-----------------LISTENING-----------------\n\n");
rb_reset(rec_rb);
rb_reset(ns_rb);
rb_reset(agc_rb);
}
} else {
int command_id = multinet->detect(model_data_mn, buffer);
mn_chunks++;
if (mn_chunks == chunk_num || command_id > -1) {
mn_chunks = 0;
detect_flag = 0;
if (command_id > -1) {
speech_commands_action(command_id);
} else {
printf("can not recognize any speech commands\n");
}
printf("\n-----------awaits to be waken up-----------\n");
rb_reset(rec_rb);
rb_reset(ns_rb);
rb_reset(agc_rb);
}
}
chunks++;
}</code></pre>
<p> 这个循环在不断地获取声音,然后调用识别算法。根据 detect_flag 标志分为两种情况:一是识别唤醒词,另一种是唤醒后识别命令词。用的算法也不同,前者是 wakenet, 后者是 multinet. 因为识别的复杂度不同,设计两个分开的算法也是有道理的。</p>
<p> </p>
<p> <strong>int r = wakenet->detect(model_data, buffer);</strong></p>
<p>这一个调用是识别是否有唤醒词。buffer 是采集的语音数据片段,model_data 来自 wakenetTask() 任务的参数。再看主函数中如何生成它:<br />
<strong>model_iface_data_t *model_data = wakenet->create(model_coeff_getter, DET_MODE_90);</strong><br />
实际上用的是已经定义好的东西:<br />
<strong>static const esp_wn_iface_t *wakenet = &WAKENET_MODEL;<br />
static const model_coeff_getter_t *model_coeff_getter = &WAKENET_COEFF;</strong><br />
在 esp-sr/wake_word_engine/include/esp_wn_models.h 中找到 WAKENET_MODEL 和 WAKENET_COEFF 的定义出处——它们都是根据配置文件从预定义的常量中选择。这些常量在库文件里面,对应到选择的算法和不同的唤醒词。算法的实现过程以及训练后的模型数据是什么样就无从分析了。</p>
<p> </p>
<p> <strong>int command_id = multinet->detect(model_data_mn, buffer);</strong><br />
这个调用则需要返回识别到哪一个命令词。除了 multinet 是来自 MULTINET_MODEL (也是通过配置选择库中的几个版本之一)外,其参数 model_data_mn 是全局变量,在主程序中由<br />
<strong>model_data_mn = multinet->create(&MULTINET_COEFF, 4000);</strong><br />
创建。在 esp_mn_models.h 中有这段预处理代码</p>
<pre>
<code class="language-cpp">#if CONFIG_SR_CHINESE && CONFIG_SINGLE_RECOGNITION
#include "multinet1_ch.h"
#define MULTINET_MODEL esp_sr_multinet1_quantized_cn
#define MULTINET_COEFF get_coeff_multinet1_ch
#elif CONFIG_SR_ENGLISH && CONFIG_SINGLE_RECOGNITION
#include "multinet1_en.h"
#define MULTINET_MODEL esp_sr_multinet1_quantized_en
#define MULTINET_COEFF get_coeff_multinet1_en
#elif CONFIG_SR_CHINESE && CONFIG_CONTINUOUS_RECOGNITION
#include "multinet1_ch.h"
#define MULTINET_MODEL esp_sr_multinet2_quantized_cn
#define MULTINET_COEFF get_coeff_multinet1_ch
#else
#error No valid wake word selected.
#endif</code></pre>
<p>也就是意味着,命令词识别的形式有中文单次、英文单次和中文连续,这三种。</p>
<p> </p>
<p> 在 mn_process_commands.c 中写了一个 get_id_name() 函数:</p>
<pre>
<code class="language-cpp">char *get_id_name(int i)
{
if (i == 0)
return MN_SPEECH_COMMAND_ID0;
else if (i == 1)
return MN_SPEECH_COMMAND_ID1;
else if (i == 2)
return MN_SPEECH_COMMAND_ID2;
else if (i == 3)
return MN_SPEECH_COMMAND_ID3;
else if (i == 4)
return MN_SPEECH_COMMAND_ID4;
</code></pre>
<p>而这些宏定义,源头来自配置文件,也就是自定义的要识别的命令词了。在前面的实验中,我为了填加命令词,导致整个工程呀重新编译。那么直接改这个文件(虽然它不属于库里面的)应该容易些。我猜想在 multinet->create() 调用的时候,这个 get_id_name() 函数会被调用很多次。</p>
<p> </p>
<p> 修改一下:</p>
<pre>
<code class="language-cpp">char *get_id_name(int i)
{
printf("get_id_name(%d) ",i);
switch(i)
{
case 0: return "ling";
case 1: return "yi";
case 2: return "er";
case 3: return "san";
case 4: return "si";
case 5: return "wu";
case 6: return "liu";
case 7: return "qi";
case 8: return "ba";
case 9: return "jiu";
case 10:return "shi";
default: return "";
}
}</code></pre>
<p>然后重新编译,下载。运行,可以看到终端打印的命令词信息部分,如我所料。</p>
<p><span style="font-size:12px;"><span style="font-family:Courier;">I (1610) MN: ---------------------SPEECH COMMANDS---------------------<br />
get_id_name(0) I (1610) MN: Command ID0, phrase 0: ling<br />
get_id_name(1) I (1620) MN: Command ID1, phrase 1: yi<br />
get_id_name(2) I (1630) MN: Command ID2, phrase 2: er<br />
get_id_name(3) I (1630) MN: Command ID3, phrase 3: san<br />
get_id_name(4) I (1640) MN: Command ID4, phrase 4: si<br />
get_id_name(5) I (1640) MN: Command ID5, phrase 5: wu<br />
get_id_name(6) I (1650) MN: Command ID6, phrase 6: liu<br />
get_id_name(7) I (1650) MN: Command ID7, phrase 7: qi<br />
get_id_name(8) I (1660) MN: Command ID8, phrase 8: ba<br />
get_id_name(9) I (1670) MN: Command ID9, phrase 9: jiu<br />
get_id_name(10) I (1670) MN: Command ID10, phrase 10: shi<br />
get_id_name(11) get_id_name(12) get_id_name(13) get_id_name(14) get_id_name(15) get_id_name(16) get_id_name(17) get_id_name(18) get_id_name(19) get_id_name(20) get_id_name(21) get_id_name(22) get_id_name(23) get_id_name(24) get_id_name(25) get_id_name(26) get_id_name(27) get_id_name(28) get_id_name(29) get_id_name(30) get_id_name(31) get_id_name(32) get_id_name(33) get_id_name(34) get_id_name(35) get_id_name(36) get_id_name(37) get_id_name(38) get_id_name(39) get_id_name(40) get_id_name(41) get_id_name(42) get_id_name(43) get_id_name(44) get_id_name(45) get_id_name(46) get_id_name(47) get_id_name(48) get_id_name(49) get_id_name(50) get_id_name(51) get_id_name(52) get_id_name(53) get_id_name(54) get_id_name(55) get_id_name(56) get_id_name(57) get_id_name(58) get_id_name(59) get_id_name(60) get_id_name(61) get_id_name(62) get_id_name(63) get_id_name(64) get_id_name(65) get_id_name(66) get_id_name(67) get_id_name(68) get_id_name(69) get_id_name(70) get_id_name(71) get_id_name(72) get_id_name(73) get_id_name(74) get_id_name(75) get_id_name(76) get_id_name(77) get_id_name(78) get_id_name(79) get_id_name(80) get_id_name(81) get_id_name(82) get_id_name(83) get_id_name(84) get_id_name(85) get_id_name(86) get_id_name(87) get_id_name(88) get_id_name(89) get_id_name(90) get_id_name(91) get_id_name(92) get_id_name(93) get_id_name(94) get_id_name(95) get_id_name(96) get_id_name(97) get_id_name(98) get_id_name(99) I (1800) MN: ---------------------------------------------------------</span></span><br />
</p>
<p> 按上面这么定的语音命令(0~10的读音)测试,识别效果还不能令我满意。最难识别的是"liu":极少成功,其次"ling","er"和 "wu"也难识别。"jiu"更容易识别成7, 不知何故。还能遇到识别中 ESP32 重起的情况。</p>
<p>奇怪这些"liu""ling","er"和 "wu"为什难以识别,与硬件还是软件有关</p>
<p>感谢分享,感觉挺好的,离线语音识别也够一些基础测试用了!</p>
页:
[1]