OpenEar 是基于 CMU PocketSphinx 的开源语音库,提供了丰富的语音合成、语音识别相关的功能,用 Cocoa 风格重新封装了 PocketSphinx 的接口,方便 iOS 开发者快速在 PocketSphinx 基础上进行语音方面的研究和开发。
基础原理
CMU PocketSphinx 主要有四部分组成:信号处理和特征提取、声学模型、语言模型和解码器。工作流程如图所示:
信号处理和特征提取部分以音频信号为输入,通过消除噪音、信道失真等对语音进行增强,将语音信号从时域转化到频域,并为后面的声学模型提取合适的特征。声学模型将声学和发音学的知识进行整合,以特征提取模块提取的特征为输入,生成声学模型得分。语言模型估计通过重训练语料学习词之间的相互概率,来估计假设词序列的可能性,也即语言模型得分。如果了解领域或者任务相关的先验知识,语言模型得分通常可以估计的更准确。解码器对给定的特征向量序列和若干假设词序列计算声学模型得分和语言模型得分,将总体输出分数最高的词序列作为识别结果。PocketSphinx 使用的是梅尔倒谱系数特征和混合高斯-隐马尔科夫(GMM-HMM)声学模型。
文件格式
声学模型(Acoustic Model):
可以从 CMU 官网可以下载到训练好的声学模型。
- g2p: 提供字母到音素的转换,利用规则对字母生成发音,用来生成发音字典。
- feat.params: 特征提取中所使用的参数列表。
- mdef: 对 Tri-Phone 进行聚类形成 senone, 该文件定义了 Tri-Phone 到 senones 的映射定义。
- means: 保存模型中多个概率分布均值的文件。
- variances: 保存模型中多个概率分布方差的文件。
- sendump: 量化并压缩的混合高斯模型。
- transition_matrices: 隐马尔科夫模型的概率转移矩阵。
- noisedict: 噪声过滤字典。其中还包括标示一段语音开始和结束的语音信息。
语言模型(Language Model)
实际是概率分布模型,其中内容包括 corpus 中所有的字符串和其对应的出现概率。语言模型老版的格式后缀为.arpa
,新版为.lm
。实际使用的是由.arpa
压缩为二进制格式的.DMP
文件。
字典文件(.dic)
根据声学模型生成的字典,其中包括 corpus 中所有的字符串和其对应的发音,提供了声学模型建模单元与语言模型建模单元间的映射。
语法文件(.gram)
根据 JSGF 语法生成,规定识别过程中的固定语法。
使用步骤
- 把 openear.framework 加入 Link Binary With Libraries.
- 将声学模型 AcousticModelChinese.bundle 加入工程的 Copy Bundle Resource.
- 根据官方 demo 初步学习使用。
当 corpus 文件较大时,生成词典(dic 文件)和语音模型(dmp 文件)的过程较慢,第一次生成后可来到沙盒中的
Library/Caches
目录把生成好的两个文件移出,加入工程的 Copy Bundle Resource. 下次直接从 dic 和 dmp 文件初始化,并直接调用:1
2
3
4
5[[OEPocketsphinxController sharedInstance]
startListeningWithLanguageModelAtPath:self.pathToDynamicallyGeneratedLanguageModel
dictionaryAtPath:self.pathToDynamicallyGeneratedDictionary
acousticModelAtPath:[OEAcousticModel pathToModel:@"AcousticModelChinese"]
languageModelIsJSGF:FALSE];开发中把 OEPocketsphinxController / OELogging 的 Log 开关打开,以得到更多的日志信息。
Troubleshooting
- OpenEar 支持从 txt 格式的 corpus 文件直接生成字典文件和语音模型,但从中文声学模型生成大写字母注音会出现错误,它会把
A
误注为汉语拼音韵母a
,造成识别不准确。
声学模型中有一个LanguageModelGeneratorLookupList.text
文件,程序对 corpus 中每个词条进行注音时,先从 LMGLL 文件查询是否存在有现成的词条和注音,如果有,则直接使用而不调用生成注音的相关方法。为了防止前面提到的某些特殊词条的自动注音错误,在 LMGLL 中添加相应词条和正确的注音即可。注意,添加词条时要按已有首字排列顺序加入,程序的查找算法才能找到手动添加的词条。 - LMGLL 文件中有上万条词条,体积 2M 左右,如果我们只进行小范围且固定词条的语音识别,完全用不到这么大的 LMGLL 文件。将 LMGLL 中的内容替换成字典文件的内容,可有效减小声学模型体积。
在使用中文模型进行语音识别时,要调整 vadThreshold 的值,最佳值在
3.5~4
之间,可自行调整。1
[OEPocketsphinxController sharedInstance].vadThreshold = 3.5;
pocketsphinx 支持从 JSGF 文件生成命令语法。比如在固定位数的数字识别中,定义语法显然比把所有可能数字列举进 corpus 更方便。
1
2
3
4#JSGF V1.0;
grammar grammarNumberLM;
<rule_0> = ( 一 | 二 | 三 | 四 | 五 | 六 | 七 | 八 | 九 | 零 );
public <rule_1> = ( <rule_0> <rule_0> <rule_0> <rule_0> <rule_0> <rule_0> );OpenEar 支持直接从字典数据生成 JSGF 文件。
默认的识别使用的接口基于动态规划的 Viterbi 算法。在待识别的词条中有音近词等易混淆的情况时,可用
A*
路径搜索算法的接口返回多个结果。1
[OEPocketsphinxController sharedInstance].returnNbest = TRUE;
在
OEEventsObserverDelegate
的委托方法中对返回的 nbest 数组hypothesisArray
进行处理。hypothesisArray
是一个包含三组 Hypothesis 的数组。1
2
3
4
5
6
7
8
9
10
11
12- (void) pocketsphinxDidReceiveNBestHypothesisArray:(NSArray *)hypothesisArray {
// Pocketsphinx has an n-best hypothesis dictionary.
NSLog(@"Local callback: hypothesisArray is %@",hypothesisArray);
[hypothesisArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj objectForKey:@"Hypothesis"]) {
newPothesis = [obj objectForKey:@"Hypothesis"];
// do some stuff...
}
}];
}
性能测试
词表大小为7000左右,随机抽取600个词表中的词录音,从录音wav
文件进行识别测试,单个词识别速度达到0.2s
,准确率可达94%
。基本达到手机端语音识别的性能要求。