三、语音识别

你是否曾经在你的设备上点击过几个菜单和选项,直到你能做你想做的事情?你有没有希望自己能说几句话就能成功?本章介绍自动语音识别(ASR)将口语转化为书面语的过程。涵盖的主题如下:

  • 语音识别技术
  • 使用谷歌语音识别
  • 使用谷歌语音识别应用编程接口开发应用

到本章结束时,您应该对在应用中使用语音识别所涉及的问题有了很好的了解,并且应该能够使用谷歌语音应用编程接口开发简单的应用。

语音识别技术

以下是语音识别中的两个主要阶段:

  • 信号处理 : 这一阶段包括捕捉对着麦克风说出的单词,并使用模数转换器 ( ADC ) 将其转换为可由计算机处理的数字数据。模数转换器处理数字数据以消除噪声,并执行其他处理,如回声消除,以便能够提取与语音识别相关的特征。
  • 语音识别 : 信号被分割成微小的片段,这些片段与待识别语言的音素相匹配。音素是最小的语音单位,大致相当于字母表中的字母。例如:猫这个词中的音素有 /k///t/ 。例如,在英语中,大约有 40 个音素,这取决于所说的是哪一种英语。

语音识别最成功的方法是对语音进行统计建模,以便该过程的结果是对用户可能说过的话进行一系列猜测(或假设),并根据计算出的概率进行排序。复杂的概率函数用于执行这种统计建模。其中最常用的是隐马尔可夫模型、,但也使用神经网络,在某些情况下,混合过程使用这两种方法的组合。模型通过使用大量训练数据的训练过程进行调整。通常,使用数百到数千个音频小时来处理人类语音的可变性和复杂性。其结果是一个声学模型代表了一种语言的声音和单词的不同发音方式。

声学模型本身不足以实现高性能的语音识别。语音识别系统的一个附加部分是另一个统计模型,即语言模型。这个模型包含了关于允许的单词序列以及在给定的序列中哪些单词更可能的知识。例如,虽然从声学上来说两个听起来是一样的,但是前者更可能出现在短语我去商店中,后者出现在短语我买了两件衬衫中。语言模型将有助于在每个短语中返回正确的单词。

语音识别的输出是一个识别结果列表(称为【N-best list】),根据识别器对识别正确的置信度进行排序,区间为 0 到 1。接近 1.0 的值表示识别正确的高置信度,而接近 0.0 的值表示低置信度。最佳列表和置信度得分是有用的,因为第一最佳识别字符串可能不是用户实际所说的,并且备选方案可能提供更合适的结果。置信度得分也是有用的,因为它们可以为决定系统应该如何继续提供基础,例如,是否确认所识别的短语。

使用谷歌语音识别

语音识别服务从安卓 2.1(API 7 级)开始就在安卓设备上可用。一个可以识别的地方是通过安卓键盘上的麦克风图标。点击麦克风按钮激活谷歌语音识别服务,如下图所示。红色麦克风和文本提示表示系统正在等待一些语音:

Using Google speech recognition

如果没有检测到语音,将生成一个重新提示对话,要求用户再次尝试说话。另一种可能的情况是,对于用户的口头输入找不到合适的匹配。在这种情况下,屏幕显示消息没有找到匹配项。最后,当没有互联网连接时,会显示消息连接问题

车载语音识别服务的参数可以通过设置 | 语言和输入 | 语音 | 语音搜索进行调整,或者根据设备的不同,点击屏幕右上角的工具图标。许多可用语言将会出现。检查语言的可用性以及选择语言都可以通过编程来完成。这将在第 7 章多语言和多模式对话中介绍。

用谷歌语音识别 API 开发应用

谷歌语音应用编程接口(包android.speech)的组件记录在网站。这里列出了接口和类,点击它们可以获得更多的细节。

有两种方法可以在安卓设备上进行语音识别:仅基于RecognizerIntent方法,或者通过创建SpeechRecognizer实例。前者提供了一种易于编程的机制,通过启动意图类并处理其结果,可以创建使用语音识别的应用。遵循该方案的应用将呈现一个对话,向用户提供关于自动识别系统是否准备好的反馈,或者关于识别过程中不同错误的反馈。使用SpeechRecognizer为开发人员提供不同的识别相关事件的通知,从而允许对语音识别过程进行更细粒度的处理。采用这种方法,对话不会显示,为开发者提供了更多的应用图形用户界面控制。

在接下来的部分中,我们将展示两个应用(ASRWithIntentASRWithLib),它们识别用户所说的话,并以带有置信度得分的 N-best 列表的形式展示识别结果。本质上,它们是同一个应用,但是它们是按照所描述的两种不同的方法开发的:ASRWithIntent使用RecognizerIntent方法,而ASRWithLib使用SpeechRecognizer方法,这是我们在 ASRLib 库中(代码包中的sandra.libs.asr.asrlib)编程的。

自动退出意向应用

这个简单的应用说明了以下内容:

  1. 用户选择语音识别的参数。
  2. 用户按下一个按钮,说一些话。
  3. 识别出的单词和它们的置信度分数一起显示在列表中。

在打开的画面中,有一个按钮,信息为按下按钮说话。当用户按下按钮时,使用用户选择的参数启动语音识别。激活语音识别并说出字样的实例结果如下图所示:贝尔法斯特的天气如何:

ASRWithIntent app

用户按下开始语音识别的按钮是参照asrwithintent.xml中指定的按钮设置的,可以在代码包中找到(在ASRWithIntent项目的res/layout文件夹中):

//Gain reference to speak button 
Button speak = (Button) findViewById(R.id.speech_btn); 
//Set up click listener 
speak.setOnClickListener(
       new View.OnClickListener() { 
       @Override 
       public void onClick(View v) { 
             //Speech recognition does not currently work on simulated devices, 
             //it is the user attempting to run the app in a simulated device 
             //they will get a Toast 
             if("generic".equals(Build.BRAND. toLowerCase())){ 
                 Toast toast = Toast.makeText(getApplicationCon text(),"ASR is not supported on virtual devices", Toast.LENGTH_SHORT); 
                toast.show(); 
                Log.d(LOGTAG, "ASR attempt on virtual device"); 
             } 
             else{ 
                setRecognitionParams(); //Read parameters from GUI 
                listen(); //Set up the recognizer and start listening 
             } 
       } 
});

当用户按下按钮时,调用listen() 方法,其中RecognizerIntent的细节被设置。在ASRWithIntent中,类RecognizerIntent 通过使用方法startActivityForResult(Intent,int)、发送带有动作ACTION_RECOGNIZE_SPEECH、的意图来支持语音识别,其中int值是开发人员定义的请求代码。如果大于 0,当活动退出时,该代码将在onActivityResult() 中返回。请求代码作为一个标识符来区分哪个意图产生了某个结果和应用中可能调用的不同意图。

下面的代码启动一个识别语音的活动:

Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// Specify language model
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
// Specify how many results to receive. Results listed in order of confidence
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, numberRecoResults);
// Start listening
startActivityForResult(intent, ASR_CODE);

如代码所示,有许多额外的东西与ACTION_RECOGNIZE_SPEECH相关联。其中之一EXTRA_LANGUAGE_MODEL是必需的。其他都是可选的。顾名思义,EXTRA_LANGUAGE_MODEL指定了在识别过程中使用的语言模型。它支持以下两个选项:

  • LANGUAGE_MODEL_FREE_FORM : 这种语言模型基于自由形式的语音识别,用于识别自由形式的语音,例如,在电子邮件的听写中。
  • LANGUAGE_MODEL_WEB_SEARCH : 该语言模型基于网络搜索词,用于建模更受限的输入形式,例如更短的、类似搜索的短语,例如,飞往伦敦的航班马德里的天气等等。

可以看出,这两个选项都意味着输入完全不受限制。要构建一个只接受某些关键词的应用,或者使用识别语法,目前有必要对识别结果进行后处理,使其与预期模式相匹配。如何做到这一点的一些例子将在第 6 章对话语法中显示。

其他额外内容在RecognizerIntent类的安卓文档中有所描述。以下是一些更常用的可选附加功能:

  • EXTRA_PROMPT:这个给提供了一个文本提示,当用户被要求发言的时候会显示出来。
  • EXTRA_MAX_RESULTS : 该整数值指定了要返回的最大结果数的限制。如果省略,识别器将选择返回多少结果。结果是对应于用户输入的不同可能文本,并从最可能到不太可能排序(本章前面讨论的 N-best 列表)。
  • EXTRA_LANGUAGE : 这指定了可以使用的语言,而不是设备上提供的默认语言。其他语言的使用包含在第 7 章多语言和多模式对话中。

可以要求用户从这些选项中进行选择,也可以通过编程进行设置。ASRWithIntent应用提示用户语言模型和最大结果数。如果未提供信息,则使用两个常量中指示的默认值(参见asrwithintent.java文件中的showDefaultValuessetRecognitionParams方法)。

语音识别的结果通过onActivityResults(int, int, Intent)中的活动结果返回。至此,识别完成。然而,通常我们希望看到结果或以某种方式使用它们。这需要的额外步骤是在ASRWithIntent后面的注释代码中说明的:

protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
    if (requestCode == ASR_CODE) { 
        if (resultCode == RESULT_OK) { 
           //Retrieves the N-best list and the confidences from the ASR result 
           ArrayList<String> nBestList = 
             data.getStringArrayListE xtra(RecognizerIntent.EXTRA_RESULTS); 
           float[] nBestConfidences = 
             data.getFloatArrayExtra(RecognizerIntent.EXTRA_CONFIDENCE_SCORES;

           /** Creates a collection of strings, each one with a recognition result and its confidence, e.g. "Phrase matched (conf: 0.5)" */
           ArrayList<String> nBestView = new ArrayList<String>(); 

           for(int i=0; i<nBestList.size(); i++){ 
               if(nBestConfidences[i]<0) 
                 nBestView.add(nBestList.get(i) + " (no confidence value available)"); 
               else 
                 nBestView.add(nBestList.get(i) + " (conf: " + nBestConfidences[i] + ")"); 
           } 

           //Includes the collection in the listview of the GUI 
           setListView(nBestView); 

           //Adds information to log 
           Log.i(LOGTAG, "There were : "+ nBestView. size()+" recognition results"); 
       } 
       else { 
           //Reports error in log 
           Log.e(LOGTAG, "Recognition was not successful"); 
      } 
   } 
} 

如前所述,识别结果通过意图返回。匹配的句子可以使用方法getStringArrayListExtra 使用RecognizerIntent.EXTRA_RESULTS 作为参数来访问。类似地,可以用getFloatArrayExtraRecognizerIntent.EXTRA_CONFIDENCE_SCORES获得具有置信度得分的数组。

结果保存在一个新的ArrayList<String>中,其元素组合了来自匹配的字符串和来自浮点数组的分数(表示为字符串)。请注意,置信度得分有可能为-1;当信心分数不可用时就是这种情况。然后在setListView方法中调用ArrayAdapter,将格式化后的字符串插入到图形用户界面的ListView方法中。

也可能发生识别不能令人满意地执行的情况(在前面的代码中为resultCode!=RESULT_OK)。RecognizerIntent类中定义的主要错误情况有不同的常量:RESULT_AUDIO_ERRORRESULT_CLIENT_ERRORRESULT_NETWORK_ERRORRESULT_SERVER_ERRORRESULT_NOMATCH。它们都对应于由于物理设备或网络导致的错误,除了最后一个对应于没有短语匹配音频输入的情况。开发人员可以使用结果代码对这些错误进行详细的处理。然而,显示的对话已经实现了对这种错误的简单处理,这对于更简单的应用来说已经足够了。在这种情况下,用户接收关于错误的反馈,并且当反馈适用时,他们被要求重复他们的话语。

最后,由于 app 需要接入互联网进行语音识别,需要在manifest文件中设置权限:

<uses-permission android:name="android.permission.INTERNET" />

ASRWithLib 应用

ASRWithLib应用具有与ASRWithIntent完全相同的功能,但是它没有单独使用RecognizerIntent类,而是创建了一个SpeechRecognizer类的实例。为了使代码在所有使用 ASR 的应用中可用,我们建议使用一个库,就像我们在上一章中对 TTS 所做的那样。在这种情况下,库在sandra.libs.asr.asrlib中,只包含一个类ASR,实现了RecognitionListener接口,从而实现了所有应对不同识别事件的方法,即 onResultsonErroronBeginningOfSpeechonBufferReceivedonEndOfSpeechonEventonPartialResultsonReadyForSpeechonRmsChanged

在检查设备中的 ASR 引擎可用后,在createRecognizer方法中创建SpeechRecognizer实例:

List<ResolveInfo> intActivities = packManager.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);

if (intActivities.size() != 0) {
    myASR = SpeechRecognizer.createSpeechRecognizer(ctx);
    myASR.setRecognitionListener(this);
}

该代码查询PackageManager类检查是否支持识别。如果intActivities的值。size()大于零,支持语音识别,然后创建一个SpeechRecognizer的实例,保存在属性 myASR中。使用对this的引用来设置用于识别的监听器,因为 ASR 类实现了 RecognitionListener接口。

listen方法是用一节RecognizerIntent课开始听。虽然附加功能与ASRWithIntent应用相同,但识别开始的方式略有不同。ASRWithIntent使用startActivityForResult而在这种情况下,SpeechRecognizer对象负责开始识别,接收意图作为参数。

public void listen(String languageModel, int maxResults) throws Exception{ 

      if((languageModel.equals(RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) || languageModel.equals(RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH)) && (maxResults>=0){ 
          Intent intent = 
                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 
          // Specify the calling package to identify the application 
          intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, ctx. getPackageName()); 

          // Specify language model intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, 
          languageModel); 

          // Specify how many results to receive. 
          // Results listed in order of confidence 
          intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); 

          // Start recognition 
          myASR.startListening(intent); 
      }
      else 
          throw new Exception("Invalid parameters for listen method"); 
}

ASRWithIntent应用中,所有代码都在同一个类中,识别参数(languageModelmaxResults)的正确性测试是在该单个类中进行的。然而,这里我们正在构建一个将在许多应用中使用的库,因此不可能想当然地认为在调用listen方法之前会检查参数。这就是为什么该方法会检查它们的值,并在它们不正确时引发异常。

有几种方法用于对 ASR 引擎可能引发的不同事件做出反应。在库中实施对这些事件的响应不是一个好的策略,首先是因为这将意味着所有使用库的应用对这些事件进行相同的管理;其次是因为响应事件的大部分时间都涉及到向用户显示消息或者以某种其他方式使用 GUI,将应用的逻辑与其界面分离是一个基本的设计原则。

这就是为什么ASR类采用抽象方法的原因。抽象方法只声明头,子类有责任为它们提供代码。这样,负责响应 ASR 事件的每一个方法都会调用一个抽象方法,并且这些方法的行为对于 ASR 的每一个子类都是不同的。在ASRWithLib示例中,ASRWithLib类是 ASR 的子类,以某种方式实现抽象方法。如果我们有另一个应用,我们想在其中开发不同的行为,我们可以为这些方法编写单独的代码。

例如,当引擎找到与用户所说相匹配的句子时,就会调用ASR.java类中的onResults方法。ASR.java中该方法的代码如下。注意,为了检索结果,它使用来自SpeechRecognizer类的静态方法,而不是像ASRWithIntent中那样来自RecognizerIntent:

public void onResults(Bundle results) {
processAsrResults(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION),results.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES));
}

该方法调用抽象方法 processAsrResults。这个方法是在ASRWithLib类中实现的,指示如何处理结果(在这种情况下,通过填充列表视图,如在ASRWithIntent中)。

正如您可能已经观察到的那样,ASRWithLib应用不显示识别对话。这在执行连续语音识别的应用中可能是可取的(自动语音识别作为后台服务总是活跃的),对于这种应用,这样的反馈可能会让用户感到讨厌。但是,对于其他应用,有必要向用户显示一些反馈,以便他们知道该应用正在收听。这是用onAsrReadyForSpeech方法进行的。该方法在 ASR 引擎准备开始监听时执行,是从ASRLib中抽象出来的方法,通过改变语音按钮的颜色和消息在ASRWithLib.java中实现(文本和颜色都不是硬编码的,而是从res/values文件夹中检索到的):

Button button = (Button) findViewById(R.id.speech_btn); button.setText(getResources().getString(R.string.speechbtn_listening));
button.setBackgroundColor(getResources().getColor(R.color.speechbtn_listening));

最后,需要在manifest文件中设置使用 ASR 访问互联网和录制音频的权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>

总结

本章展示了如何使用谷歌语音应用编程接口来实现语音识别服务,并检查了这些服务在设备上是否可用。用户被提示说出一些单词,并且识别的结果、识别的字符串及其置信度分数被显示在屏幕上。用户可以选择用于识别的语言模型和要检索的最大结果数。该功能在ASRWithIntentASRWithLib应用中通过两种不同的方法实现。

ASRWithIntent应用是一个基本的易于开发的例子,其中所有的代码都包含在同一个类中。ASR 使用RecognizerIntent类执行,并有一个自动生成的对话,提供关于发动机是否正在侦听或是否有任何错误的反馈。

ASRWithLib应用展示了如何模块化并创建一个可以在许多应用中使用的语音识别库。它不仅仅依赖于RecognizerIntent类,而是使用SpeechRecognizer类的一个实例,并使用抽象方法实现RecognitionListener接口,提供了一个灵活的实现,可以响应各种各样的 ASR 事件。

虽然这两个例子本身并不是特别有用的应用,但这里给出的代码可以在任何使用语音识别的应用中不加改动地使用。这本书未来的例子将建立在这个代码上。下一章将展示如何将 TTS 和 ASR 结合起来执行简单的语音交互,用户可以在其中询问信息或向设备发出命令。