在线时间408 小时
UID3006061
注册时间2015-3-23
NXP金币1170
TA的每日心情 | 开心 2 小时前 |
---|
签到天数: 815 天 [LV.10]以坛为家III
金牌会员
- 积分
- 5494
- 最后登录
- 2024-4-19
|
本帖最后由 leo121_3006061 于 2017-11-5 14:10 编辑
不知不觉已经到了最终任务了,网上搜索了不少关于语音识别(语音控制)的资料,脑补了一下,基本了解了(单机语音识别)以下几个必备的知识, 关键词:1.HWVAD(代替软VAD),实现数据采集,噪声过滤
2.MFCC 语音的Mel频率倒谱系数(MFCC),主要由FFT(快速傅立叶变换)来实现。
3.DTW 动态时间弯折算法与特征模板相匹配,最终输出识别结果
4.matlab 用Matlab对上述算法进行仿真,经多次试验得出算法中所需各系数的最优值(我本人对这个不是太熟悉,略过)
语音处理流程:先通过LPC54110自带的硬件HWVAD采集有效的语音数据,利用DMA直接传送给RAM(不占用MCU资源),,再把语音数据发送给MFCC处理,然后通过DTW算法进行模版匹配,找到最相近的语音,返回识别结果。本例实现0~9的数字识别,其中0~7对应控制板子的8个led灯
说明:目前我的工程实现了以上流程的每个步骤,但是还没有把这些步骤按照整个个流程串联起来,后边要花点时间来做这件事情。
整个工程是在例程里的dmic_ hwvad的基础上修改而成的。并且引入ARM_math,如下图所示
如果编译报错信息为==》
#error "Define according the used Cortex core ARM_MATH_CM7, ARM_MATH_CM4, ARM_MATH_CM3, ARM_MATH_CM0PLUS or ARM_MATH_CM0"还需要在Project->Options for target"XXXX")中的C/C++选项卡的Preprocessor Symbols栏的Define中加入如下的语句:ARM_MATH_CM4, __FPU_PRESENT=1, __FPU_USED =1, __CC_ARM
第一步,用dmic_hwvad实现数据采集,过滤噪声
dmic_init()采用标准的例程里的代码,在前边的任务里都做过了,毋须多言了
dmic_dma_init() 如下图
我们要在void DMIC0_HWVAD_Callback(void)里实现语音数据采集,红框下边是打印输出采集到的数据
为了验证采集的数据,我们对着DMIC说话,或者拍手,通过串口助手,显示采集数据是变化的,(也可以通过wm8904来播放采集到的数据,加以验证)
第二步 实现MFCC,第一步采集到的数据,是非常复杂的语音数据,是不能直接用于识别的,需要通过MFCC进行处理,语音信号特征提取。提取有效语音中每帧语音信号的Mel频率倒谱系数(MFCC)系数。- <blockquote>/******* MFCC.C *******/
复制代码
乱码的部分请参考下边的截图
第三步 要建立一个性能良好的语音识别系统仅有好的语音特征还不够,还要有适当的语音识别的模型和算法。在现阶段,语音识别的过程是根据模式匹配的原则,计算未知语音模式与语音模板库中的每一个模板的距离测度,从而得到最佳的匹配模式。目前,语音识别所应用的模型匹配方法主要有动态时间弯折(DTW:Dynamic Time Warping)、隐马尔可夫模型(HMM:Hidden Markov Model)和人工神经网络(ANN:Artificial Neural Networks)等。当今孤立词识别领域最常用的识别算法是DTW和HMM。
DTW算法是较早的一种模式匹配和模型训练技术,它应用动态规划的方法成功解决了语音信号特征参数序列比较时时长不等的难题,在孤立词语音识别中获得了良好的性能。DTW算法是建立在动态规划(DP:Dynamic Programming)的理论基础上的。动态规划是一个很有效的方法来求取一个问题的最佳解。其中心思想简单的说可以描述为:在一条最佳的路径上,其中任意一条子路径也都必须是相关子问题的最佳路径,否则原路径就不是最佳路径。下边附上代码
- /******* DTW.C ********/
- #include <math.h>
- #include "MFCC.H"
- #include "DTW.H"
- #include "fsl_device_registers.h"
- #include "fsl_debug_console.h"
- #include "board.h"
- #include "fsl_dmic.h"
- #include <stdlib.h>
- #include <string.h>
- #include "app_led.h"
- #include "app_key.h"
- #include "app_dmic.h"
- #include "pin_mux.h"
- #include <stdbool.h>
- #include "stdint.h"
- /*
- DTWËã·¨ ͨ¹ý¾Ö²¿ÓÅ»¯µÄ·½·¨ÊµÏÖ¼ÓȨ¾àÀë×ܺÍ×îС
-
- ʱ¼ä¹æÕûº¯Êý£º
- C={c(1),c(2),¡,c(N)}
- NΪ·¾¶³¤¶È£¬c(n)=(i(n),j(n))±íʾµÚn¸öÆ¥ÅäµãÊÇÓвο¼Ä£°åµÄ
- µÚi(n)¸öÌØÕ÷ʸÁ¿Óë´ý²âÄ£°åµÄµÚj(n)¸öÌØÕ÷ʸÁ¿¹¹³ÉµÄÆ¥Åäµã¶Ô£¬Á½
- ÕßÖ®¼äµÄ¾àÀëd(x(i(n)),y(j(n)))³ÆΪƥÅä¾àÀë¡£
- ʱ¼ä¹æÕûº¯ÊýÂú×ãÒ»ÏÂÔ¼Êø£º
- 1.µ¥µ÷ÐÔ£¬¹æÕûº¯Êýµ¥µ÷Ôö¼Ó¡£
- 2.ÆðµãÖÕµãÔ¼Êø£¬Æðµã¶ÔÆðµã£¬ÖÕµã¶ÔÖյ㡣
- 3.Á¬ÐøÐÔ£¬²»ÔÊÐíÌø¹ýÈκÎÒ»µã¡£
- 4.×î´ó¹æÕûÁ¿²»³¬¹ýijһ¼«ÏÞÖµ¡£|i(n)-j(n)|<M,MΪ´°¿í¡£¹æÕû
- º¯ÊýËù´¦µÄÇøÓòλÓÚƽÐÐËıßÐÎÄÚ¡£¾Ö²¿Â·¾¶Ô¼Êø£¬ÓÃÓÚÏÞÖƵ±µÚn²½
- Ϊ(i(n),j(n))ʱ£¬Ç°¼¸²½´æÔÚ¼¸ÖÖ¿ÉÄܵÄ·¾¶¡£
- DTW²½Ö裺
- 1.³õʼ»¯¡£Áîi(0)=j(0)=0,i(N)=in_frm_num,j(N)=mdl_frm_num.
- È·¶¨Ò»¸öƽÐÐËıßÐΣ¬ÓÐÁ½¸öλÓÚ(0,0)ºÍ(in_frm_num,mdl_frm_num)µÄ¶¥µã£¬ÏàÁÚб±ßб
- ÂÊ·Ö±ðÊÇ2ºÍ1/2.¹æÕûº¯Êý²»¿É³¬³ö´ËƽÐÐËıßÐΡ£
- 2.µÝÍÆÇóÀۼƾàÀë¡£
-
- ÈôÊäÈëÌØÕ÷ÓëÌØÕ÷Ä£°åµÄÖ¡Êý²î±ð¹ý´ó£¬Ö±½Ó½«Æ¥Åä¾àÀëÉèΪ×î´ó
- frm_in_num<(frm_mdl_num/2)||frm_in_num>(frm_mdl_num*2)
- */
- /*
- »ñÈ¡Á½¸öÌØÕ÷ʸÁ¿Ö®¼äµÄ¾àÀë
- ²ÎÊý
- frm_ftr1 ÌØÕ÷ʸÁ¿1
- frm_ftr2 ÌØÕ÷ʸÁ¿2
- ·µ»ØÖµ
- dis ʸÁ¿¾àÀë
- */
- uint32_t get_dis(int16_t *frm_ftr1, int16_t *frm_ftr2)
- {
- uint8_t i;
- uint32_t dis;
- int32_t dif; //Á½Ê¸Á¿Ïàͬά¶ÈÉϵIJîÖµ
-
- dis=0;
- for(i=0;i<mfcc_num;i++)
- {
- //PRINTF("dis=%d ",dis);
- dif=frm_ftr1[i]-frm_ftr2[i];
- dis+=(dif*dif);
- }
- //PRINTF("dis=%d ",dis);
- dis=sqrtf(dis);
- //PRINTF("%d\r\n",dis);
- return dis;
- }
- //ƽÐÐËıßÐÎÁ½ÍâÁ½¶¥µã X×ø±êÖµ
- static uint16_t X1; //Éϱ߽»µã
- static uint16_t X2; //ϱ߽»µã
- static uint16_t in_frm_num; //ÊäÈëÌØÕ÷Ö¡Êý
- static uint16_t mdl_frm_num;//ÌØÕ÷Ä£°åÖ¡Êý
- #define ins 0
- #define outs 1
- /*
- ·¶Î§¿ØÖÆ
- */
- uint8_t dtw_limit(uint16_t x, uint16_t y)
- {
- if(x<X1)
- {
- if(y>=((2*x)+2))
- {
- return outs;
- }
- }
- else
- {
- if((2*y+in_frm_num-2*mdl_frm_num)>=(x+4))
- {
- return outs;
- }
- }
-
- if(x<X2)
- {
- if((2*y+2)<=x)
- {
- return outs;
- }
- }
- else
- {
- if((y+4)<=(2*x+mdl_frm_num-2*in_frm_num))
- {
- return outs;
- }
- }
-
- return ins;
- }
- /*
- DTW ¶¯Ì¬Ê±¼ä¹æÕû
- ²ÎÊý
- ftr_in :ÊäÈëÌØÕ÷Öµ
- ftr_mdl :ÌØÕ÷Ä£°æ
- ·µ»ØÖµ
- dis :ÀÛ¼ÆÆ¥Åä¾àÀë
- */
- uint32_t dtw(v_ftr_tag *ftr_in, v_ftr_tag *frt_mdl)
- {
- uint32_t dis;
- uint16_t x,y;
- uint16_t step;
- int16_t *in;
- int16_t *mdl;
- uint32_t up,right,right_up;
- uint32_t min;
-
- in_frm_num=ftr_in->frm_num;
- mdl_frm_num=frt_mdl->frm_num;
-
- if((in_frm_num>(mdl_frm_num*2))||((2*in_frm_num)<mdl_frm_num))
- {
- //PRINTF("in_frm_num=%d mdl_frm_num=%d\r\n", in_frm_num,mdl_frm_num);
- return dis_err;
- }
- else
- {
- // ¼ÆËãÔ¼ÊøƽÐÐËıßÐζ¥µãÖµ
- X1=(2*mdl_frm_num-in_frm_num)/3;
- X2=(4*in_frm_num-2*mdl_frm_num)/3;
- in=ftr_in->mfcc_dat;
- mdl=frt_mdl->mfcc_dat;
- dis=get_dis(in,mdl);
- x=1;
- y=1;
- step=1;
- do
- {
- up=(dtw_limit(x,y+1)==ins)?get_dis(mdl+mfcc_num,in):dis_err;
- right=(dtw_limit(x+1,y)==ins)?get_dis(mdl,in+mfcc_num):dis_err;
- right_up=(dtw_limit(x+1,y+1)==ins)?get_dis(mdl+mfcc_num,in+mfcc_num):dis_err;
-
- min=right_up;
- if(min>right)
- {
- min=right;
- }
- if(min>up)
- {
- min=up;
- }
-
- dis+=min;
-
- if(min==right_up)
- {
- in+=mfcc_num;
- x++;
- mdl+=mfcc_num;
- y++;
- }
- else if(min==up)
- {
- mdl+=mfcc_num;
- y++;
- }
- else
- {
- in+=mfcc_num;
- x++;
- }
- step++;
- //PRINTF("x=%d y=%d\r\n",x,y);
- }
- while((x<in_frm_num)&&(y<mdl_frm_num));
- //PRINTF("step=%d\r\n",step);
- }
- return (dis/step);//²½³¤¹éÒ»»¯
- }
- void get_mean(int16_t *frm_ftr1, int16_t *frm_ftr2, int16_t *mean)
- {
- uint8_t i;
- for(i=0;i<mfcc_num;i++)
- {
- mean[i]=(frm_ftr1[i]+frm_ftr2[i])/2;
- PRINTF("x=%d y=%d ",frm_ftr1[i],frm_ftr2[i]);
- PRINTF("mean=%d\r\n",mean[i]);
- }
- }
- /*
- ´ÓÁ½ÌØÕ÷ʸÁ¿»ñÈ¡ÌØÕ÷Ä£°å
- ²ÎÊý
- ftr_in1 :ÊäÈëÌØÕ÷Öµ
- ftr_in2 :ÊäÈëÌØÕ÷Öµ
- ftr_mdl :ÌØÕ÷Ä£°æ
- ·µ»ØÖµ
- dis :ÀÛ¼ÆÆ¥Åä¾àÀë
- */
- uint32_t get_mdl(v_ftr_tag *ftr_in1, v_ftr_tag *ftr_in2, v_ftr_tag *ftr_mdl)
- {
- uint32_t dis;
- uint16_t x,y;
- uint16_t step;
- int16_t *in1;
- int16_t *in2;
- int16_t *mdl;
- uint32_t up,right,right_up;
- uint32_t min;
-
- in_frm_num=ftr_in1->frm_num;
- mdl_frm_num=ftr_in2->frm_num;
-
- if((in_frm_num>(mdl_frm_num*2))||((2*in_frm_num)<mdl_frm_num))
- {
- return dis_err;
- }
- else
- {
- // ¼ÆËãÔ¼ÊøƽÐÐËıßÐζ¥µãÖµ
- X1=(2*mdl_frm_num-in_frm_num)/3;
- X2=(4*in_frm_num-2*mdl_frm_num)/3;
- in1=ftr_in1->mfcc_dat;
- in2=ftr_in2->mfcc_dat;
- mdl=ftr_mdl->mfcc_dat;
- dis=get_dis(in1,in2);
- get_mean(in1, in2, mdl);
- x=1;
- y=1;
- step=1;
- do
- {
- up=(dtw_limit(x,y+1)==ins)?get_dis(in2+mfcc_num,in1):dis_err;
- right=(dtw_limit(x+1,y)==ins)?get_dis(in2,in1+mfcc_num):dis_err;
- right_up=(dtw_limit(x+1,y+1)==ins)?get_dis(in2+mfcc_num,in1+mfcc_num):dis_err;
-
- min=right_up;
- if(min>right)
- {
- min=right;
- }
- if(min>up)
- {
- min=up;
- }
-
- dis+=min;
-
- if(min==right_up)
- {
- in1+=mfcc_num;
- x++;
- in2+=mfcc_num;
- y++;
- }
- else if(min==up)
- {
- in2+=mfcc_num;
- y++;
- }
- else
- {
- in1+=mfcc_num;
- x++;
- }
- step++;
-
- mdl+=mfcc_num;
- get_mean(in1, in2, mdl);
-
- PRINTF("x=%d y=%d\r\n",x,y);
- }
- while((x<in_frm_num)&&(y<mdl_frm_num));
- PRINTF("step=%d\r\n",step);
- ftr_mdl->frm_num=step;
- }
- return (dis/step); //²½³¤¹éÒ»»¯
- }
复制代码 还是乱码,先不管了,等全部完成了 ,大家如果有兴趣,我就把代码共享出来。(代码也是来自于网络,我做了些修改)
编译都能通过了。后边找时间把整个流程串起来,就实现简单的语音识别了。
今天9月9号周末,终于可以有时间继续未完的任务,拖拉了很久,争取这周完成。
先回忆一下我们用的这块板子的主要的2个特性,flash是256k,ram是192k,ram够大了,这样我就考虑简单点的办法,直接把单个语音模板(即只录制一个数字语音)放到缓存里,待识别的语音也放到缓存里,分别经过VAD,MFCC处理,然后再用DTW去比较识别。通过串口反馈回结果为真并控制相应的led亮起来。
经过几天努力,终于有个了雏形,识别率不是很高。后边再慢慢的改进吧。操作步骤如下:
1 .首先下载编译好的hex到板子
2.程序里定义了key0用于录制语音模板,key1用于录制语音,用以识别
录制语音的时候,0~9数字,要多重复说几次,例如,我录制模板时候,我想录制数字3,按下key0,串口助手会提示开始录制,那么尽可能重复说3这个数字。要不然会提示录制模板失败,主要是录制时间短,还没控制好开始和结束(后边想想办法利用hwvad回调函数),同样当按下key1开始语音识别的时候,也尽可能多重复说数字3,这样就可以提高识别率,如下图串口终端所示
程序里也要给这个语音模板定义3,这样识别正确才会返回数字3,也可以是其他数字,一样的操作方式
识别结果正确,相应的红色的led3亮起
综述,仅仅实现了,单个数字录制模板,单个数字语音识别,其实单字识别也可以的。期望能起到抛砖引玉的作用。程序我后续整理一下放上来,个人水平有限,应该可优化的地方很多,期望社区大神来补充完善。至此最终任务基本完成了。hex文件
dmic_hwvad.zip
(20.95 KB, 下载次数: 56)
|
|