语音识别 订阅
语音识别是一门交叉学科。近二十年来,语音识别技术取得显著进步,开始从实验室走向市场。人们预计,未来10年内,语音识别技术将进入工业、家电、通信、汽车电子、医疗、家庭服务、消费电子产品等各个领域。 语音识别听写机在一些领域的应用被美国新闻界评为1997年计算机发展十件大事之一。很多专家都认为语音识别技术是2000年至2010年间信息技术领域十大重要的科技发展技术之一。 语音识别技术所涉及的领域包括:信号处理、模式识别、概率论和信息论、发声机理和听觉机理、人工智能等等。 展开全文
语音识别是一门交叉学科。近二十年来,语音识别技术取得显著进步,开始从实验室走向市场。人们预计,未来10年内,语音识别技术将进入工业、家电、通信、汽车电子、医疗、家庭服务、消费电子产品等各个领域。 语音识别听写机在一些领域的应用被美国新闻界评为1997年计算机发展十件大事之一。很多专家都认为语音识别技术是2000年至2010年间信息技术领域十大重要的科技发展技术之一。 语音识别技术所涉及的领域包括:信号处理、模式识别、概率论和信息论、发声机理和听觉机理、人工智能等等。
信息
外文名
Voice recognition
发展史
1952年贝尔研究所Davis
中国发展
中国的语音识别研究起始
中文名
语音识别
简    介
与机器进行语音交流
语音识别简介
与机器进行语音交流,让机器明白你说什么,这是人们长期以来梦寐以求的事情。中国物联网校企联盟形象得把语音识别比做为“机器的听觉系统”。语音识别技术就是让机器通过识别和理解过程把语音信号转变为相应的文本或命令的高技术。 语音识别技术主要包括特征提取技术、模式匹配准则及模型训练技术三个方面。语音识别技术车联网也得到了充分的引用,例如在翼卡车联网中,只需按一键通客服人员口述即可设置目的地直接导航,安全、便捷。
收起全文
精华内容
下载资源
问答
  • 语音识别

    万次阅读 2017-10-27 10:49:32
    语音识别 语音识别主流开源框架:HTK、Kaldi、CMUSphinx。 因为目前只接触了Sphinx,对于另外两个框架没有太深入研究。 Kaldi适用于在服务器搭建的语音识别系统,也可以适用于android,但没有实验过,根据官方提供...

    语音识别

          语音识别根据实际需求的不同也会有所不同。目前主要追求大词汇量、连续、非特定人。

           语音识别主流开源框架:HTK、Kaldi、CMUSphinx。

    因为目前只接触了Sphinx,对于另外两个框架没有太深入研究。

            1)Kaldi适用于在服务器搭建的语音识别系统,也可以适用于android,但没有实验过,根据官方提供方法:http://jcsilva.github.io/2017/03/18/compile-kaldi-android/

    其使用模型很多:DNN-HMM、GMM-HMM等

            2)PocketSphinx是应用于嵌入式而开发的(Sphinx用于联网后台识别系统,Sphinx现在版本是Sphinx4-5prealpha,在0.8版本时还有支持中文模型,现在已经没有中文语音模型了)。PocketSphinx和Sphinx由于不同的解码器使用了不同的声学模型。

     

            该文讲解:将语音识别大概流程和涉及到的一些算法汇总,最后PocketSphinx安装、编译、训练、结构以及涉及到的一些坑。


    一、语音的特征

           推荐http://blog.csdn.net/u013378306/article/details/65951935该博客文章,请一定细读,讲解语音产生、基本名词和语音特征,比较详尽。

    二、识别流程及原理

           对于语音识别系统而言,第一步要检测是否有语音输入,即,语音激活检测(VAD)。在低功耗设计中,相比于语音识别的其它部分,VAD采用always on的工作机制。当VAD检测到有语音输入之后,VAD便会唤醒后续的识别系统。识别系统总体流程如图2所示,主要包括特征提取、识别建模及模型训练、解码得到结果几个步骤。


    图2.语音识别系统

    1、VAD(语音激活检测)

           用于判断什么时候有语音输入,什么时候是静音状态。语音识别后续的操作都是在VAD截取出来的有效片段上进行,从而能够减小语音识别系统噪声误识别率及系统功耗。在近场环境下,由于语音信号衰减有限,信噪比(SNR)比较高,只需要简单的方式(比如过零率、信号能量)来做激活检测。但是在远场环境中,由于语音信号传输距离比较远,衰减比较严重,因而导致麦克风采集数据的SNR很低,这种情况下,简单的激活检测方法效果很差。使用深度神经网络(DNN)做激活检测是基于深度学习的语音识别系统中常用的方法(在该方法下,语音激活检测即为一个分类问题)。在MIT的智能语音识别芯片中使用了精简版的DNN来做VAD,该方法在噪声比较大的情况下也具有很好的性能。但是更复杂的远场环境中,VAD仍然是未来研究的重点。

    2、特征提取

           梅尔频率倒谱系数(MFCC)是最为常用的语音特征,梅尔频率是基于人耳听觉特征提取出来的。MFCC主要由预加重、分帧、加窗、快速傅里叶变换(FFT)、梅尔滤波器组、离散余弦变换几部分组成,其中FFT与梅尔滤波器组是MFCC最重要的部分。但是近年研究表明,对于语音识别而言,梅尔滤波器组不一定是最优方案。受限的玻尔兹曼机(RBM)、卷积神经网络(CNN)、CNN-LSTM-DNN(CLDNN)等深度神经网络模型作为一个直接学习滤波器代替梅尔滤波器组被用于自动学习的语音特征提取中,并取得良好的效果。

           目前已经证明,在特征提取方面,CLDNN比对数梅尔滤波器组有明显的性能优势。基于CLDNN的特征提取过程可以总结为:在时间轴上的卷积、pooling、pooled信号进入到CLDNN中三个步骤。

           远场语音识别领域,由于存在强噪声、回响等问题,麦克风阵列波束成形仍然是主导方法。

           另外,现阶段,基于深度学习的波束成形方法在自动特征提取方面亦取得了众多研究成果。

           通俗形象点解释特征提取过程

           在开始识别之前,有时需要把首尾端的静音切除,降低对后续步骤造成的干扰。这个静音切除的操作一般称为 VAD,需要用到信号处理的一些技术。

      要对声音进行分析,需要对声音分帧,也就是把声音切开成一小段一小段,每小段称为一帧。分帧操作一般不是简单的切开,而是使用移动窗函数来实现,这里不详述。帧与帧之间一般是有交叠的,就像下图这样:

      

      图中,每帧的长度为 25 毫秒,每两帧之间有 25-10=15 毫秒的交叠。我们称为以帧长 25 ms、帧移 10 ms 分帧。图中,每帧的长度为 25 毫秒,每两帧之间有 25-10=15 毫秒的交叠。我们称为以帧长 25 ms、帧移 10 ms 分帧。

      分帧后,语音就变成了很多小段。但波形在时域上几乎没有描述能力,因此必须将波形作变换。常见的一种变换方法是提取 MFCC 特征,根据人耳的生理特性,把每一帧波形变成一个多维向量,可以简单地理解为这个向量包含了这帧语音的内容信息。这个过程叫做声学特征提取。实际应用中,这一步有很多细节,声学特征也不止有 MFCC 这一种,具体这里不讲。

      至此,声音就成了一个 12 行(假设声学特征是 12 维)、N 列的一个矩阵,称之为观察序列,这里 N 为总帧数。观察序列如下图所示,图中,每一帧都用一个 12 维的向量表示,色块的颜色深浅表示向量值的大小。(不过一般提取MFCC的39维特征值)

      

    3、识别建模(解码)

           语音识别本质上是音频序列到文字序列转化的过程,即在给定语音输入的情况下,找到概率最大的文字序列。基于贝叶斯原理,可以把语音识别问题分解为给定文字序列出现这条语音的条件概率以及出现该条文字序列的先验概率,对条件概率建模所得模型即为声学模型,对出现该条文字序列的先验概率建模所得模型是语言模型。

          通俗形象点解释识别过程

          首先解释两个概念

      1.音素:单词的发音由音素构成。对英语,一种常用的音素集是卡内基梅隆大学的一套由 39 个音素构成的音素集,参见 The CMU Pronouncing DicTIonary。汉语一般直接用全部声母和韵母作为音素集,另外汉语识别还分有调无调,不详述。

      2.状态:这里理解成比音素更细致的语音单位就行啦。通常把一个音素划分成 3 个状态。

      语音识别是怎么工作的呢?实际上一点都不神秘,无非是:

      第一步,把帧识别成状态(难点);

      第二步,把状态组合成音素;

      第三步,把音素组合成单词。

      如下图所示:

      

      图中,每个小竖条代表一帧,若干帧语音对应一个状态,每三个状态组合成一个音素,若干个音素组合成一个单词。也就是说,只要知道每帧语音对应哪个状态了,语音识别的结果也就出来了。图中,每个小竖条代表一帧,若干帧语音对应一个状态,每三个状态组合成一个音素,若干个音素组合成一个单词。也就是说,只要知道每帧语音对应哪个状态了,语音识别的结果也就出来了。

      那每帧音素对应哪个状态呢?有个容易想到的办法,看某帧对应哪个状态的概率最大,那这帧就属于哪个状态。比如下面的示意图,这帧对应 S3 状态的概率最大,因此就让这帧属于 S3 状态。

      

      那这些用到的概率从哪里读取呢?有个叫「声学模型」的东西,里面存了一大堆参数,通过这些参数,就可以知道帧和状态对应的概率。获取这一大堆参数的方法叫做「训练」,需要使用巨大数量的语音数据,训练的方法比较繁琐,这里不讲。

      但这样做有一个问题:每一帧都会得到一个状态号,最后整个语音就会得到一堆乱七八糟的状态号,相邻两帧间的状态号基本都不相同。假设语音有 1000 帧,每帧对应 1 个状态,每 3 个状态组合成一个音素,那么大概会组合成300个音素,但这段语音其实根本没有这么多音素。如果真这么做,得到的状态号可能根本无法组合成音素。实际上,相邻帧的状态应该大多数都是相同的才合理,因为每帧很短。

      解决这个问题的常用方法就是使用隐马尔可夫模型(Hidden Markov Model,HMM)。这东西听起来好像很高深的样子,实际上用起来很简单:

      第一步,构建一个状态网络。

      第二步,从状态网络中寻找与声音最匹配的路径。

      这样就把结果限制在预先设定的网络中,避免了刚才说到的问题,当然也带来一个局限,比如你设定的网络里只包含了「今天晴天」和「今天下雨」两个句子的状态路径,那么不管说些什么,识别出的结果必然是这两个句子中的一句。

      那如果想识别任意文本呢?把这个网络搭得足够大,包含任意文本的路径就可以了。但这个网络越大,想要达到比较好的识别准确率就越难。所以要根据实际任务的需求,合理选择网络大小和结构。

      搭建状态网络,是由单词级网络展开成音素网络,再展开成状态网络。语音识别过程其实就是在状态网络中搜索一条最佳路径,语音对应这条路径的概率最大,这称之为「解码」。路径搜索的算法是一种动态规划剪枝的算法,称之为 Viterbi 算法,用于寻找全局最优路径。

      

      这里所说的累积概率,由三部分构成,分别是:

      观察概率:每帧和每个状态对应的概率

      转移概率:每个状态转移到自身或转移到下个状态的概率

      语言概率:根据语言统计规律得到的概率

      其中,前两种概率从声学模型中获取,最后一种概率从语言模型中获取。语言模型是使用大量的文本训练出来的,可以利用某门语言本身的统计规律来帮助提升识别正确率。语言模型很重要,如果不使用语言模型,当状态网络较大时,识别出的结果基本是一团乱麻。


    3.1  声学模型

           声学模型是把语音转化为声学表示的输出,即找到给定的语音源于某个声学符号的概率。对于声学符号,最直接的表达方式是词组,但是在训练数据量不充分的情况下,很难得到一个好的模型。词组是由多个音素的连续发音构成,另外,音素不但有清晰的定义而且数量有限。因而,在语音识别中,通常把声学模型转换成了一个语音序列到发音序列(音素)的模型和一个发音序列到输出文字序列的字典。

           需要注意的是,由于人类发声器官运动的连续性,以及某些语言中特定的拼读习惯,会导致音素的发音受到前后音素的影响。为了对不同语境的音素加以区分,通常使用能够考虑前后各一个音素的三音子作为建模单元。

           另外,在声学模型中,可以把三音子分解为更小的颗粒—状态,通常一个三音子对应3个状态,但是这会引起建模参数的指数增长,常用的解决方案是使用决策树先对这些三音子模型进行聚类,然后使用聚类的结果作为分类目标。

           至此,语音识别有了最终的分类目标—状态。最常用的声学建模方式是隐马尔科夫模型(HMM)。在HMM下,状态是隐变量,语音是观测值,状态之间的跳转符合马尔科夫假设。其中,状态转移概率密度多采用几何分布建模,而拟合隐变量到观测值的观测概率的模型常用高斯混合模型(GMM)。基于深度学习的发展,深度神经网络(DNN)、卷积神经网络(CNN)、循环神经网络(RNN)等模型被应用到观测概率的建模中,并取得了非常好的效果。下文给出各个模型的原理、所解决的问题及各自局限性,且给出了由模型的局限性而引起建模方式发展的脉络。


    1)高斯混合模型(GMM)

           观测概率密度函数由高斯混合模型建模,训练中,不断迭代优化,以求取GMM中的加权系数及各个高斯函数的均值与方差。GMM模型训练速度较快,且GMM声学模型参数量小,可以容易地嵌入到终端设备中。在很长一段时间内,GMM-HMM混合模型都是表现最优秀的语音识别模型。但是GMM不能利用语境信息,其建模能力有限。


    2)深度神经网络(DNN)

           最早用于声学模型建模的神经网络,DNN解决了基于高斯混合模型进行数据表示的低效问题。语音识别中,DNN-HMM混合模型大幅度的提升了识别率。目前阶段,DNN-HMM基于其相对有限的训练成本及高识别率,仍然是特定的语音识别工业领域常用的声学模型。需要注意的是,基于建模方式的约束(模型输入特征长度的一致性需求),DNN模型使用的是固定长度的滑动窗来提取特征。


    3)循环神经网络(RNN)/卷积神经网络(CNN)模型

           对于不同的音素与语速,利用语境信息最优的特征窗长度是不同的。能够有效利用可变长度语境信息的RNN与CNN在语音识别中能够取得更好的识别性能。因而,在语速鲁棒性方面,CNN/RNN比DNN表现的更好。

           在使用RNN建模方面,用于语音识别建模的模型有:多隐层的长短期记忆网络(LSTM)、highway LSTM、ResidualLSTM、双向LSTM、时延控制的双向LSTM。

           LSTM,基于门控电路设计,其能够利用长短时信息,在语音识别中取得了非常好的性能。另外,可以通过增加层数进一步提升识别性能,但是简单地增加LSTM的层数会引起训练困难及梯度消失问题。

           Highway LSTM,在LSTM相邻层的记忆单元间添加一个门控的直接链路,为信息在不同层间流动提供一个直接且不衰减的路径,从而解决梯度消失问题

           Residual LSTM,在LSTM层间提供一个捷径,亦能解决梯度消失问题。

           双向LSTM,能够利用过去及未来的语境信息,因而其识别性能比单向的LSTM好,但是由于双向LSTM利用了未来的信息,因而基于双向LSTM建模的语音识别系统需要观察完整的一段话之后才能识别,从而不适用于实时语音识别系统。

           时延控制的双向LSTM,通过调整双向LSTM的反向LSTM,实现了性能与实时性的一个折中建模方案,能够应用于实时的语音识别系统。


           CNN建模方面,包括时延神经网络(TDNN)、CNN-DNN、CNN-LSTM-DNN(CLDNN)、CNN-DNN-LSTM(CDL)、深度CNN、逐层语境扩展和注意(LACE)CNN、dilated CNN。


           TDNN,最早被用于语音识别的CNN建模方式,TDNN会沿频率轴和时间轴同时进行卷积,因此能够利用可变长度的语境信息。TDNN用于语音识别分为两种情况,第一种情况下:只有TDNN,很难用于大词汇量连续性语音识别(LVCSR),原因在于可变长度的表述(utterance)与可变长度的语境信息是两回事,在LVCSR中需要处理可变长度表述问题,而TDNN只能处理可变长度语境信息;第二种情况:TDNN-HMM混合模型,由于HMM能够处理可变长度表述问题,因而该模型能够有效地处理LVCSR问题。


           CNN-DNN,在DNN前增加一到两层的卷积层,以提升对不同说话人的可变长度声道(vocal tract)问题的鲁棒性,对比于单纯DNN,CNN-DNN性能有一定幅度(5%)的提升


           CLDNN及CDL,在这两个模型中,CNN只处理频率轴的变化,LSTM用于利用可变长度语境信息。


           深度CNN,这里的“深度”是指一百层以上。语谱图可以被看作是带有特定模式的图像,通过使用比较小的卷积核以及更多的层,来利用时间及频率轴上长范围的相关信息,深度CNN的建模性能与双向LSTM性能相当,但是深度CNN没有时延问题。在控制计算成本的情况下,深度CNN能够很好的应用于实时系统。


           逐层语境扩展和注意(LACE)CNN及dilatedCNN,深度CNN的计算量比较大,因而提出了能够减小计算量的 LACE CNN与dilatedCNN,其把整个话语看作单张输入图,因而可以复用中间结果,另外,可以通过设计LACE CNN及dilatedCNN网络每一层的步长,使其能够覆盖整个核,来降低计算成本。


           语音识别的应用环境常常比较复杂,选择能够应对各种情况的模型建模声学模型是工业界及学术界常用的建模方式。但是各个单一模型都有局限性。HMM能够处理可变长度的表述,CNN能够处理可变声道,RNN/CNN能够处理可变语境信息。声学模型建模中,混合模型由于能够结合各个模型的优势,是目前声学建模的主流方式。


    3.2  语言模型


           语音识别中,最常见的语言模型是N-Gram。近年,深度神经网络的建模方式也被应用到语言模型中,比如基于CNN及RNN的语言模型。


    4、端到端的语音识别系统


           在DNN-HMM或者CNN/RNN-HMM模型中,DNN/CNN/RNN与HMM是分开优化的,但是语音识别本质上是一个序列识别问题,如果模型中的所有组件都能够联合优化,很可能会获取更好的识别准确度,这一点从语音识别的数学表达式也可以看出(利用贝叶斯准则变化之后的表达式),因而端到端的处理方式亦被引入到语音识别系统中。


    4.1  CTC准则


           其核心思想是引入空白标签,然后基于前向后向算法做序列到序列的映射。CTC准则可分为character-basedCTC、other output units-based CTC、word-basedCTC,由于CTC准则是直接预测字符、单词等,而不是预测音素,因而其能够剔除语音识别中的字典等专家知识。由于在非word-basedCTC中,仍然需要语言模型及解码器。因而,character-basedCTC与other output units-basedCTC是非纯粹的端到端的语音识别系统。相反,word-based CTC模型是纯粹的端到端语音识别系统。


           基于word-based CTC准则,使用10万个词作为输出目标且使用 12.5 万小时训练样本得到的语音序列到单词序列的模型,能够超越基于音素单元的模型。但是word-based CTC模型有训练困难及收敛慢的问题。


    4.2  Attention-based模型


           相比于CTC准则,Attention-based模型不需要有帧间独立性假设,这也是Attention-based模型的一大优势,因而Attention-based模型可能能够取得更好的识别性能。但是相比于CTC准则,Attention-based模型训练更加困难,且有不能单调地从左到右对齐及收敛更慢的缺点。通过将CTC 目标函数用作辅助代价函数,Attention训练和 CTC训练以一种多任务学习的方式结合到了一起。这种训练策略能够很大程度上改善Attention-based模型的收敛问题,并且缓解了对齐问题。


           语音识别的发展过程中,深度学习起到了关键的作用。声学模型遵循从DNN 到LSTM再到端到端建模的发展路径。深度学习最大的优势之一是特征表征。在有噪声、回响等情况下,深度学习可以把噪声、回响看为新的特征,并通过对有噪声、回响数据的学习,达到比较理想的识别性能。目前阶段,端到端的建模方式是声学模型建模的重点研究方向,但是相比于其它的建模方式,其还没有取得明显的性能优势。如何在端到端建模的基础上,提升训练速度及性能,并解决收敛问题是声学模型的重要研究方向。


    5、解码


           基于训练好的声学模型,并结合词典、语言模型,对输入的语音帧序列识别的过程即为解码的过程。传统的解码是将声学模型、词典以及语言模型编译成一个网络。解码就是在这个动态网络空间中,基于最大后验概率,选择一条或多条最优路径作为识别结果(最优的输出字符序列)。搜索常用的方法是Viterbi算法。对于端到端的语音识别系统,最简单的解码方法是beamsearch算法。


    6、远场复杂环境下解决方案


           目前阶段,在近场安静环境下,语音识别能够取得非常理想的识别效果,但是在高噪声、多人说话、强口音等环境,特别是远场环境下,语音识别还有诸多问题需要解决。语音模型自适应、语音增强与分离、识别模型优化等是常用的可选解决方案。


    6.1  语音增强与分离


           远场环境下,语音输入信号衰减比较严重,为了对语音信号增强,常采用麦克风阵列的波束形成技术,比如,GoogleHome采用双麦的设计方案,亚马逊Echo采用6+1的麦克风阵列设计方案。近年,深度学习方法被应用到语音增强与分离中,核心思想是把语音增强与分离转化为一个监督学习问题,即预测输入声音源的问题。有研究使用DNN替代波束形成,实现语音增强,并在一定场景下取得了比较理想的效果。但是在背景噪声很大的环境中,该方法性能还有较大提升空间。


           在多人说话的情况下,如果不对输入信号做分离处理,而进行语音识别的话,识别效果会很差。对于该问题,在多个说话人距离较远的情况下,波束形成是一个比较好的解决方案,但是当多个说话人距离很近的时候,波束形成的语音分离效果也很差。为了避开波束形成所带来的场景分类问题,传统的方法多是在单通道下尝试解决该问题,常用算法有computationalauditory scene analysis、非负矩阵分解、deep clustering等,但是这些方法只有当噪声信号(除声源外的其他信号)与声音源信号有明显不同的特征时,这些技术才取得比较好的效果。其它情况下,这些方法在语音分离中取得的效果一般。2016年,俞栋博士提出了一种新的深度学习训练准则--permutation invariant training,巧妙地解决了该问题,并取得了不错的效果。


    6.2  语音模型自适应


          大量且丰富(能够提供更多信息)的数据集是提升模型泛化能力的最直接简单的方法;

    基于成本及训练时间的考虑,一般情况下只使用有限的训练数据。此时,在模型训练中加入Kullback-Leiblerdivergence正则项是解决模型自适应问题非常有效的方式;

    除了加入正则项外,使用非常少的参数来表征说话者特征是另一种自适应方式,其包括:奇异值分解瓶颈自适应,把满秩矩阵分解为两个低秩矩阵,减小训练参数;子空间法,子空间法又包括:


          1. 在输入空间及深度网络的各个层中加入i-vector、扬声器(speaker)编码、噪声估计等辅助特征;


          2. 聚类自适应训练(CAT);


          3. 隐层分解(FHL),相比于CAT,FHL只需要少量的训练数据,原因在于FHL的基是秩为1的矩阵,而CAT的基是满秩矩阵,在基数量一样的情况下,CAT需要更多的训练数据。


          实时性是语音识别应用中关注度很高的问题之一,实时性直接影响用户的体验感,提高语音识别的实时性可以通过降低运算时间成本与提升识别硬件计算能力两方面完成。


    7、降低运算时间成本

          SVD,基于奇异值分解的数学原理,把满秩矩阵分解为两个低秩矩阵,减小深度模型的参数,且能够不降低模型识别性能;

    压缩模型,使用向量量化或者极低比特量化算法;

          改变模型结构,主要针对LSTM,在LSTM中增加一个线性映射层,降低原有LSTM的输出维度,从而降低运算时间成本;

          使用跨帧的相关性来降低评估深度网络分数的频率,对于DNN或CNN而言,这可以通过使用跳帧策略完成,即每隔几帧才计算一次声学分数,并在解码时将该分数复制到没有评估声学分数的帧 。

          另外,提升识别阶段硬件的运算能力,开发专用的语音识别芯片对增强语音识别的实时性意义重大,下文将会在这方面展开讨论。


    三、算法

           至此语音识别的大体过程已经OK了,下面来看具体传统模型GMM-HMM的算法。

           语音识别过程就是输入一段语音信号,找到一串文字(字或词)序列的过程,

    语音输入

    O =o1,o2,o3,...,ot

    对应的标注

    W =w1,w2,w3,...,w


    这个过程一般用概率来表示,用O表示语音信号,用W表示文字序列,则是要解决下面这个问题:


    由贝叶斯公式


    展开,可得



    由于P(O|W )P(W ) /P(O)是对每个句子进行计算的,而对每个句子来说P(O) 是不变的,所以可以改写成如下

    即:

    其中P(O|)称做观测最大释然,由声学模型计算可得
    其中P(w)称做先验概率,由语言模型模型计算可得
    综上所述,语音识别就是解码(decoding)过程,如下图所示:



    声学模型的任务是计算P(O|), 即给定文字之后发出这段语音的概率(最后利用贝叶斯,求P(O|)是使用)。 首先第一问题: 怎么才能知道每个单词发什么音呢? 这就需要另外一个模块,叫做词典,看eesen的源码在数据准备阶段就是先求出词对应音素的dict, 它的作用就是把单词串转化成音素串,然后再求的语言模型和 训练声学模型(用lstm+ctc 训练声学模型).

    有了dict的帮助,声学模型就知道给定的文字串该依次发哪些音了。不过为了计算语音跟音素串的匹配程度,还需要知道每个音素的起止时间。 这是利用动归来进行的,可以高效的找到音素的分界点,使得每一段语音与音素的匹配程度(用概率表示)之积最大。实际使用的算法称为viterbi算法,它不仅仅考虑了每一段语音和音素的匹配程度,还考虑了各个音素之间转换的概率(转换概率通过HMM估计) 实际中使用的比音素更小的单位,原理一样(不是很确定,值得是一帧数据(25ms)吗,一帧不到不到一个音素的长度?)

    在求音素分界点的过程中,以及在有了分界点后计算P(O|)时,声学模型都需要知道怎样计算一个音素与一段语音信号的匹配程度。要做这件事,需要找到一种合适的表示语音信号的方法。一般是把语音信号分成许多帧,对于每一帧,通过傅里叶变换等一系列操作,把它转换成一个特征向量。最常用的特征是MFCC,从训练数据中,我们可以提取出大量的特征向量,以及它们对应的音素;利用这些数据,就可以训练从特征到音素的分类器。前些年最常用的分类器是高斯混合模型(GMM),它的大致原理是估计出每个音素的特征向量的分布,然后在识别阶段,计算每一帧的特征向量x_t由相应音素s_i产生的概率P(x_t|s_i),把每一帧的概率相乘,就得到P(O|)。现在,神经网络渐渐火了起来,它可以直接给出P(s_i|x_i),用贝叶斯公式可以转换成P(x_t|s_i),再相乘得到P(O|)

    P(O|)称做观测最大释然,由声学模型计算可得,本章就主要描述HMM+GMM来计算最大释然的过程。

    首先回顾一下:在解码过程中


    P(O|)由声学模型训练得到,

    P(O|)是W的似然函数,结合之前讲述的声学特征也就是说,在给定的W情况,使得当前的特征向量(MFCC)的概率最大,结合HMM的概念,也就是说在在t时刻给定状态qi 的前提下,求输出O的概率,即p(ot|qi) ,即矩阵B,状态对应的是word,phone或者subphone,在LVCSR中对应的是subphone

    在解码阶段,在固定观测向量ot 的前提下,我们需要计算每一个HMM状态可能产生的概率,然后找到最大可能的状态(subphone)序列,所以训练过程就是计算观测似然矩阵B的过程。

    理想的方式计算MFCC的时候,可以把输入的帧映射为一些离散的量化符号方便计算,如下图所示


    然后这么计算似然是有问题的,因为输入音频是连续的,特征基本变化很大的,很难进行比较好的聚类,因此提出连续空间的概率密度函数(PDF),最常用的计算声学似然的方式是高斯混合模型,即GMM模型(当然SVM,CRF,NN也可以)。

    高斯分布也是一种正态分布,函数如下所示


    不同的均值,方差下,对应的高斯分布如下所示:


    离散情况下,均值,方差计算如下所示:

     


    当高斯函数用来当做概率密度函数时,曲线下的面积和应该为1,如下所示,灰色区域面积为0.341



    我们可以用单GMM pdf来估测一个特定的HMM状态j,产生一个单一维度的声学特征向量O的概率,(假设

    ot 服从正态分布),换句话说,就是用对一维特征来说,一元高斯来代表观测似然函数bj(ot)  ,

    假设HMM状态j 对应的均值方式是μj 和σ2j  ,那么计算似然  bj(ot) 可以通过Gaussian PDF来计算,如下所示:


    有了以上公式,我们就可以来进行HMM decoding了

    然而如何计算均值和方差呢,按理来说可以通过如下公式进行计算


    然而,状态是HMM隐藏状态,我们不知道Ot是由哪个状态产生的,

    但是我们可以通过HMM中t时刻在状态i的概率来按比例分配,即把每个Ot分配给每个可能的状态i。

    ξ(i) 记做在t时刻状态i产生Ot的概率,

    那么通过Baum-Welch 迭代算法进行计算,如下所示:


    也叫作HMM前向-后向(Baum-Welch)训练。

    以上讨论的是一维特征,在实际中,MFCC是39维特征,因此我们使用了多元高斯模型,

    多元高斯分布函数如下所示:


    协方差计算公式如下:


    则,高斯概率似然函数(o如下所示:


    因为对角协方差计算量更小,所以可以简化为对角协方差

    可以表述为:在t时刻,状态j产生声学特征向量Ot的似然函数可以表述为对角协方差多元高斯,

    上述公式可以简化成如下公式



    ξ(i) 记做t时刻,状态i产生Ot的似然对应均值方差为:


    以上的前提声学特征符合正态分布,但是实际倒谱特征MFCC不是正态分布,因此我们可以改进为

    加权的混合多远高斯模型,即GMM,对应函数公式如下所示:


    对应的输出似然函数bj(ot如下所示:


    把ξtm(j)记做t时刻,状态j,在m元 混合模型情况下产生声学特征Ot的概率,公式如下


    对应均值方差,同样可以由Baum-Welch 迭代算出来,公式如下:


    在实际计算中,一个句子的似然是一串概率相乘,导致概率数值非常低,如.00000001=10容易下溢出,而且不方便计算,,所以常常用log来计算,则上述输出似然函数(o)可以改写如下所示


    重写可得:


    其中C为:


    C为常数,可以再decoding之前提前计算出来,节省计算时间

    以上就是GMM在训练声学模型中的应用。


    本章主要讲解HMM训练过程,首先回顾上章的HMM模型如下:

    Q =q1q2...qN   状态集合(subphone集合)

    A =a01a02...an1...ann   状态(subphone)转移矩阵,Q和A构成了发音字典

    B=bi(ot)  观测似然,也叫作发射概率 ,表述为:每个subphone状态i产生倒谱特征Ot的概率

    最简单的方式是给定手工标注的孤立词和对应的音频文件,计算每个子音素(subphone)对应的标注来计算矩阵B,然而实际中却无法做到,因为每个subphone对应的Ot是很难用手工去标注的。(用手工去在一断连续的音频上标注一个子因素是不可行的)因此,训练每个phone的HMM是嵌入在整个句子中进行的,音素的分割和对其是在训练过程中自动进行的,所以整个这个训练过程叫做嵌入式训练embedded training 

    数据准备:在训练过程前,需要准备wav音频文件,对应的标注文本,还有发音字典,基于句子的HMM构建如下:


    接下来就是训练状态转移矩阵A和似然估计矩阵B了,

    ξj(t) 表示:在t时刻,状态i生成观测序列O的概率。

    在初始阶段,我们需要对ai jbj(ot一个初始的估计 ,最简单的做法叫做flat start

    在flat start中,状态转移矩阵中,状态的自环和跳转到下一个状态(subphone)的概率相同,为0.5,高斯的均值方差取全局训练数据的均值和方差。现在有了基础的HMM-GMM参数了,接下来就要在整个训练集合上跑Balum-Welch算法,每次迭代,都要修改HMM参数,直到系统趋于一致不变。首先在给定初始的矩阵A和B的情况下,计算前向-后向概率,然后,用前向-后向概率重新估算新的矩阵A和矩阵B,具体推导会在下一篇文章《HMM基础-HMM训练-前向后向算法》章节详细讨论。同时用EM算法来更新多元高斯模型的均值和方差。

    综上所述,标准的嵌入式训练过程如下所述:

    给定训练音频文件,标注文件,发音字典情况下

    1)如上图所述,对每个句子,构建一个句子的HMM模型。

    2)初始化状态转移中的非零元素(自环为0.75,和跳转到下一个状态的为0.25)

    3)初始化发射概率矩阵每个高斯的均值方差为所以训练集合的全局均值和方差。

    4)EM迭代多次,用Viterbi来计算ξj(t) (在t时刻,状态i生成观测序列O的概率),

    为计算ξj(t) ,要累计所有可能的路径,这样计算太慢了,一个更高效的算法是Viterbi 算法,

    训练算法中,不再是用前向-后向算法累计所有的路径来计算ξj(t),而是通过重复的跑Viterbi路径(最大概率路径)

    来接近估测这个值。

    用Viterbi来训练数据过程,也叫作强制Viterbi对齐,或强制对齐过程

    Viterbi对齐过程中,因为已经知道观察序列对应的词序列,所以合适的设置aij ,就可以强制Viterbi算法来通过某个指定的词。

    Viterbi对齐其实是Viterbi解码的一个简化版,因为,Viterbi强制对齐过程中只需要找到观测值Ot对应正确的状态(或subphone)序列,而不需要找到正确的词序列。训练结果就是Viterbi强制对齐:即,对应观测序列O的,一条最优状态序列。

    接下来,我们可以用对齐的HMM状态到累计counts,从而重新估计HMM参数。

    Viterbi对齐中重新训练高斯模型参数的公式如下所示:



    高斯混合模型的计算参加上一篇文章。

    以上就是嵌入式训练过程。

    上一篇讨论了语音识别中的训练过程,本章讨论语音识别中,解码的过程。

    解码的过程就是在给定声学特征的情况下,找到最可能对应的词组的过程,再次看如下求解目的的公式:


    其中似然概率是在一系列给定声学frame情况下,计算每个对应的分类器得分,然后相乘得出的概率,使得其值变得很小,而P(W)比较大,这样就导致

    P(w)权重太大了,所以需要对齐进行缩放,以平衡贡献值,所以把上面公式改写如下:


    因为P(w)小于1,使LMSF大于1,(5-15),这样就减小了P(w)对整个公式的贡献,以达到缩放的目的。

    但是在P(w)中以上惩罚对词插入的情况下是有副作用的,所以改写如下:


    在log形式展开,最后解码的目标就是如下公式所示:


    有了上述的目标公式,接下来就要讨论,如何解码取其最大值

    解码中最常用的是Viterbi算法,首先看一下语音识别中HMM模型:

    Q =q1q2...qN   对应subphone的状态序列

    A =a01a02...an1...ann   状态转移矩阵(自环和转到下一个状态)

    B =bi(ot)   观测似然,或者叫做发射概率,代表在t时刻,状态i产生声音倒谱特征O的概率

    其中A和B由上一章中的嵌入式训练得到。下图为识别数字的HMM结构图。


    首先我看用前向算法O(N2T来进行解码的过程,

    举例如下:英文字母five,有对应状态[f], [ay], 和[v] ,观测序列O,如下所示:

         


    首先我们引入αt(j)  ,记做:在看见前t个观测值之后,处于状态j的概率。


    这里qt=j 表示在状态序列中,t时刻状态为j,

    αj) 可以理解为,所有能到到达当前状态的所有路径的概率之和,即:


    其中,αt1(i) 表示前一个(t-1)之前的路径的概率,

    aij 表示概率转移矩阵,表示从状态qi  到当前状态q j 的概率

    bj(ot) 叫做状态观测似然,也叫作发射概率,表示给定状态j,观测到ot  的概率

    前向算法如下所示:


    qF 表示结束状态。

    假设矩阵A自环概率概率为0.5,假设矩阵B如下所示:

    则单词“five”的前向算法过程如下所示:


    接着讨论Viterbi算法,

    Viterbi算法是返回最可能的状态序列,虽然不是最可能的次序列,但是也足够接近了,其时间复杂度为

    O(N2T) ,

    使得vt(j)记做:在给定 λ 的情况下,在看到前t个观测特征Ot,且通过了最可能的q1...qt1 状态序列的情况下,HMM当前状态为j的概率,即:


    根据动态规划算法,可以理解为t-1时刻的在状态i时候的最大概率路径到当前时刻t时候,状态为j的概率,记做:


    vt1(i) 表示:前一时刻,Viterbit路径的概率;

    ai j 表示:状态qi 到状态qj的概率 ;

    bj(ot) 表示:状态观测似然,表示为给定状态j,产生观测向量ot  的概率。

    根据上式可知,Viterbi算法是在给定观测序列o= (o1o2o3...oT) 情况下,找到最优的状态序列q=(q1q2q3...qT)的过程

    ,同时找到对应的最大的概率。对比上面的前向算法可知,他们目标都是一致的,但是Viterbi算法是求其最大值。

    Viterbi具体解码算法如下所示:(假设起始状态为0,结束状态为qF,是非发射状态)


    例子:

    假设矩阵A自环概率概率为0.5,假设矩阵B如下所示:

    则对应的计算数值如下所示:


    Viterbi解码的真正用处不仅仅是在词内解码,更重要的是可以解码一串词,为了使Viterbi能够进行词间进行解码,我们得增加矩阵A,使其不仅要有词内的状态转移概率,还需要增加从一个词末尾,到另一个词开始的状态转移概率。

    下图补充了2-gram间的转移概率,


    下图展示了2-gram词间解码的过程:


    一旦一句话的Viterbi解码计算完毕,就可以用过后项指针回溯,来找到最大概率的状态序列,即最大概率的词序列。

    如下图所示:最后的回溯词序列为w1wN···w


    实际上Viterbi解码过程中需要进行beam剪枝,通过设定beam的宽度θ 来进行beam剪枝。

    Beam剪枝算法我们以后章节继续讨论。

    以上就是语音识别过程中,解码的所有细节,谢谢!


    由于本人实在看不懂这些算法,这也是个人理出来的算法流程逻辑,如有错误请见谅!

    详情请参考博客 http://blog.csdn.net/quhediegooo?viewmode=contents


    四、PocketSphinx的安装、编译、训练以及一些坑

           先说一些遇到的问题:1、我说过在Sphinx0.8以后,Sphinx就没有支持中文model,(后面是猜测)所以Sphinx并没有更新和优化中文相关的逻辑,也就是中文模型的训练没搞好,在实际应用的时候在他的网站训练后的Grammer存在一定误识别率,我想原因就是因为它的训练集比不是很多。2、在0.8之后,现在最新的版本中没有中文模型,所以面临的问题也是训练数据集不足,也会出现误识别率很高,所以根据官网给出的建议,200人5小时的训练时长(非特定人)和覆盖较大的字典(需大词汇量,否则小词汇量会导致分类较少)是比较好的选择。

           具体安装、编译、训练方法,请根据http://www.cnblogs.com/bhlsheji/p/4514475.html描述进行,已验证过可行。这里不做过多描述了。注意的细节我之后会总结!

           对于PocketSphinx的代码框架结构网上没有找到解析,但是可以根据http://ishare.iask.sina.com.cn/f/66428902.html这篇文章(讲解Sphinx4的代码结构)其中氛围预处理模块、搜索管理模块、语言模块。大部分逻辑在SphinxBase项目中、PocketSphinx做一些封装调用(猜的)。


    五、总结

           说一些我个人看法。

    1)以我们当前只做非特定人、小词汇量、半连续语音识别,注意的同样在于训练数据集较少,导致误识别率很高。

    2)语音识别框架最好用C/C++,好处:可移植性强、高效、安全。









    展开全文
  • 本文搭建一个完整的中文语音识别系统,包括声学模型和语言模型,能够将输入的音频信号识别为汉字。 声学模型使用了应用较为广泛的递归循环网络中的GRU-CTC的组合,除此之外还引入了科大讯飞提出的DFCNN深度全序列...

    本文搭建一个完整的中文语音识别系统,包括声学模型和语言模型,能够将输入的音频信号识别为汉字。

    该系统实现了基于深度框架的语音识别中的声学模型和语言模型建模,其中声学模型包括CNN-CTC、GRU-CTC、CNN-RNN-CTC,语言模型包含transformerCBHG
    数据集采用了目前能找到的所有中文免费数据,包括:thchs-30、aishell、primewords、st-cmd四个数据集,训练集总计大约450个小时。

    项目地址:https://github.com/audier/DeepSpeechRecognition

    为了方便同学们自己做实验,写了实践版的tutorial:

    1. 声学模型

    声学模型目前开源了部分示例模型,后期将不定期更新一些模型。

    GRU-CTC

    我们使用 GRU-CTC的结构搭建了第一个声学模型,该模型在项目的gru_ctc.py文件中。
    利用循环神经网络可以利用语音上下文相关的信息,得到更加准确地信息,而GUR又能选择性的保留需要的长时信息,使用双向rnn又能够充分的利用上下文信号。
    但该方法缺点是一句话说完之后才能进行识别,且训练相对cnn较慢。该模型使用python/keras进行搭建,本文系统都使用python搭建。

    • 网络结构如下:
        def _model_init(self):
            self.inputs = Input(name='the_inputs', shape=(None, 200, 1))
            x = Reshape((-1, 200))(self.inputs)
            x = dense(512, x)
            x = dense(512, x)
            x = bi_gru(512, x)
            x = bi_gru(512, x)
            x = bi_gru(512, x)
            x = dense(512, x)
            self.outputs = dense(self.vocab_size, x, activation='softmax')
            self.model = Model(inputs=self.inputs, outputs=self.outputs)
            self.model.summary()
    

    DFCNN

    由于两个原因在使用GRU作为语音识别的时候我们会遇到问题。

    • 一方面是我们常常使用双向循环神经网络才能取得更好的识别效果,这样会影响解码实时性。
    • 另一方面随着网络结构复杂性增加,双向GRU的参数是相同节点数全连接层的6倍,这样会导致训练速度非常缓慢。

    科大讯飞提出了一种使用深度卷积神经网络来对时频图进行识别的方法,就是DFCNN
    论文地址:http://xueshu.baidu.com/usercenter/paper/show?paperid=be5348048dd263aced0f2bdc75a535e8&site=xueshu_se

    利用CNN参数共享机制,可以将参数数量下降几个数量级别,且深层次的卷积和池化层能够充分考虑语音信号的上下文信息,且可以在较短的时间内就可以得到识别结果,具有较好的实时性。
    该模型在cnn_ctc.py中,实验中是所有网络结果最好的模型,目前能够取得较好的泛化能力。其网络结构如下:
    DFCNN

    def cnn_cell(size, x, pool=True):
        x = norm(conv2d(size)(x))
        x = norm(conv2d(size)(x))
        if pool:
            x = maxpool(x)
        return x
    
    class Am():
        def _model_init(self):
            self.inputs = Input(name='the_inputs', shape=(None, 200, 1))
            self.h1 = cnn_cell(32, self.inputs)
            self.h2 = cnn_cell(64, self.h1)
            self.h3 = cnn_cell(128, self.h2)
            self.h4 = cnn_cell(128, self.h3, pool=False)
            self.h5 = cnn_cell(128, self.h4, pool=False)
            # 200 / 8 * 128 = 3200
            self.h6 = Reshape((-1, 3200))(self.h5)
            self.h7 = dense(256)(self.h6)
            self.outputs = dense(self.vocab_size, activation='softmax')(self.h7)
            self.model = Model(inputs=self.inputs, outputs=self.outputs)
            self.model.summary()
    

    DFSMN

    而前馈记忆神经网络也也解决了双向GRU的参数过多和实时性较差的缺点,它利用一个记忆模块,包含了上下几帧信息,能够得到不输于双向GRU-CTC的识别结果,阿里最新的开源系统就是基于DFSMN的声学模型,只不过在kaldi的框架上实现的。我们将考虑使用DFSMN+CTC的结构在python上实现。该网络实质上是用一个特殊的CNN就可以取得相同的效果,我们将CNN的宽设置为memory size,将高度设置为feature dim,将channel设置为hidden units,这样一个cnn的层就可以模仿fsmn的实现了。
    结构如下:
    DFSMN

    2. 语言模型

    n-gram

    n元语法是一个非常经典的语言模型,这里不过多介绍啦。
    如果想要通过语料来训练一个n-gram的模型,可以参考我的小实验的实现方法。

    CBHG

    该想法来自于一个大神搞输入法的项目,下面部分也引用此处:搜喵出入法
    他是利用该模型建立一个按键到汉字的作用,本文对其结构和数据处理部分稍作改动,作为语言模型。

    拼音输入的本质上就是一个序列到序列的模型:输入拼音序列,输出汉字序列。所以天然适合用在诸如机器翻译的seq2seq模型上。

    模型初始输入是一个随机采样的拼音字母的character embedding,经过一个CBHG的模型,输出是五千个汉字对应的label。
    这里使用的CBHG模块是state-of-art的seq2seq模型,用在Google的机器翻译和语音合成中,该模型放在cbhg.py中,结构如下:
    图片来自 Tacotron: Towards End-to-End Speech Synthesis
    CBHG

    该模型训练实验结果如下,实际上,后续和transformer的比较中,该模型无论是收敛速度还是识别效果上都是很难和transformer比较。

    请输入测试拼音:ta1 mei2 you3 duo1 shao3 hao2 yan2 zhuang4 yu3 dan4 ta1 que4 ba3 ai4 qin1 ren2 ai4 jia1 ting2 ai4 zu3 guo2 ai4 jun1 dui4 wan2 mei3 de tong3 yi1 le qi3 lai2
    她没有多少豪言壮语但她却把爱亲人爱家庭爱祖国爱军队完美地统一了起来
    
    请输入测试拼音:chu2 cai2 zheng4 bo1 gei3 liang3 qian1 san1 bai3 wan4 yuan2 jiao4 yu4 zi1 jin1 wai4 hai2 bo1 chu1 zhuan1 kuan3 si4 qian1 wu3 bai3 qi1 shi2 wan4 yuan2 xin1 jian4 zhong1 xiao3 xue2
    除财政拨给两千三百万元教太资金外还拨出专款四千五百七十万元新建中小学
    
    请输入测试拼音:ke3 shi4 chang2 chang2 you3 ren2 gao4 su4 yao2 xian1 sheng1 shuo1 kan4 jian4 er4 xiao3 jie3 zai4 ka1 fei1 guan3 li3 he2 wang2 jun4 ye4 wo4 zhe shou3 yi1 zuo4 zuo4 shang4 ji3 ge4 zhong1 tou2
    可是常常有人告诉姚先生说看见二小姐在咖啡馆里和王俊业握着族一坐坐上几个钟头
    

    transformer

    新增基于transformer结构的语言模型transformer.py,该模型已经被证明有强于其他框架的语言表达能力。

    建议使用transformer作为语言模型,该模型是自然语言处理这两年最火的模型,今年的bert就是使用的该结构。本文最近更新系统使用的语言模型就是transformer。

     the  0 th example.
    文本结果: lv4 shi4 yang2 chun1 yan1 jing3 da4 kuai4 wen2 zhang1 de di3 se4 si4 yue4 de lin2 luan2 geng4 shi4 lv4 de2 xian1 huo2 xiu4 mei4 shi1 yi4 ang4 ran2
    原文结果: lv4 shi4 yang2 chun1 yan1 jing3 da4 kuai4 wen2 zhang1 de di3 se4 si4 yue4 de lin2 luan2 geng4 shi4 lv4 de2 xian1 huo2 xiu4 mei4 shi1 yi4 ang4 ran2
    原文汉字: 绿是阳春烟景大块文章的底色四月的林峦更是绿得鲜活秀媚诗意盎然
    识别结果: 绿是阳春烟景大块文章的底色四月的林峦更是绿得鲜活秀媚诗意盎然
    
     the  1 th example.
    文本结果: ta1 jin3 ping2 yao1 bu4 de li4 liang4 zai4 yong3 dao4 shang4 xia4 fan1 teng2 yong3 dong4 she2 xing2 zhuang4 ru2 hai3 tun2 yi4 zhi2 yi3 yi1 tou2 de you1 shi4 ling3 xian1
    原文结果: ta1 jin3 ping2 yao1 bu4 de li4 liang4 zai4 yong3 dao4 shang4 xia4 fan1 teng2 yong3 dong4 she2 xing2 zhuang4 ru2 hai3 tun2 yi4 zhi2 yi3 yi1 tou2 de you1 shi4 ling3 xian1
    原文汉字: 他仅凭腰部的力量在泳道上下翻腾蛹动蛇行状如海豚一直以一头的优势领先
    识别结果: 他仅凭腰部的力量在泳道上下翻腾蛹动蛇行状如海豚一直以一头的优势领先
    
     the  2 th example.
    文本结果: pao4 yan3 da3 hao3 le zha4 yao4 zen3 me zhuang1 yue4 zheng4 cai2 yao3 le yao3 ya2 shu1 di4 tuo1 qu4 yi1 fu2 guang1 bang3 zi chong1 jin4 le shui3 cuan4 dong4
    原文结果: pao4 yan3 da3 hao3 le zha4 yao4 zen3 me zhuang1 yue4 zheng4 cai2 yao3 le yao3 ya2 shu1 di4 tuo1 qu4 yi1 fu2 guang1 bang3 zi chong1 jin4 le shui3 cuan4 dong4
    原文汉字: 炮眼打好了炸药怎么装岳正才咬了咬牙倏地脱去衣服光膀子冲进了水窜洞
    识别结果: 炮眼打好了炸药怎么装岳正才咬了咬牙倏地脱去衣服光膀子冲进了水窜洞
    

    3. 数据集

    数据集采用了目前我能找到的所有中文免费数据,包括:thchs-30、aishell、primewords、st-cmd四个数据集,训练集总计大约450个小时,在之前实验过程中,使用thchs-30+aishell+st-cmd数据集对DFCNN声学模型进行训练,以64batch_size训练。
    包括stc、primewords、Aishell、thchs30四个数据集,共计约430小时, 相关链接:http://www.openslr.org/resources.php

    Name train dev test
    aishell 120098 14326 7176
    primewords 40783 5046 5073
    thchs-30 10000 893 2495
    st-cmd 10000 600 2000

    数据标签整理在data路径下,其中primewords、st-cmd目前未区分训练集测试集。

    若需要使用所有数据集,只需解压到统一路径下,然后设置utils.py中datapath的路径即可。

    4. 配置

    本项目现已训练一个迷你的语音识别系统,将项目下载到本地上,下载thchs数据集并解压至data,运行test.py,不出意外能够进行识别,结果如下:

     the  0 th example.
    文本结果: lv4 shi4 yang2 chun1 yan1 jing3 da4 kuai4 wen2 zhang1 de di3 se4 si4 yue4 de lin2 luan2 geng4 shi4 lv4 de2 xian1 huo2 xiu4 mei4 shi1 yi4 ang4 ran2
    原文结果: lv4 shi4 yang2 chun1 yan1 jing3 da4 kuai4 wen2 zhang1 de di3 se4 si4 yue4 de lin2 luan2 geng4 shi4 lv4 de2 xian1 huo2 xiu4 mei4 shi1 yi4 ang4 ran2
    原文汉字: 绿是阳春烟景大块文章的底色四月的林峦更是绿得鲜活秀媚诗意盎然
    识别结果: 绿是阳春烟景大块文章的底色四月的林峦更是绿得鲜活秀媚诗意盎然
    

    若自己建立模型则需要删除现有模型,重新配置参数训练。

    与数据相关参数在utils.py中:

    • data_type: train, test, dev
    • data_path: 对应解压数据的路径
    • thchs30, aishell, prime, stcmd: 是否使用该数据集
    • batch_size: batch_size
    • data_length: 我自己做实验时写小一些看效果用的,正常使用设为None即可
    • shuffle:正常训练设为True,是否打乱训练顺序
    def data_hparams():
        params = tf.contrib.training.HParams(
            # vocab
            data_type = 'train',
            data_path = 'data/',
            thchs30 = True,
            aishell = True,
            prime = False,
            stcmd = False,
            batch_size = 1,
            data_length = None,
            shuffle = False)
          return params
    

    使用train.py文件进行模型的训练。

    声学模型可选cnn-ctc、gru-ctc,只需修改导入路径即可:

    from model_speech.cnn_ctc import Am, am_hparams

    from model_speech.gru_ctc import Am, am_hparams

    语言模型可选transformer和cbhg:

    from model_language.transformer import Lm, lm_hparams

    from model_language.cbhg import Lm, lm_hparams

    转载请注明出处:https://blog.csdn.net/chinatelecom08/article/details/82557715

    展开全文
  • html5录音+百度语音实现语音识别

    万次阅读 热门讨论 2017-10-07 22:09:36
    首先,语音识别技术已经不是什么新鲜的词汇了,各大公司也提供了自己的语音识别API,据说百度、讯飞等公司的识别率已经达到99%。 最近我也想给网站加上一个语音识别功能,用于搜索词汇。我首选的是讯飞,毕竟人家是...

    首先,语音识别技术已经不是什么新鲜的词汇了,各大公司也提供了自己的语音识别API,据说百度、讯飞等公司的识别率已经达到99%。

    最近我也想给网站加上一个语音识别功能,用于搜索词汇。我首选的是讯飞,毕竟人家是专业做语音的,但关于html5的SDK讯飞已经下架,无法使用人家现成的接口。

    没办法只能使用百度的语音识别,百度语音识别,需要提供音频文件,格式为pcm、wav 、avr。所以需要做一个html5的录音功能,我辗转各个网站,浏览了三天的信息,入了无数个坑,也发现了现在的html5录音基本上使用的是recorder.js,通过配合html5的audio标签来实现的。但网上挂着的那些录音代码十个有九个不能用,特别坑。现在网上的html5录音格式基本上都是mp3的,我又费了一点劲调成了wav格式的。

    现在步入正题哈

    1.首先建一个html,代码如下:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title></title>
    </head>
    <body>
        <div>
            <audio controls autoplay></audio>
            <input οnclick="startRecording()" type="button" value="录音" />
            <input οnclick="stopRecording()" type="button" value="停止" />
            <input οnclick="playRecording()" type="button" value="播放" />
            <input οnclick="uploadAudio()" type="button" value="提交" />
        </div>

        <script type="text/javascript" src="HZRecorder.js"></script>

        <script>
            var recorder;
            var audio = document.querySelector('audio');
            function startRecording() {
                HZRecorder.get(function (rec) {
                    recorder = rec;
                    recorder.start();
                });
            }
            function stopRecording() {
                recorder.stop();
            }
            function playRecording() {
                recorder.play(audio);
            }
            function uploadAudio() {
                recorder.upload("UploadVideoServlet.do", function (state, e) {
                    switch (state) {
                        case 'uploading':
                            //var percentComplete = Math.round(e.loaded * 100 / e.total) + '%';
                            break;
                        case 'ok':
                            //alert(e.target.responseText);
                            //alert("上传成功");
                            window.location.href="VideoSearchServlet.do";
                            break;
                        case 'error':
                            alert("上传失败");
                            break;
                        case 'cancel':
                            alert("上传被取消");
                            break;
                    }
                });
            }

        </script>
    </body>
    </html>

    2.再建一个HZRecorder.js

    (function (window) {
        //兼容
        window.URL = window.URL || window.webkitURL;
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;


        var HZRecorder = function (stream, config) {
            config = config || {};
            config.sampleBits = config.sampleBits || 16;      //采样数位 8, 16
            config.sampleRate = config.sampleRate || (8000);   //采样率(1/6 44100)


            var context = new AudioContext();
            var audioInput = context.createMediaStreamSource(stream);
            var recorder = context.createScriptProcessor(4096, 1, 1);


            var audioData = {
                size: 0          //录音文件长度
                , buffer: []     //录音缓存
                , inputSampleRate: context.sampleRate    //输入采样率
                , inputSampleBits: 16       //输入采样数位 8, 16
                , outputSampleRate: config.sampleRate    //输出采样率
                , oututSampleBits: config.sampleBits       //输出采样数位 8, 16
                , input: function (data) {
                    this.buffer.push(new Float32Array(data));
                    this.size += data.length;
                }
                , compress: function () { //合并压缩
                    //合并
                    var data = new Float32Array(this.size);
                    var offset = 0;
                    for (var i = 0; i < this.buffer.length; i++) {
                        data.set(this.buffer[i], offset);
                        offset += this.buffer[i].length;
                    }
                    //压缩
                    var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
                    var length = data.length / compression;
                    var result = new Float32Array(length);
                    var index = 0, j = 0;
                    while (index < length) {
                        result[index] = data[j];
                        j += compression;
                        index++;
                    }
                    return result;
                }
                , encodeWAV: function () {
                    var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
                    var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
                    var bytes = this.compress();
                    var dataLength = bytes.length * (sampleBits / 8);
                    var buffer = new ArrayBuffer(44 + dataLength);
                    var data = new DataView(buffer);


                    var channelCount = 1;//单声道
                    var offset = 0;


                    var writeString = function (str) {
                        for (var i = 0; i < str.length; i++) {
                            data.setUint8(offset + i, str.charCodeAt(i));
                        }
                    }
                    
                    // 资源交换文件标识符 
                    writeString('RIFF'); offset += 4;
                    // 下个地址开始到文件尾总字节数,即文件大小-8 
                    data.setUint32(offset, 36 + dataLength, true); offset += 4;
                    // WAV文件标志
                    writeString('WAVE'); offset += 4;
                    // 波形格式标志 
                    writeString('fmt '); offset += 4;
                    // 过滤字节,一般为 0x10 = 16 
                    data.setUint32(offset, 16, true); offset += 4;
                    // 格式类别 (PCM形式采样数据) 
                    data.setUint16(offset, 1, true); offset += 2;
                    // 通道数 
                    data.setUint16(offset, channelCount, true); offset += 2;
                    // 采样率,每秒样本数,表示每个通道的播放速度 
                    data.setUint32(offset, sampleRate, true); offset += 4;
                    // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8 
                    data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;
                    // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8 
                    data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
                    // 每样本数据位数 
                    data.setUint16(offset, sampleBits, true); offset += 2;
                    // 数据标识符 
                    writeString('data'); offset += 4;
                    // 采样数据总数,即数据总大小-44 
                    data.setUint32(offset, dataLength, true); offset += 4;
                    // 写入采样数据 
                    if (sampleBits === 8) {
                        for (var i = 0; i < bytes.length; i++, offset++) {
                            var s = Math.max(-1, Math.min(1, bytes[i]));
                            var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
                            val = parseInt(255 / (65535 / (val + 32768)));
                            data.setInt8(offset, val, true);
                        }
                    } else {
                        for (var i = 0; i < bytes.length; i++, offset += 2) {
                            var s = Math.max(-1, Math.min(1, bytes[i]));
                            data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
                        }
                    }


                    return new Blob([data], { type: 'audio/wav' });
                }
            };


            //开始录音
            this.start = function () {
                audioInput.connect(recorder);
                recorder.connect(context.destination);
            }


            //停止
            this.stop = function () {
                recorder.disconnect();
            }


            //获取音频文件
            this.getBlob = function () {
                this.stop();
                return audioData.encodeWAV();
            }


            //回放
            this.play = function (audio) {
                audio.src = window.URL.createObjectURL(this.getBlob());
            }


            //上传
            this.upload = function (url, callback) {
                var fd = new FormData();
                fd.append("audioData", this.getBlob());
                var xhr = new XMLHttpRequest();
                if (callback) {
                    xhr.upload.addEventListener("progress", function (e) {
                        callback('uploading', e);
                    }, false);
                    xhr.addEventListener("load", function (e) {
                        callback('ok', e);
                    }, false);
                    xhr.addEventListener("error", function (e) {
                        callback('error', e);
                    }, false);
                    xhr.addEventListener("abort", function (e) {
                        callback('cancel', e);
                    }, false);
                }
                xhr.open("POST", url);
                xhr.send(fd);
            }


            //音频采集
            recorder.onaudioprocess = function (e) {
                audioData.input(e.inputBuffer.getChannelData(0));
                //record(e.inputBuffer.getChannelData(0));
            }


        };
        //抛出异常
        HZRecorder.throwError = function (message) {
            alert(message);
            throw new function () { this.toString = function () { return message; } }
        }
        //是否支持录音
        HZRecorder.canRecording = (navigator.getUserMedia != null);
        //获取录音机
        HZRecorder.get = function (callback, config) {
            if (callback) {
                if (navigator.getUserMedia) {
                    navigator.getUserMedia(
                        { audio: true } //只启用音频
                        , function (stream) {
                            var rec = new HZRecorder(stream, config);
                            callback(rec);
                        }
                        , function (error) {
                            switch (error.code || error.name) {
                                case 'PERMISSION_DENIED':
                                case 'PermissionDeniedError':
                                    HZRecorder.throwError('用户拒绝提供信息。');
                                    break;
                                case 'NOT_SUPPORTED_ERROR':
                                case 'NotSupportedError':
                                    HZRecorder.throwError('浏览器不支持硬件设备。');
                                    break;
                                case 'MANDATORY_UNSATISFIED_ERROR':
                                case 'MandatoryUnsatisfiedError':
                                    HZRecorder.throwError('无法发现指定的硬件设备。');
                                    break;
                                default:
                                    HZRecorder.throwError('无法打开麦克风。异常信息:' + (error.code || error.name));
                                    break;
                            }
                        });
                } else {
                    HZRecorder.throwErr('当前浏览器不支持录音功能。'); return;
                }
            }
        }


        window.HZRecorder = HZRecorder;


    })(window);

     

    现在前台的代码就到这里了。

     

    3.下面进行Servlet处理

    我先说一下,这是在Tomcat服务器中运行的。

    下面的这个Servlet:UploadVideoServlet.do是将音频文件保存到服务器端的。

    代码如下:

    package com.hanfeng.servlet;

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.PrintWriter;
    import java.util.ArrayList;
    import java.util.List;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import javax.sound.sampled.AudioInputStream;
    import javax.sound.sampled.AudioSystem;
    import javax.sound.sampled.Clip;


    import java.io.IOException;
    import javax.servlet.annotation.WebServlet;
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;


    import com.hanfeng.dao.Music;
    import com.hanfeng.dao.Regist;
    import com.hanfeng.service.MusicService;
    import com.hanfeng.service.RegistService;


    public class UploadVideoServlet extends HttpServlet {


    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


    }


    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    MusicService musicService = new MusicService();
    HttpSession  hs = request.getSession();
    String userName = (String) hs.getAttribute("username");
    // 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
    String savePath = "D:/java web/apache-tomcat-9.0.0.M17/webapps/pcm";

    File file = new File(savePath);
    // 判断上传文件的保存目录是否存在
    if (!file.exists() && !file.isDirectory()) {
    System.out.println(savePath + "目录不存在,需要创建");
    // 创建目录
    file.mkdir();
    }
    // 消息提示
    String message = "";
    try {
    String filename = null;
    // 使用Apache文件上传组件处理文件上传步骤:
    // 1、创建一个DiskFileItemFactory工厂
    DiskFileItemFactory factory = new DiskFileItemFactory();
    // 2、创建一个文件上传解析器
    ServletFileUpload upload = new ServletFileUpload(factory);
    // 解决上传文件名的中文乱码
    upload.setHeaderEncoding("UTF-8");
    // 3、判断提交上来的数据是否是上传表单的数据
    if (!ServletFileUpload.isMultipartContent(request)) {
    // 按照传统方式获取数据
    return;
    }
    // 4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
    List<FileItem> list = upload.parseRequest(request);


    //System.out.println(list.get(0));
    String[] value = new String[5]; 
    int i=0;
    for (FileItem item : list) {
    // 如果fileitem中封装的是普通输入项的数据
    if (item.isFormField()) {
    // System.out.println("歌曲名"+item.getString("musicName")+"类别"+item.getString("musicType"));
    String name = item.getFieldName();
    // 解决普通输入项的数据的中文乱码问题
    value[i++] = item.getString("UTF-8");
    // value = new String(value.getBytes("iso8859-1"),"UTF-8");
    //System.out.println(name + "=" + value);
    } else {// 如果fileitem中封装的是上传文件
    // 得到上传的文件名称,

    filename = "test.wav";
    //item.getName();
    System.out.println(filename);
    if (filename == null || filename.trim().equals("")) {
    continue;
    }
    // 注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:
    // c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
    // 处理获取到的上传文件的文件名的路径部分,只保留文件名部分
    filename = filename.substring(filename.lastIndexOf("\\") + 1);
    // 获取item中的上传文件的输入流
    InputStream in = item.getInputStream();
    // 创建一个文件输出流
    FileOutputStream out = new FileOutputStream(savePath + "\\" + filename);
    // 创建一个缓冲区
    byte buffer[] = new byte[1024];
    // 判断输入流中的数据是否已经读完的标识
    int len = 0;
    // 循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
    while ((len = in.read(buffer)) > 0) {
    // 使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\"
    // + filename)当中
    out.write(buffer, 0, len);
    }
    // 关闭输入流
    in.close();
    // 关闭输出流
    out.close();
    // 删除处理文件上传时生成的临时文件
    item.delete();
    message = "文件上传成功!";
    }
    }

    } catch (Exception e) {
    message = "文件上传失败!";
    e.printStackTrace();
    }

    }

    }

    4.下面进入百度语音识别端口的连接,

    首先进入百度语音网站申请端口(都是免费的)http://yuyin.baidu.com/

    (1)点击SDK下载

    (2)选择应用,自己填一下信息,申请一下,很简单的

    (3)选择SDK,画红圈的是百度语音提供的demo,做javaweb用这个足以,就下载那个就行。

    选择java,导入到自己的工程中。

    (4)上述的都弄好了之后,进入应用管理,获取自己的key

     

    5.下面进行百度语音识别API连接

    建一个java文件

    package com.baidu.speech.serviceapi;


    import java.io.BufferedReader;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;


    import javax.xml.bind.DatatypeConverter;


    import org.json.JSONObject;


    public class Sample {


        private static final String serverURL = "http://vop.baidu.com/server_api";
        private static String token = "";
        private static final String testFileName = "D:/java web/apache-tomcat-9.0.0.M17/webapps/pcm/test.wav";//需要识别的音频文件
        //put your own params here
        private static final String apiKey = "自己的apiKey";
        private static final String secretKey = "自己的secretKey";
        private static final String cuid = “自己的网卡物理地址";


        public static void main(String[] args) throws Exception {
            getToken();
            method1();
            method2();
        }
        private static void getToken() throws Exception {
            String getTokenURL = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials" + 
                "&client_id=" + apiKey + "&client_secret=" + secretKey;
            HttpURLConnection conn = (HttpURLConnection) new URL(getTokenURL).openConnection();
            token = new JSONObject(printResponse(conn)).getString("access_token");
        }


        private static void method1() throws Exception {
            File wavFile = new File(testFileName);
            HttpURLConnection conn = (HttpURLConnection) new URL(serverURL).openConnection();


            // construct params
            JSONObject params = new JSONObject();
            params.put("format", "wav");
            params.put("rate", 16000);
            params.put("channel", "1");
            params.put("token", token);
            params.put("cuid", cuid);
            params.put("lan", "zh");
            params.put("len", wavFile.length());
            params.put("speech", DatatypeConverter.printBase64Binary(loadFile(wavFile)));


            // add request header
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");


            conn.setDoInput(true);
            conn.setDoOutput(true);


            // send request
            DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
            wr.writeBytes(params.toString());
            wr.flush();
            wr.close();


            printResponse(conn);
        }


        private static void method2() throws Exception {
            File wavFile = new File(testFileName);
            HttpURLConnection conn = (HttpURLConnection) new URL(serverURL
                    + "?cuid=" + cuid + "&token=" + token).openConnection();


            // add request header
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "audio/wav; rate=16000");


            conn.setDoInput(true);
            conn.setDoOutput(true);


            // send request
            DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
            wr.write(loadFile(wavFile));
            wr.flush();
            wr.close();


            printResponse(conn);
        }


        private static String printResponse(HttpURLConnection conn) throws Exception {
            if (conn.getResponseCode() != 200) {
                // request error
                return "";
            }
            InputStream is = conn.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(is));
            String line;
            StringBuffer response = new StringBuffer();
            while ((line = rd.readLine()) != null) {
                response.append(line);
                response.append('\r');
            }
            rd.close();
            System.out.println(new JSONObject(response.toString()).toString(4));
            //System.out.println(response.toString());
            return response.toString();
        }


        private static byte[] loadFile(File file) throws IOException {
            InputStream is = new FileInputStream(file);


            long length = file.length();
            byte[] bytes = new byte[(int) length];


            int offset = 0;
            int numRead = 0;
            while (offset < bytes.length
                    && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
                offset += numRead;
            }


            if (offset < bytes.length) {
                is.close();
                throw new IOException("Could not completely read file " + file.getName());
            }


            is.close();
            return bytes;
        }
        public static String getChinese(String paramValue) {
        String regex = "([\u4e00-\u9fa5]+)";
        String str = "";
        Matcher matcher = Pattern.compile(regex).matcher(paramValue);
        while (matcher.find()) {
        str+= matcher.group(0);
        }
        return str;
        }
    }

     

     

    好了做完上述操作基本上语音识别就实现了,自己可以先打开html的录音录好后,运行java程序,就可以查看语音识别结果了。

    当然你也可以将main方法去掉,将代码放入到一个Servlet中,将识别结果return出来,可以用ajaxj将结果回调获取,传值到搜索的页面,获得搜索结果。

    这是我做的Demo,有需要的可以自行下载  https://download.csdn.net/download/qq_33609401/10847695

    好的,就这些了,希望能帮助到需要语音识别的你们。

    如果有不懂的地方可以留言,谢谢。

    展开全文
  • Android语音识别,简单的理解就是把语音转化为文字。 在日常中,语音识别,车载导航、语音输入等,虽然不一定准确,但用途广泛。 这里就介绍下谷歌原生的语音识别与百度的语音识别 谷歌语音识别 谷歌语音识别...

    Android语音识别,简单的理解就是把语音转化为文字。

    在日常中,语音识别,车载导航、语音输入等,虽然不一定准确,但用途广泛。

    这里就介绍下谷歌原生的语音识别与百度的语音识别

    谷歌语音识别

    谷歌语音识别做法很简单

    1、首先检测本地是否有语音识别工具,比如谷歌语音搜索,如果没有就结束;

    2、用intent意图表示语音识别;

    3、发送这个intent,并等待返回;

    4、显示返回的内容;

    具体的代码如下:

    package com.example.speak_csdn;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import android.os.Bundle;
    import android.speech.RecognizerIntent;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.Toast;
    import android.app.Activity;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.content.pm.ResolveInfo;
    
    public class MainActivity extends Activity {
    
    	final int RESPONCERESULT=99;
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		Button btnButton=(Button)findViewById(R.id.mybtn);
    		btnButton.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				speak();
    			}
    		});
    	}
    
    	public void speak()
    	{
                try{  
                 //通过Intent传递语音识别的模式,开启语音  
                 Intent intent=new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);  
                 //语言模式和自由模式的语音识别  
                 intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);  
                 //提示语音开始  
                 intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "开始语音");  
                 
                 //开始语音识别  
                 startActivityForResult(intent, RESPONCERESULT);  
                 }catch (Exception e) {  
                     // TODO: handle exception  
                     e.printStackTrace();  
                     Toast.makeText(getApplicationContext(), "找不到语音设备", 1).show();  
                 }  
    	}
    
    	@Override
    	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    		// TODO Auto-generated method stub
    		
    		//回调获取从谷歌得到的数据   
            if(requestCode==RESPONCERESULT && resultCode==RESULT_OK){  
                //取得语音的字符  
                ArrayList<String> results=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);  
                //谷歌可能有许多语音类似的返回,越往上优先级越高,这里列出所有的返回并拼接成字符串   
                String resultString="";  
                for(int i=0;i<results.size();i++){  
                    resultString+=results.get(i);  
                }  
                Toast.makeText(this, resultString, 1).show();  
            }  
    		super.onActivityResult(requestCode, resultCode, data);
    	}
    	
    	
    
    }
    

    代码完成了,注意要加上网络访问权限,因为这个是在线语音识读,代码关键的语句在以下几句:

     //通过Intent传递语音识别的模式,开启语音  
                 Intent intent=new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);  
                 //语言模式和自由模式的语音识别  
                 intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);  
                 //提示语音开始  
                 intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "开始语音");  
                 
                 //开始语音识别  
                 startActivityForResult(intent, RESPONCERESULT);  


    对应这个的布局语句很简单,只有一个button按钮。给这个按钮绑定事件,点击运行...


    这和我们预料的不同啊,网络没有问题,测试了WIFI和GPRS都是同样的结果。最终这只能归结为谷歌后台服务无法连接,你或者可以通过翻墙来看到效果。

    悲伤。

    那现在我们看看中国本地的语音识别,百度语音。

    百度语音识别

    百度语音识别,应用的是百度提供的SDK来实现。这个在百度的开放平台上可以看到很详细的说明。
    应用它的步骤如下:
    1、下载jar包;
    2、添加权限;
    3、在代码中,用给定的API来做语音识别;
    4、显示返回内容;

    jar包、so文件下载

    以下是从百度开放平台上下载的jar包以及so文件,用于后续的开发使用

    权限添加

    AndroidManifest.xml中添加需要的权限,如下:
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_SETTINGS" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <!-- 蓝牙录音 -->
        <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
        <!-- 某些手机启动SCO音频连接需要此权限 -->
        <uses-permission android:name="android.permission.BROADCAST_STICKY" />
        <!-- 蓝牙录音检测耳机状态 -->
        <uses-permission android:name="android.permission.BLUETOOTH" />

    代码中使用API

    这个就是使用API的过程,如下代码:
    package com.example.baiduspeak_csdn;
    
    import java.util.ArrayList;
    
    import com.baidu.voicerecognition.android.ui.BaiduASRDigitalDialog;
    import com.baidu.voicerecognition.android.ui.DialogRecognitionListener;
    
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.Toast;
    import android.app.Activity;
    
    public class MainActivity extends Activity {
    
    	//百度自定义对话框
    	 private BaiduASRDigitalDialog mDialog = null;
    	 //对话框监听
    	 private DialogRecognitionListener mRecognitionListener;
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		Button btnButton=(Button)findViewById(R.id.mybtn);
    		btnButton.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				speak_Baidu();
    			}
    		});
    		
    		mRecognitionListener = new DialogRecognitionListener() {
    
                @Override
                public void onResults(Bundle results) {
                    ArrayList<String> rs = results != null ? results.getStringArrayList(RESULTS_RECOGNITION) : null;
                    if (rs != null && rs.size() > 0) {
                    	Toast.makeText(MainActivity.this, rs.get(0), 1).show();
                    }
    
                }
            };
    	}
        //百度语音识别
    	public void speak_Baidu()
    	{
    		if (mDialog != null) {
                mDialog.dismiss();
            }
            Bundle params = new Bundle();
            //设置注册百度开放平台得到的值 API_KEY,SECRET_KEY
            params.putString(BaiduASRDigitalDialog.PARAM_API_KEY, Constants.API_KEY);
            params.putString(BaiduASRDigitalDialog.PARAM_SECRET_KEY, Constants.SECRET_KEY);
            //设置对话框模式
            params.putInt(BaiduASRDigitalDialog.PARAM_DIALOG_THEME, Config.DIALOG_THEME);
            //根据设置新建对话框
            mDialog = new BaiduASRDigitalDialog(this, params);
            //设置对话框的监听
            mDialog.setDialogRecognitionListener(mRecognitionListener);
            //对话框设置
    	    mDialog.getParams().putInt(BaiduASRDigitalDialog.PARAM_PROP, Config.CURRENT_PROP);
    	    mDialog.getParams().putString(BaiduASRDigitalDialog.PARAM_LANGUAGE, Config.getCurrentLanguage());
    	    mDialog.getParams().putBoolean(BaiduASRDigitalDialog.PARAM_START_TONE_ENABLE, Config.PLAY_START_SOUND);
    	    mDialog.getParams().putBoolean(BaiduASRDigitalDialog.PARAM_END_TONE_ENABLE, Config.PLAY_END_SOUND);
    	    mDialog.getParams().putBoolean(BaiduASRDigitalDialog.PARAM_TIPS_TONE_ENABLE, Config.DIALOG_TIPS_SOUND);
    	    mDialog.show();
    	}
    	 @Override
    	    protected void onDestroy() {
    	        if (mDialog != null) {
    	            mDialog.dismiss();
    	        }
    	        super.onDestroy();
    	    }
    
    }
    
    代码也只是简单的定义一个按钮,按钮绑定一个事件。
    事件发起时,我们设定了挺多参数,重要的有平台分配的APP_KEY,SECRET_KEY,PROP,语言的选择等。
    根据这些,百度对我们发出的声音,在服务端,得到匹配的内容并返回前端。
    效果如下:


    这个效果我们是能看到的。简单的用法就是这样的。类似谷歌语音,一个请求即可。

    源码

    源码包括以上的谷歌和百度语音识别,可供下载:

    展开全文
  • 腾讯云语音识别服务开放实时语音识别、一句话识别和录音文件识别三种服务形式,满足不同类型开发者需求... 语音识别 简介 腾讯云语音识别 为企业提供极具性价比的语音识别服务 被微信王者荣耀 腾讯视频等大量内部...
  • 腾讯云语音识别服务开放实时语音识别、一句话识别和录音文件识别三种服务形式,满足不同类型开发者需求... 语音识别 简介 腾讯云语音识别 为企业提供极具性价比的语音识别服务 被微信王者荣耀 腾讯视频等大量内部...
  • [语音识别] 语音识别系统化整理

    千次阅读 2020-03-22 15:59:32
    [语音识别] 01 语音识别概述 [语音识别] 02 语音信号处理及特征提取 [语音识别] 03 GMM以及EM算法 [语音识别] 04 隐马尔可夫模型 (HMM) [语音识别] 05 基于GMM-HMM的语音识别系统(单音素、三音素) [语音识别]...
  • 本文采用百度云语音识别API接口,实现低于60s音频的语音识别,也可以用于合成文本长度小于1024字节的音频,此外采用snowboy离线语音唤醒引擎可实现离线语音唤醒,实现语音交互。基于本内容可实现语音控制小车,语音...
  • 腾讯云语音识别服务开放实时语音识别、一句话识别和录音文件识别三种服务形式,满足不同类型开发者需求... 语音识别 简介 腾讯云语音识别 为企业提供极具性价比的语音识别服务 被微信王者荣耀 腾讯视频等大量内部...
  • 语音识别--浅谈语音识别

    千次阅读 多人点赞 2018-07-31 17:08:50
    1.语音识别简介: 语音识别:将语音转成文字的一个过程; 2.语音识别过程: 3.学习语音资料:
  • 语音识别识别性别

    2020-10-27 18:21:27
    广告关闭腾讯云双11爆品提前享,精选热门... 腾讯云语音识别服务开放实时语音识别、一句话识别和录音文件识别三种服务形式,满足不同类型开发者需求... 语音识别 简介 腾讯云语音识别 为企业提供极具性价比的语音...
  • [语音识别] 10 端到端语音识别

    千次阅读 2020-03-21 11:52:53
    端到端语音识别
  • 语音识别概述

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 23,908
精华内容 9,563
关键字:

语音识别