版权声明:本文为elecdog原创文章,可以转载,但必须在明确位置注明出处!谢谢合作。

关于语音识别,国内已经有比较好的公司推出相关的服务了,比如百度免费的离在线融合语音识别以及讯飞收费的在线和离线语音识别服务。这里不作过多介绍,需要的同学可以直接去官网阅读接入文档。这里要介绍的是一个离线语音识别的开源项目——CMU PocketSpinnx,在安卓开发中的使用。在智能家居 APP 开发中常需要在没法联网的设备识别一些比较简单的命令词,百度的离在线融合语音识别识别率还不错,不过在设备连接局域网的情况下仍然优先使用在线识别,导致识别时间太长或者转换不过来,讯飞离线语音识别没有使用过,因为是收费的,而且对于个人开发者价格也不便宜,相比较之下,PocketSpinnx则是完全离线的语音识别,只要按照要求部署项目,识别率也差强人意。

首先我们可以通过 PocketSpinnx 官网的介绍来了解该离线语音识别项目的工作原理,能够加深对项目使用的理解。下面我们一步一步来把PocketSpinnx的离线语音识别功能引入到我们自己的项目中来。

获取语言模型

首先我们需要把想要识别的命令词编写成命令集,打开 Sublime Text 新建 txt 文件,编码采用 utf-8,每一行写一个命令词,如图所示:

然后访问网址 http://www.speech.cs.cmu.edu/tools/lmtool-new.html 生成语言模型(国内访问不稳定,需自备梯子),点击选择文件,选择刚才编写的命令集文件 command.txt,然后点击COMPILE KNOWLEDGE BASE按钮就可以生成语言模型,如图:

这里生成了好几个文件,我们可以把整个 .tgz 文件下载下来解压缩,其中得到的 .lm 文件就是我们需要的语言模型。

获取字典模型

字典模型的作用,就是告诉语音识别器中文的发音,这样他才能认得中文,字典模型很简单,首先到 PocketSpinnx 的资源网盘,进入 Mandarin 文件夹,下载一个后缀为 .dic 的文件,里面涵盖了很多普通话的发音,查找到我们的命令词,找不到完整命令词的也可以找单个字的发音,然后参考这个 .dic 文件的格式,在上一步获取到的 .lm 语言模型文件中还有一个 .dic 文件,补充完整这个 .dic 文件的发音,如图所示:

这样字典模型就算是完成了。

获取声学模型

同样是在资源网盘的 Mandarin 文件夹下,下载 .tar.bz2 的压缩文件解压后,得到如下声学模型文件:

在项目中导入接口

在以上必要文件都准备好之后,我们可以在 PocketSpinnx 开源的安卓 demo中直观地了解具体的用法,项目结构如图所示:

我们可以把 demo 中的 aars 和 models 导入到我们自己的项目中,快速集成相关接口。demo 中 en-us-ptm 中的是英文的声学模型,为了能够识别中文,我们可以依样画葫芦新建一个 ptm-zh 文件夹,放入我们前面获取的普通话声学模型。同时,还需要把我们的语言模型和地点模型放进来,准备工作算是完成了。

获取识别器

现在我们已经可以在项目中调用相关的 API 了,首先需要获取最重要的语音识别器类SpeechRecognizer,如demo中的代码:

    private void runRecognizerSetup() {        // Recognizer initialization is a time-consuming and it involves IO,
        // so we execute it in async task
        new AsyncTask<Void, Void, Exception>() {            @Override
            protected Exception doInBackground(Void... params) {                try {
                    Assets assets = new Assets(PocketSphinxActivity.this);
                    File assetDir = assets.syncAssets();                    setupRecognizer(assetDir);
                } catch (IOException e) {                    return e;
                }                return null;
            }            @Override
            protected void onPostExecute(Exception result) {                if (result != null) {
                    ((TextView) findViewById(R.id.caption_text))
                            .setText("Failed to init recognizer " + result);
                } else {                    switchSearch(KWS_SEARCH);
                }
            }
        }.execute();
    }    private void setupRecognizer(File assetsDir) throws IOException {        // The recognizer can be configured to perform multiple searches
        // of different kind and switch between them

        recognizer = SpeechRecognizerSetup.defaultSetup()
                .setAcousticModel(new File(assetsDir, "en-us-ptm"))//设置声学模型的文件夹
                .setDictionary(new File(assetsDir, "cmudict-en-us.dict"))//设置字典模型
                .setRawLogDir(assetsDir) // To disable logging of raw audio comment out this call (takes a lot of space on the device)

                .getRecognizer();
        recognizer.addListener(this);        /** In your application you might not need to add all those searches.         * They are added here for demonstration. You can leave just one.         */

        // 创建短语监听
        recognizer.addKeyphraseSearch(KWS_SEARCH, KEYPHRASE);        //创建命令文件监听
        File menuGrammar = new File(assetsDir, "menu.gram");
        recognizer.addGrammarSearch(MENU_SEARCH, menuGrammar);        // Create grammar-based search for digit recognition
        File digitsGrammar = new File(assetsDir, "digits.gram");
        recognizer.addGrammarSearch(DIGITS_SEARCH, digitsGrammar);        // Create language model search
        File languageModel = new File(assetsDir, "weather.dmp");
        recognizer.addNgramSearch(FORECAST_SEARCH, languageModel);        // Phonetic search
        File phoneticModel = new File(assetsDir, "en-phone.dmp");
        recognizer.addAllphoneSearch(PHONE_SEARCH, phoneticModel);
    }    private void switchSearch(String searchName) {
        recognizer.stop();        // If we are not spotting, start listening with timeout (10000 ms or 10 seconds).
        if (searchName.equals(KWS_SEARCH))
            recognizer.startListening(searchName);        else
            recognizer.startListening(searchName, 10000);
    }

这里需要注意设置声学模型文件夹的时候不需要写成 sync/ptm-zh,sync不需要写,否则会报错找不到文件。到这里按照 demo 的示例代码基本可以学会重要的方法调用了,如开始监听和结束监听等。

这里再提一下我们创建命令文件监听的时候需要使用的 .gram 文件,其实看一下 demo 中的 .gram 文件我们也知道该如何编写自己的命令文件了

#JSGF V1.0;grammar menu;public <item> = 命令词1 | 命令词2 | 命令词3;

一旦开始命令文件监听,则监听器就会监听命令文件中的命令词,当监听到语音的时候就会取出发音最相似的那个命令词返回到监听结果。请注意,这里意思是取出发音最相近的,这导致的时候也许你并没有说这些命令词的任何一个,只是监听器同样会取出他认为最相近的一个返回给结果,也就是表现的识别过于敏感,我在使用过程中还是属于可接受范围内。可以根据自己需求选择短语监听或者命令文件监听。

结束

到这里基本就可以使用 PocketSpinnx 离线语音识别了,一些细节的处理还需要自己阅读 demo 中的代码,代码不多而且容易理解,可以加深对使用的理解,另外,推荐阅读官方文档,能够详细知道项目的运行原理,以及文中没有提到的一些内容,虽然是英文的,但通过单词翻译也不难理解。

原文链接:http://electhuang.com/2017/04/27/android-pocketSphinx/

http://www.cnblogs.com/elecdog/p/7235235.html