2016-07-07 08:46:56 guchuanhang 阅读数 7007

高德地图官方导航部分Demo使用android sdk 24编译,执行会崩溃。经过和高德工程师的交涉得知,地图中使用了科大讯飞的语音模块,崩溃的就是语音部分。探究一下科大讯飞语音识别,在此记录一下成长点滴!
这里使用的是科大讯飞

科大讯飞API提供的功能

  • 语音合成

    将一段文字转换为成语音,可根据需要合成出不同音色、语速和语调的声音,让机器像人一样开口说话。

  • 语音听写

    将一段语音转换成文本,把语音中包含文字信息提取出来,并可以优先识别用户手机特有的联系人和个性化数据。

  • 语法识别

    判断用户所说的内容是否与预定义的语法相符合, 主要用于识别用户是否下达某项指令,使用语法识别前,需要先定义语法。

  • 语义理解

    在语音听写基础上,分析理解用户的说话意图,返回结构化的指令信息。开发者可在语义开放平台定义专属的问答格式。

  • 语音评测

    通过智能语音技术自动对发音水平进行评价,给出用户综合得分和发音信息。

  • 声纹密码

    据语音波形反映说话人生理和行为特征的语音参数,自动识别说话人身份,声纹识别所提供的安全性可与其他生物识别技术(指纹、掌形和虹膜)相媲美。

  • 人脸识别

    基于人的脸部特征信息进行身份识别的一种生物识别技术,可以自动在图像中检测和跟踪人脸,进而对检测到的人脸进行检测和验证。系统同时支持人脸关键点检出、视频流人脸检测等功能,识别率高达 99%。

由于时间和能力限制,这里仅仅对语音听写&&语音合成中的云服务部分进行简单探究

环境搭建

讯飞官网,申请一个开发者账号,创建一个应用,下载相应的sdk,添加相关的jar和so文件。
1.在Application中完成讯飞模块的初始化

 @Override
    public void onCreate() {
        super.onCreate();
        // 将“12345678”替换成您申请的 APPID
// 请勿在“ =” 与 appid 之间添加任务空字符或者转义符
SpeechUtility.createUtility(context, SpeechConstant.APPID +"=12345678");
        SpeechUtility.createUtility(this, SpeechConstant.APPID +"=12345678");
    }

2.添加相应的权限

  <!--连接网络权限,用于执行云端语音能力 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <!--读取网络信息状态 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--获取当前wifi状态 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <!--允许程序改变网络连接状态 -->
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <!--读取手机信息权限 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!--读取联系人权限,上传联系人需要用到此权限 -->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <!--外存储写权限, 构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!--外存储读权限,构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!--配置权限,用来记录应用配置信息 -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />

    <!--摄相头权限, 拍照需要用到 -->
    <uses-permission android:name="android.permission.CAMERA" />

语音合成

就是将文本读出来
使用的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
    <!--activity_text2speech.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingLeft="10dp"
    android:paddingRight="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="讯飞合成示例"
        android:textSize="30sp" />

    <EditText
        android:id="@+id/tts_text"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="top|left"
        android:text="科大讯飞作为中国最大的智能语音技术提供商,在智能语音技术领域有着长期的研究积累,并在中文语音合成、语音识别、
    口语评测等多项技术上拥有国际领先的成果。科大讯飞是我国唯一以语音技术为产业化方向的“国家863计划成果产业化基地”…"
        android:textSize="20sp" />


    <Button
        android:id="@+id/tts_play"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始合成"
        android:textSize="20sp" />

</LinearLayout>

java代码如下:

package com.iflytek.voicedemo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechSynthesizer;
import com.iflytek.cloud.SynthesizerListener;

public class Text2SpeechActivity extends Activity implements OnClickListener {
    // 语音合成对象
    private SpeechSynthesizer mTts;
    private Toast mToast;

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_text2speech);

        findViewById(R.id.tts_play).setOnClickListener(this);
        // 初始化合成对象
        mTts = SpeechSynthesizer.createSynthesizer(this, null);

        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
        applyPermission();
    }

    //获取权限,将语音保存到sd卡
    private void applyPermission() {
        ActivityCompat.requestPermissions(this, new String[]{
                Manifest.permission.READ_EXTERNAL_STORAGE
        }, 12);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            // 开始合成
            // 收到onCompleted 回调时,合成结束、生成合成音频
            // 合成的音频格式:只支持pcm格式
            case R.id.tts_play: {
                String text = ((EditText) findViewById(R.id.tts_text)).getText().toString();
                // 设置参数
                setParam();
                int code = mTts.startSpeaking(text, mTtsListener);
//          /** 
//           * 只保存音频不进行播放接口,调用此接口请注释startSpeaking接口
//           * text:要合成的文本,uri:需要保存的音频全路径,listener:回调接口
//          */
//          String path = Environment.getExternalStorageDirectory()+"/tts.pcm";
//          int code = mTts.synthesizeToUri(text, path, mTtsListener);
                if (code != ErrorCode.SUCCESS) {
                    showTip("语音合成失败,错误码: " + code);
                }
            }
            break;
        }

    }

    /**
     * 合成回调监听。
     */
    private SynthesizerListener mTtsListener = new SynthesizerListener() {

        @Override
        public void onSpeakBegin() {
            showTip("开始播放");
        }

        @Override
        public void onSpeakPaused() {
            showTip("暂停播放");
        }

        @Override
        public void onSpeakResumed() {
            showTip("继续播放");
        }

        @Override
        public void onBufferProgress(int percent, int beginPos, int endPos,
                                     String info) {
            // 合成进度
        }

        @Override
        public void onSpeakProgress(int percent, int beginPos, int endPos) {
            // 播放进度
        }

        @Override
        public void onCompleted(SpeechError error) {
            if (error == null) {
                showTip("播放完成");
            } else if (error != null) {
                showTip(error.getPlainDescription(true));
            }
        }

        @Override
        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
            // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
            // 若使用本地能力,会话id为null
 // if (SpeechEvent.EVENT_SESSION_ID == eventType) {
//      String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
            //      Log.d(TAG, "session id =" + sid);
            //  }
        }
    };

    private void showTip(final String str) {
        mToast.setText(str);
        mToast.show();
    }

    /**
     * 参数设置
     *
     * @return
     */
    private void setParam() {
        // 清空参数
        mTts.setParameter(SpeechConstant.PARAMS, null);
        // 根据合成引擎设置相应参数
        mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
        // 设置在线合成发音人
        mTts.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
        //设置合成语速
        mTts.setParameter(SpeechConstant.SPEED,
                "50");
        //设置合成音调
        mTts.setParameter(SpeechConstant.PITCH, "50");
        //设置合成音量
        mTts.setParameter(SpeechConstant.VOLUME,
                "50");
        //设置播放器音频流类型
        mTts.setParameter(SpeechConstant.STREAM_TYPE,
                "3");
        // 设置播放合成音频打断音乐播放,默认为true
        mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");

        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/tts.wav");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mTts.stopSpeaking();
        // 退出时释放连接
        mTts.destroy();
    }

}

使用官方封装的UI,进行语音听写

为了便于开发者调用,讯飞封装了一套用于语音输入的UI。在使用之前需要将官方Demo中的assets目录下的iflytek放到你的项目的assets目录下。其内容如下:

这里写图片描述
展示界面的布局文件

<?xml version="1.0" encoding="utf-8"?>
    <!--activity_speech2text_ui.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingLeft="10dp"
    android:paddingRight="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="讯飞听写示例"
        android:textSize="30sp" />

    <EditText
        android:id="@+id/iat_text"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="top|left"
        android:paddingBottom="20dp"
        android:textSize="20sp" />

    <Button
        android:id="@+id/iat_recognize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始"
        android:textSize="20sp" />

</LinearLayout>

java代码:

package com.iflytek.voicedemo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;

import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;
import com.iflytek.speech.util.JsonParser;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.LinkedHashMap;

public class Speech2TextUIActivity extends Activity implements OnClickListener {
    private static String TAG = Speech2TextUIActivity.class.getSimpleName();
    // 语音听写UI
    private RecognizerDialog mIatDialog;
    // 用HashMap存储听写结果
    private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();

    private EditText mResultText;
    private Toast mToast;

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_speech2text_ui);

        findViewById(R.id.iat_recognize).setOnClickListener(this);
        // 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer
        // 使用UI听写功能,放置布局文件和图片资源
        //使用云服务InitListener参数写成null
        mIatDialog = new RecognizerDialog(this, null);

        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
        mResultText = ((EditText) findViewById(R.id.iat_text));
        applyPermission();
    }

    private void applyPermission() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 12);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            // 开始听写
            // 如何判断一次听写结束:OnResult isLast=true 或者 onError
            case R.id.iat_recognize:
                mResultText.setText(null);// 清空显示内容
                mIatResults.clear();
                // 显示听写对话框
                mIatDialog.setListener(mRecognizerDialogListener);
                mIatDialog.show();
                showTip("请开始说话…");
                break;
            default:
                break;
        }
    }


    private void printResult(RecognizerResult results) {
        String text = JsonParser.parseIatResult(results.getResultString());

        String sn = null;
        // 读取json结果中的sn字段
        try {
            JSONObject resultJson = new JSONObject(results.getResultString());
            sn = resultJson.optString("sn");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        mIatResults.put(sn, text);

        StringBuffer resultBuffer = new StringBuffer();
        for (String key : mIatResults.keySet()) {
            resultBuffer.append(mIatResults.get(key));
        }

        mResultText.setText(resultBuffer.toString());
        mResultText.setSelection(mResultText.length());
    }

    /**
     * 听写UI监听器
     */
    private RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {
        public void onResult(RecognizerResult results, boolean isLast) {
            printResult(results);
        }

        /**
         * 识别回调错误.
         */
        public void onError(SpeechError error) {
            showTip(error.getPlainDescription(true));
        }

    };


    private void showTip(final String str) {
        mToast.setText(str);
        mToast.show();
    }

}

解释服务器返回数据,用到的工具类

package com.iflytek.speech.util;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.util.Log;

/**
 * Json结果解析类
 */
public class JsonParser {

    public static String parseIatResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONObject joResult = new JSONObject(json);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                // 转写结果词,默认使用第一个结果
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                JSONObject obj = items.getJSONObject(0);
                ret.append(obj.getString("w"));
//              如果需要多候选结果,解析数组其他字段
//              for(int j = 0; j < items.length(); j++)
//              {
//                  JSONObject obj = items.getJSONObject(j);
//                  ret.append(obj.getString("w"));
//              }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return ret.toString();
    }

    public static String parseGrammarResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                for(int j = 0; j < items.length(); j++)
                {
                    JSONObject obj = items.getJSONObject(j);
                    if(obj.getString("w").contains("nomatch"))
                    {
                        ret.append("没有匹配结果.");
                        return ret.toString();
                    }
                    ret.append("【结果】" + obj.getString("w"));
                    ret.append("【置信度】" + obj.getInt("sc"));
                    ret.append("\n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        } 
        return ret.toString();
    }

    public static String parseLocalGrammarResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                for(int j = 0; j < items.length(); j++)
                {
                    JSONObject obj = items.getJSONObject(j);
                    if(obj.getString("w").contains("nomatch"))
                    {
                        ret.append("没有匹配结果.");
                        return ret.toString();
                    }
                    ret.append("【结果】" + obj.getString("w"));
                    ret.append("\n");
                }
            }
            ret.append("【置信度】" + joResult.optInt("sc"));

        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        } 
        return ret.toString();
    }
}

这里写图片描述
官方定制的UI还是挺漂亮的!

不使用官方UI控件进行语音听写

使用到的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
    <!--activity_speech2text.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingLeft="10dp"
    android:paddingRight="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="讯飞听写示例"
        android:textSize="30sp" />

    <EditText
        android:id="@+id/iat_text"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="top|left"
        android:paddingBottom="20dp"
        android:textSize="20sp" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dp"
        android:layout_marginTop="10dp"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <Button
            android:id="@+id/iat_recognize"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始"
            android:textSize="20sp" />

        <Button
            android:id="@+id/iat_stop"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止"
            android:textSize="20sp" />

        <Button
            android:id="@+id/iat_cancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

java代码:

package com.iflytek.voicedemo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.speech.util.JsonParser;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.LinkedHashMap;

public class Speech2TextActivity extends Activity implements OnClickListener {
    private static String TAG = Speech2TextActivity.class.getSimpleName();
    // 语音听写对象
    private SpeechRecognizer mIat;
    // 用HashMap存储听写结果
    private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();

    private EditText mResultText;
    private Toast mToast;

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_speech2text);

        findViewById(R.id.iat_recognize).setOnClickListener(Speech2TextActivity.this);
        findViewById(R.id.iat_stop).setOnClickListener(Speech2TextActivity.this);
        findViewById(R.id.iat_cancel).setOnClickListener(Speech2TextActivity.this);
        mIat = SpeechRecognizer.createRecognizer(this, null);

        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
        mResultText = ((EditText) findViewById(R.id.iat_text));
        applyPermission();
    }

    private void applyPermission() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 12);

    }

    int ret = 0; // 函数调用返回值

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            // 开始听写
            // 如何判断一次听写结束:OnResult isLast=true 或者 onError
            case R.id.iat_recognize:
                mResultText.setText(null);// 清空显示内容
                mIatResults.clear();
                // 设置参数
                setParam();
                ret = mIat.startListening(mRecognizerListener);
                if (ret != ErrorCode.SUCCESS) {
                    showTip("听写失败,错误码:" + ret);
                }
                break;
            case R.id.iat_stop:
                mIat.stopListening();
                showTip("停止听写");
                break;
            case R.id.iat_cancel:
                mIat.cancel();
                showTip("取消听写");
                break;
            default:
                break;
        }
    }


    /**
     * 听写监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

        @Override
        public void onBeginOfSpeech() {
            // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
            showTip("开始说话");
        }

        @Override
        public void onError(SpeechError error) {
            // Tips:
            // 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
            // 如果使用本地功能(语记)需要提示用户开启语记的录音权限。
            showTip(error.getPlainDescription(true));
        }

        @Override
        public void onEndOfSpeech() {
            // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
            showTip("结束说话");
        }

        @Override
        public void onResult(RecognizerResult results, boolean isLast) {
            Log.d(TAG, results.getResultString());
            printResult(results);
        }

        @Override
        public void onVolumeChanged(int volume, byte[] data) {
            showTip("当前正在说话,音量大小:" + volume);
            Log.d(TAG, "返回音频数据:" + data.length);
        }

        @Override
        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
            // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
            // 若使用本地能力,会话id为null
            //  if (SpeechEvent.EVENT_SESSION_ID == eventType) {
            //      String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
            //      Log.d(TAG, "session id =" + sid);
            //  }
        }
    };

    private void printResult(RecognizerResult results) {
        String text = JsonParser.parseIatResult(results.getResultString());

        String sn = null;
        // 读取json结果中的sn字段
        try {
            JSONObject resultJson = new JSONObject(results.getResultString());
            sn = resultJson.optString("sn");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        mIatResults.put(sn, text);

        StringBuffer resultBuffer = new StringBuffer();
        for (String key : mIatResults.keySet()) {
            resultBuffer.append(mIatResults.get(key));
        }

        mResultText.setText(resultBuffer.toString());
        mResultText.setSelection(mResultText.length());
    }


    private void showTip(final String str) {
        mToast.setText(str);
        mToast.show();
    }

    /**
     * 参数设置
     */
    public void setParam() {
        // 清空参数
        mIat.setParameter(SpeechConstant.PARAMS, null);
        // 设置听写引擎
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
        // 设置返回结果格式
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");

        // 设置语言
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        // 设置语言区域
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin");

        // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
        mIat.setParameter(SpeechConstant.VAD_BOS, "4000");

        // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
        mIat.setParameter(SpeechConstant.VAD_EOS, "1000");

        // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
        mIat.setParameter(SpeechConstant.ASR_PTT, "1");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 退出时释放连接
        mIat.cancel();
        mIat.destroy();
    }

}
2019-07-16 22:20:52 weixin_41213648 阅读数 194

概述

虽然现在的语音识别中,DL已经应用的非常广泛了,但是语音识别终究还是有一些领域知识的,将之归类为DL或者ML,似乎都不妥当。特形成本系列文章,用以描述automatic speech recognition的领域知识和传统方法。

说起来还是要感谢DL,不然按照传统的行业划分,几乎不会有人同时研究CV和ASR。DL的出现,实际上大大降低了算法的领域迁移成本,领域知识的重要性相对下降了。

历史

早在电子计算机出现之前,人们就有了让机器识别语音的梦想。1920年生产的“Radio Rex”玩具狗可能是世界上最早的语音识别器,当有人喊“Rex”的时候,这只狗能够从底座上弹出来。

但实际上它所用到的技术并不是真正的语音识别,而是通过一个弹簧,这个弹簧在接收到500赫兹的声音时会自动释放,而500赫兹恰好是人们喊出“Rex”中元音的第一个共振峰。

学校

SR领域最牛的高校主要是美国的CMU、Johns Hopkins University、英国的Cambridge University和日本的东京大学。

书籍

《Speech and Language Processing: An introduction to natural language processing, computational linguistics, and speech recognition》,Daniel Jurafsky & James H. Martin著。

Daniel Jurafsky,1962年生,UCB本科(1983)+博士(1992)。斯坦福大学教授。 
个人主页: 
https://web.stanford.edu/~jurafsky/

James H. Martin,哥伦比亚大学本科+UCB博士。University of Colorado Boulder教授。 
个人主页: 
http://www.cs.colorado.edu/~martin/

这本书比较老了(1999年),但毕竟是本1000页左右的书,传统方法该说的基本都说了。主要问题在于NLP和语义学的内容较多,相对来说ASR的内容就少了些。

这本书在2008年出了第2版(改动较小),如今第3版也在酝酿中,草稿可在如下网页获得:

https://web.stanford.edu/~jurafsky/slp3/

新版大幅增加了深度学习的内容。

《Spoken Language Processing-A Guide to Theory, Algorithm and System Development》,黄学东等著。

这本书基本上是ASR传统方法的大全了,无论理论还是工程实践都有相当大的篇幅,但也有些老了(2001年)。

《解析深度学习:语音识别实践》,俞栋、邓力著。

这本书算是中文写的比较好的教程了,而且DL的篇幅很大,内容非常新。(2016年)

教程

http://tts.speech.cs.cmu.edu/courses/11492/schedule.html

Speech Processing。CMU的这个教程主要包含ASR(Automatic Speech Recognition)、TTS(Text To Speech)和SDS(Spoken Dialog Systems)等三方面的内容。

Alan W Black,苏格兰计算机科学家。Coventry University本科(1984)+University of Edinburgh硕博(1984,1993)。CMU教授。语音处理专家。 
个人主页: 
http://www.cs.cmu.edu/~awb/ 
他的主页上有好多Speech、NLP方面的教程。他本人长得太像Java之父James Gosling了。

http://web.stanford.edu/class/cs224s/index.html

CS224S / LINGUIST285 - Spoken Language Processing。Stanford的教程相对比较新,DL涉及的比较多。

http://www.inf.ed.ac.uk/teaching/courses/asr/index.html

Automatic Speech Recognition。这个课程至少从2012年就开始了,每年都有更新。

http://speech.ee.ntu.edu.tw/DSP2018Spring/

国立台湾大学李琳山教授的课程。

李琳山,国立台湾大学本科(1974)+Stanford博士(1977)。国立台湾大学教授。

http://www.cs.cmu.edu/afs/cs/user/bhiksha/WWW/courses/11-756.asr/spring2014/

Theory and practice of speech recognition systems。CMU的Bhiksha Raj教授的课程,只有ASR的内容。

顺便说一句,Bhiksha Raj的主页上还有好多其他课程。

https://cs.nyu.edu/~eugenew/asr13/

这是MIT博士Eugene Weinstein在NYU当助教的时候(2013年)开的课程。

http://berlin.csie.ntnu.edu.tw/Courses/Speech%20Processing/Speech%20Processing_Main_2016S.htm

Speech Processing。国立台湾师范大学的陈柏琳教授的课程。陈教授教学多年,主页上还有好多其他课程。

https://www.isip.piconepress.com/courses/msstate/ece_8463/lectures/current/index.html

Mississippi State University:ECE 8463: fundamentals of speech recognition

https://www.isip.piconepress.com/courses/msstate/ece_7000_speech/index.html

ECE 8000: special topics in speech recognition

https://www.isip.piconepress.com/courses/msstate/ece_8990_info/index.html

ECE 8990: Information Theory。这门课偏重数学理论,包括Entropy、Markov Processes、Kolmogorov Complexity等内容,适合用于补数学基础。

http://courses.cs.tamu.edu/rgutier/csce630_f14/

CSCE 630: Speech Processing

http://courses.cs.tamu.edu/rgutier/cpsc689_s07/

CPSC 689-604: Special topics in Speech and Face Recognition

https://mp.weixin.qq.com/s/oaOkla9gnUKr2C6PSjE2BA

语音识别中的End-to-End模型教程(附178页PDF全文下载)

https://mp.weixin.qq.com/s/i7JaDoU2L7uRYsw8FTR3jA

语音研究进阶指南

blog

http://www.cnblogs.com/welen/

https://blog.csdn.net/weiqiwu1986

上面两个都是welen的blog,而且内容貌似还不重复。。。

http://blog.csdn.net/xmdxcsj

一个语音识别的blog

https://blog.csdn.net/shichaog

一个语音识别+Kaldi的blog

https://blog.csdn.net/quhediegooo/

一个语音识别的blog

https://blog.csdn.net/dearwind153/article/category/6506891

这哥们的blog很杂,这是语音相关的专栏

http://www.cnblogs.com/JarvanWang/

一个语音识别+Kaldi的blog

https://www.zhihu.com/question/65516424

语音识别kaldi该如何学习?

http://vsooda.github.io/archive/

一个语音识别+DL的blog

https://zhuanlan.zhihu.com/codingmath

一个语音识别的blog

https://blog.csdn.net/jojozhangju

一个Kaldi+声源定位的blog

项目

https://en.wikipedia.org/wiki/List_of_speech_recognition_software

List of speech recognition software

https://mp.weixin.qq.com/s/LsVhMaHrh8JgfpDra6KSPw

横向对比5大开源语音识别工具包

https://github.com/lingochamp/kaldi-ctc

英语流利说开源的kaldi-ctc

https://zhuanlan.zhihu.com/p/23177950

kaldi-ctc: CTC End-to-End ASR

https://mp.weixin.qq.com/s/VkKFQ0fOOHJw0p7Z4EDugQ

绝佳的ASR学习方案:这是一套开源的中文语音识别系统

HTK

Hidden Markov Model Toolkit是Cambridge University开发的语音识别的工具包。它是GMM-HMM时代最为流行的语音识别工具,但近来流行度不如Kaldi。

官网:

http://htk.eng.cam.ac.uk/

HTK Book不仅是使用手册,也是一本介绍原理的书。

http://speech.ee.ntu.edu.tw/homework/DSP_HW2-1/htkbook.pdf

CMU Sphinx

CMU Sphinx是李开复的博士课题项目,后来成为了CMU的长期项目。洪小文、黄学东也先后参与过。该项目比较早的将HMM应用于语音识别,这在当时算是一个重大创新。

李开复,1961年生,Columbia University本科(1983)+CMU博士(1988)。先后供职于Apple、SGI、Microsoft、Google。现为创新工场董事长。

洪小文,1963年生,台湾大学本科+CMU博士。先后供职于Apple、Microsoft,现为微软亚洲研究院院长。

黄学东,1962年生,湖南大学本科(1982)+清华大学硕士(1984)+University of Edinburgh博士(1989)。现为微软首席语音科学家。

Raj Reddy,1937年生,印度裔美国计算机科学家。印度University of Madras本科(1958)+澳大利亚University of New South Wales硕士(1960)+Stanford University博士。CMU教授,首位亚裔图灵奖得主(1994)。 
他还是印度Rajiv Gandhi University of Knowledge Technologies创始人和International Institute of Information Technology, Hyderabad主席。 
他是李开复、洪小文的博士导师,黄学东的博士后导师。

官网:

https://cmusphinx.github.io/

注意:还有一个类似Elasticsearch的文本搜索引擎也叫Sphinx。它的官网是:

http://sphinxsearch.com/

SPTK

The Speech Signal Processing Toolkit是日本的几个科学家开发的语音识别工具库。

官网:

http://sp-tk.sourceforge.net/

Julius

Julius是另一个日本人开发的语音识别工具库。

官网:

http://julius.osdn.jp/en_index.php

HTS

HMM/DNN-based Speech Synthesis System也是日本人开发的工具库,主要用于语音合成。

官网:

http://hts.sp.nitech.ac.jp

Praat

Praat是一款跨平台的多功能语音学专业软件,由University of Amsterdam的Paul Boersma和David Weenink开发。主要用于对数字化的语音信号进行分析、标注、处理及合成等实验,同时生成各种语图和文字报表。

官网:

http://www.fon.hum.uva.nl/praat/

公司

http://www.aispeech.com/

思必驰

http://www.soundai.com/

声智科技。偏重于语音信号处理。

https://zhuanlan.zhihu.com/chenxl

声智科技创始人陈孝良的专栏

数据集

http://www.speech.cs.cmu.edu/databases/an4/

The CMU Audio Databases。这个数据集非常老了(1991年),只有64M。

http://download.tensorflow.org/data/speech_commands_v0.01.tar.gz

TensorFlow提供的Speech Commands Datasets

还有相关的工具:

https://github.com/petewarden/extract_loudest_section

抽取一段wav文件中声音最大的那部分

https://www.kaggle.com/davids1992/speech-representation-and-data-exploration/notebook

包含对Speech Commands Datasets的数据处理过程的blog

https://catalog.ldc.upenn.edu/LDC93S1

TIMIT数据集(收费)

https://mp.weixin.qq.com/s/w9_D1_VVhk9md4RANaipDg

Mozilla开源语音识别模型和世界第二大语音数据集

http://www.voxforge.org/

VoxForge是一个非常活跃的众包语音识别数据库和经过训练的模型库

http://pan.baidu.com/s/1dEhUghz

清华大学语音和语言技术研究中心(CSLT)公开的数据集。这个数据集除了包含thchs30之外,还包含了其他几个小语种的数据集。

http://cn-mirror.openslr.org/18/

单独的thchs30数据集

http://blog.csdn.net/sut_wj/article/details/70662181

THCHS-30:一个免费的中文语料库

http://cn-mirror.openslr.org/33/

AISHELL数据库是THCHS-30之后,目前中文语音数据开源最大的数据库。

它是由北京希尔贝壳科技有限公司(http://www.aishelltech.com)录制的中文普通话数据。由400名来自不同方言区的发音人录制,男女比例均衡。按照设计好的文本,在相对安静环境中使用手机(Android和IOS系统)录制格式为16kHz、16bit单声道数据和高保真麦克风录制格式为44.1kHz、16bit单声道数据同时采集。

http://www.aishelltech.com/aishell_2

AISHELL-2的数据规模达到1000小时和更优秀的系统级recipe。数据目前以硬盘和网盘形式免费开放给高校科研教育机构。商用似乎还是要钱的。



另外本人还开设了个人公众号:JiandaoStudio ,会在公众号内定期发布行业信息,以及各类免费代码、书籍、大师课程资源。

 

                                            

扫码关注本人微信公众号,有惊喜奥!公众号每天定时发送精致文章!回复关键词可获得海量各类编程开发学习资料!

例如:想获得Python入门至精通学习资料,请回复关键词Python即可。

2019-03-08 15:15:03 yj13811596648 阅读数 259

语音识别技术的前世今生之前世
https://blog.csdn.net/Tonywu2018/article/details/84678122

语音识别技术的前世今生之今生
https://blog.csdn.net/Tonywu2018/article/details/84679628

《语音识别的前世今生:GMM+HMM & 深度学习》讲座笔记
https://www.cnblogs.com/lyu0709/p/6929659.html
《语音识别的前世今生》Q&A
https://www.cnblogs.com/lyu0709/p/6929692.html

一篇文带你走进「语音识别技术」的前世今生…
https://www.sohu.com/a/159454845_610522

隐马尔可夫模型(HMM)
https://blog.csdn.net/Tonywu2018/article/details/85119602

期望最大化算法(expectation maximization,EM)
https://blog.csdn.net/Tonywu2018/article/details/85261020

奇异值分解(SVD)
https://blog.csdn.net/Tonywu2018/article/details/83348499

机器学习中的正则化到底是什么意思?
https://blog.csdn.net/T7SFOKzorD1JAYMSFk4/article/details/80997489

2019-03-13 16:36:13 sinat_38682860 阅读数 239

【导读】亚马逊的 Alexa 的巨大成功已经证明:在不远的将来,实现一定程度上的语音支持将成为日常科技的基本要求。整合了语音识别的 Python 程序提供了其他技术无法比拟的交互性和可访问性。最重要的是,在 Python 程序中实现语音识别非常简单。阅读本指南,你就将会了解。你将学到:

语音识别的工作原理;

PyPI 支持哪些软件包;

如何安装和使用 SpeechRecognition 软件包——一个功能全面且易于使用的 Python 语音识别库。
在这里插入图片描述

▌语言识别工作原理概述

语音识别源于 20 世纪 50 年代早期在贝尔实验室所做的研究。早期语音识别系统仅能识别单个讲话者以及只有约十几个单词的词汇量。现代语音识别系统已经取得了很大进步,可以识别多个讲话者,并且拥有识别多种语言的庞大词汇表。

语音识别的首要部分当然是语音。通过麦克风,语音便从物理声音被转换为电信号,然后通过模数转换器转换为数据。一旦被数字化,就可适用若干种模型,将音频转录为文本。

大多数现代语音识别系统都依赖于隐马尔可夫模型(HMM)。其工作原理为:语音信号在非常短的时间尺度上(比如 10 毫秒)可被近似为静止过程,即一个其统计特性不随时间变化的过程。

许多现代语音识别系统会在 HMM 识别之前使用神经网络,通过特征变换和降维的技术来简化语音信号。也可以使用语音活动检测器(VAD)将音频信号减少到可能仅包含语音的部分。

幸运的是,对于 Python 使用者而言,一些语音识别服务可通过 API 在线使用,且其中大部分也提供了 Python SDK。

▌选择 Python 语音识别包

PyPI中有一些现成的语音识别软件包。其中包括:

  • apiai
  • google-cloud-speech
  • pocketsphinx
  • SpeechRcognition
  • watson-developer-cloud
  • wit

一些软件包(如 wit 和 apiai )提供了一些超出基本语音识别的内置功能,如识别讲话者意图的自然语言处理功能。其他软件包,如谷歌云语音,则专注于语音向文本的转换。

其中,SpeechRecognition 就因便于使用脱颖而出。

识别语音需要输入音频,而在 SpeechRecognition 中检索音频输入是非常简单的,它无需构建访问麦克风和从头开始处理音频文件的脚本,只需几分钟即可自动完成检索并运行。

SpeechRecognition 库可满足几种主流语音 API ,因此灵活性极高。其中 Google Web Speech API 支持硬编码到 SpeechRecognition 库中的默认 API 密钥,无需注册就可使用。SpeechRecognition 以其灵活性和易用性成为编写 Python 程序的最佳选择。

▌安装 SpeechRecognation

SpeechRecognition 兼容 Python2.6 , 2.7 和 3.3+,但若在 Python 2 中使用还需要一些额外的安装步骤。本教程中所有开发版本默认 Python 3.3+。

读者可使用 pip 命令从终端安装 SpeechRecognition:

$ pip install SpeechRecognition

安装完成后请打开解释器窗口并输入以下内容来验证安装:

>>> import speech_recognition as sr
>>> sr.__version__
'3.8.1'

注:不要关闭此会话,在后几个步骤中你将要使用它。

若处理现有的音频文件,只需直接调用 SpeechRecognition ,注意具体的用例的一些依赖关系。同时注意,安装 PyAudio 包来获取麦克风输入。

▌识别器类

SpeechRecognition 的核心就是识别器类。

Recognizer API 主要目是识别语音,每个 API 都有多种设置和功能来识别音频源的语音,分别是:

  • recognize_bing(): Microsoft Bing Speech
  • recognize_google(): Google Web Speech API
  • recognize_google_cloud(): Google Cloud Speech - requires installation of the google-cloud-speech package
  • recognize_houndify(): Houndify by SoundHound
  • recognize_ibm(): IBM Speech to Text
  • recognize_sphinx(): CMU Sphinx - requires installing PocketSphinx
  • recognize_wit(): Wit.ai

以上七个中只有 recognition_sphinx()可与CMU Sphinx 引擎脱机工作, 其他六个都需要连接互联网。

SpeechRecognition 附带 Google Web Speech API 的默认 API 密钥,可直接使用它。其他六个 API 都需要使用 API 密钥或用户名/密码组合进行身份验证,因此本文使用了 Web Speech API。

现在开始着手实践,在解释器会话中调用 recognise_google()函数。

>>> r.recognize_google()

屏幕会出现:

Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: recognize_google() missing 1 required positional argument: 'audio_data'

相信你已经猜到了结果,怎么可能从空文件中识别出数据呢?

这 7 个 recognize_*() 识别器类都需要输入 audio_data 参数,且每种识别器的 audio_data 都必须是 SpeechRecognition 的 AudioData 类的实例。

AudioData 实例的创建有两种路径:音频文件或由麦克风录制的音频,先从比较容易上手的音频文件开始。

▌音频文件的使用

首先需要下载音频文件(https://github.com/realpython/python-speech-recognition/tree/master/audio_files),保存到 Python 解释器会话所在的目录中。

AudioFile 类可以通过音频文件的路径进行初始化,并提供用于读取和处理文件内容的上下文管理器界面。

支持文件类型

SpeechRecognition 目前支持的文件类型有:

  • WAV: 必须是 PCM/LPCM 格式
  • AIFF
  • AIFF-C
  • FLAC: 必须是初始 FLAC 格式;OGG-FLAC 格式不可用

若是使用 Linux 系统下的 x-86 ,macOS 或者是 Windows 系统,需要支持 FLAC文件。若在其它系统下运行,需要安装 FLAC 编码器并确保可以访问 flac 命令。

使用 record() 从文件中获取数据

在解释器会话框键入以下命令来处理 “harvard.wav” 文件的内容:

>>> harvard = sr.AudioFile('harvard.wav')
>>> with harvard as source:
...   audio = r.record(source)
...

通过上下文管理器打开文件并读取文件内容,并将数据存储在 AudioFile 实例中,然后通过 record()将整个文件中的数据记录到 AudioData 实例中,可通过检查音频类型来确认:

>>> type(audio)
<class 'speech_recognition.AudioData'>

现在可以调用 recognition_google()来尝试识别音频中的语音。

>>> r.recognize_google(audio)
'the stale smell of old beer lingers it takes heat
to bring out the odor a cold dip restores health and
zest a salt pickle taste fine with ham tacos al
Pastore are my favorite a zestful food is the hot
cross bun'

以上就完成了第一个音频文件的录制。

利用偏移量和持续时间获取音频片段

若只想捕捉文件中部分演讲内容该怎么办?record() 命令中有一个 duration 关键字参数,可使得该命令在指定的秒数后停止记录。

例如,以下内容仅获取文件前四秒内的语音:

>>> with harvard as source:
...   audio = r.record(source, duration=4)
...
>>> r.recognize_google(audio)
'the stale smell of old beer lingers'

在with块中调用record() 命令时,文件流会向前移动。这意味着若先录制四秒钟,再录制四秒钟,则第一个四秒后将返回第二个四秒钟的音频。

>>> with harvard as source:
...   audio1 = r.record(source, duration=4)
...   audio2 = r.record(source, duration=4)
...
>>> r.recognize_google(audio1)
'the stale smell of old beer lingers'
>>> r.recognize_google(audio2)
'it takes heat to bring out the odor a cold dip'

除了指定记录持续时间之外,还可以使用 offset 参数为 record() 命令指定起点,其值表示在开始记录的时间。如:仅获取文件中的第二个短语,可设置 4 秒的偏移量并记录 3 秒的持续时间。

>>> with harvard as source:
...   audio = r.record(source, offset=4, duration=3)
...
>>> recognizer.recognize_google(audio)
'it takes heat to bring out the odor'

在事先知道文件中语音结构的情况下,offset 和 duration 关键字参数对于分割音频文件非常有用。但使用不准确会导致转录不佳。

>>> with harvard as source:
...   audio = r.record(source, offset=4.7, duration=2.8)
...
>>> recognizer.recognize_google(audio)
'Mesquite to bring out the odor Aiko'

本程序从第 4.7 秒开始记录,从而使得词组 “it takes heat to bring out the odor” ,中的 “it t” 没有被记录下来,此时 API 只得到 “akes heat” 这个输入,而与之匹配的是 “Mesquite” 这个结果。

同样的,在获取录音结尾词组 “a cold dip restores health and zest” 时 API 仅仅捕获了 “a co” ,从而被错误匹配为 “Aiko” 。

噪音也是影响翻译准确度的一大元凶。上面的例子中由于音频文件干净从而运行良好,但在现实中,除非事先对音频文件进行处理,否则不可能得到无噪声音频。

噪声对语音识别的影响

噪声在现实世界中确实存在,所有录音都有一定程度的噪声,而未经处理的噪音可能会破坏语音识别应用程序的准确性。

要了解噪声如何影响语音识别,请下载 “jackhammer.wav” (https://github.com/realpython/python-speech-recognition/tree/master/audio_files)文件,并确保将其保存到解释器会话的工作目录中。文件中短语 “the stale smell of old beer lingers” 在是很大钻墙声的背景音中被念出来。

尝试转录此文件时会发生什么?

>>> jackhammer = sr.AudioFile('jackhammer.wav')
>>> with jackhammer as source:
...   audio = r.record(source)
...
>>> r.recognize_google(audio)
'the snail smell of old gear vendors'

那么该如何处理这个问题呢?可以尝试调用 Recognizer 类的adjust_for_ambient_noise()命令。

>>> with jackhammer as source:
...   r.adjust_for_ambient_noise(source)
...   audio = r.record(source)
...
>>> r.recognize_google(audio)
'still smell of old beer vendors'

这样就与准确结果接近多了,但精确度依然存在问题,而且词组开头的 “the” 被丢失了,这是什么原因呢?

因为使用 adjust_for_ambient_noise()命令时,默认将文件流的第一秒识别为音频的噪声级别,因此在使用 record()获取数据前,文件的第一秒已经被消耗了。

可使用duration关键字参数来调整adjust_for_ambient_noise()命令的时间分析范围,该参数单位为秒,默认为 1,现将此值降低到 0.5。

>>> with jackhammer as source:
...   r.adjust_for_ambient_noise(source, duration=0.5)
...   audio = r.record(source)
...
>>> r.recognize_google(audio)
'the snail smell like old Beer Mongers'

现在我们就得到了这句话的 “the”,但现在出现了一些新的问题——有时因为信号太吵,无法消除噪音的影响。

若经常遇到这些问题,则需要对音频进行一些预处理。可以通过音频编辑软件,或将滤镜应用于文件的 Python 包(例如SciPy)中来进行该预处理。处理嘈杂的文件时,可以通过查看实际的 API 响应来提高准确性。大多数 API 返回一个包含多个可能转录的 JSON 字符串,但若不强制要求给出完整响应时,recognition_google()方法始终仅返回最可能的转录字符。

通过把 recognition_google()中 True 参数改成 show_all 来给出完整响应。

>>> r.recognize_google(audio, show_all=True)
{'alternative': [
 {'transcript': 'the snail smell like old Beer Mongers'}, 
 {'transcript': 'the still smell of old beer vendors'}, 
 {'transcript': 'the snail smell like old beer vendors'},
 {'transcript': 'the stale smell of old beer vendors'}, 
 {'transcript': 'the snail smell like old beermongers'}, 
 {'transcript': 'destihl smell of old beer vendors'}, 
 {'transcript': 'the still smell like old beer vendors'}, 
 {'transcript': 'bastille smell of old beer vendors'}, 
 {'transcript': 'the still smell like old beermongers'}, 
 {'transcript': 'the still smell of old beer venders'}, 
 {'transcript': 'the still smelling old beer vendors'}, 
 {'transcript': 'musty smell of old beer vendors'}, 
 {'transcript': 'the still smell of old beer vendor'}
], 'final': True}

可以看到,recognition_google()返回了一个关键字为 ‘alternative’ 的列表,指的是所有可能的响应列表。此响应列表结构会因 API 而异且主要用于对结果进行调试。

▌麦克风的使用

若要使用 SpeechRecognizer 访问麦克风则必须安装 PyAudio 软件包,请关闭当前的解释器窗口,进行以下操作:

安装 PyAudio

安装 PyAudio 的过程会因操作系统而异。

  • Debian Linux

如果使用的是基于 Debian的Linux(如 Ubuntu ),则可使用 apt 安装 PyAudio:

$ sudo apt-get install python-pyaudio python3-pyaudio

安装完成后可能仍需要启用 pip install pyaudio ,尤其是在虚拟情况下运行。

  • macOS

macOS 用户则首先需要使用 Homebrew 来安装 PortAudio,然后调用 pip 命令来安装 PyAudio。

$ brew install portaudio
$ pip install pyaudio

  • Windows

Windows 用户可直接调用 pip 来安装 PyAudio。

$ pip install pyaudio

安装测试

安装了 PyAudio 后可从控制台进行安装测试。

$ python -m speech_recognition

请确保默认麦克风打开并取消静音,若安装正常则应该看到如下所示的内容:

A moment of silence, please...
Set minimum energy threshold to 600.4452854381937
Say something!

请对着麦克风讲话并观察 SpeechRecognition 如何转录你的讲话。

Microphone 类

请打开另一个解释器会话,并创建识一个别器类的例子。

>>> import speech_recognition as sr
>>> r = sr.Recognizer()

此时将使用默认系统麦克风,而不是使用音频文件作为信号源。读者可通过创建一个Microphone 类的实例来访问它。

>>> mic = sr.Microphone()

若系统没有默认麦克风(如在 RaspberryPi 上)或想要使用非默认麦克风,则需要通过提供设备索引来指定要使用的麦克风。读者可通过调用 Microphone 类的list_microphone_names()函数来获取麦克风名称列表。

>>> sr.Microphone.list_microphone_names()
['HDA Intel PCH: ALC272 Analog (hw:0,0)',
 'HDA Intel PCH: HDMI 0 (hw:0,3)',
 'sysdefault',
 'front',
 'surround40',
 'surround51',
 'surround71',
 'hdmi',
 'pulse',
 'dmix', 
 'default']

注意:你的输出可能与上例不同。

list_microphone_names()返回列表中麦克风设备名称的索引。在上面的输出中,如果要使用名为 “front” 的麦克风,该麦克风在列表中索引为 3,则可以创建如下所示的麦克风实例:

>>> # This is just an example; do not run
>>> mic = sr.Microphone(device_index=3)

但大多数情况下需要使用系统默认麦克风。

使用 listen()获取麦克风输入数据

准备好麦克风实例后,读者可以捕获一些输入。

就像 AudioFile 类一样,Microphone 是一个上下文管理器。可以使用 with 块中 Recognizer 类的 listen()方法捕获麦克风的输入。该方法将音频源作为第一个参数,并自动记录来自源的输入,直到检测到静音时自动停止。

>>> with mic as source:
...   audio = r.listen(source)
...

执行 with 块后请尝试在麦克风中说出 “hello” 。请等待解释器再次显示提示,一旦出现 “>>>” 提示返回就可以识别语音。

>>> r.recognize_google(audio)
'hello'

如果没有提示再次返回,可能是因为麦克风收到太多的环境噪音,请使用 Ctrl + C 中断这个过程,从而让解释器再次显示提示。

要处理环境噪声,可调用 Recognizer 类的 adjust_for_ambient_noise()函数,其操作与处理噪音音频文件时一样。由于麦克风输入声音的可预测性不如音频文件,因此任何时间听麦克风输入时都可以使用此过程进行处理。

>>> with mic as source:
...   r.adjust_for_ambient_noise(source)
...   audio = r.listen(source)
...

运行上面的代码后稍等片刻,尝试在麦克风中说 “hello” 。同样,必须等待解释器提示返回后再尝试识别语音。

请记住,adjust_for_ambient_noise()默认分析音频源中1秒钟长的音频。若读者认为此时间太长,可用duration参数来调整。

SpeechRecognition 资料建议 duration 参数不少于0.5秒。某些情况下,你可能会发现,持续时间超过默认的一秒会产生更好的结果。您所需要的最小值取决于麦克风所处的周围环境,不过,这些信息在开发过程中通常是未知的。根据我的经验,一秒钟的默认持续时间对于大多数应用程序已经足够。

处理难以识别的语音

尝试将前面的代码示例输入到解释器中,并在麦克风中输入一些无法理解的噪音。你应该得到这样的结果:

Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/home/david/real_python/speech_recognition_primer/venv/lib/python3.5/site-packages/speech_recognition/__init__.py", line 858, in recognize_google
  if not isinstance(actual_result, dict) or len(actual_result.get("alternative", [])) == 0: raise UnknownValueError()
speech_recognition.UnknownValueError

无法被 API 匹配成文字的音频会引发 UnknownValueError 异常,因此要频繁使用 try 和 except 块来解决此类问题。API 会尽全力去把任何声音转成文字,如短咕噜声可能会被识别为 “How”,咳嗽声、鼓掌声以及舌头咔哒声都可能会被转成文字从而引起异常。

结语:

本教程中,我们一直在识别英语语音,英语是 SpeechRecognition 软件包中每个recognition _ *()方法的默认语言。但是,识别其他语音也是绝对有可能且很容易完成的。要识别不同语言的语音,请将 recognition _ *()方法的语言关键字参数设置为与所需语言对应的字符串。

2018-12-31 21:30:47 weixin_43773093 阅读数 1711

引言

上次公众号刚刚讲过使用 python 播放音频与录音的方法,接下来我将介绍一下简单的语音分类处理流程。简单主要是指,第一:数据量比较小,主要是考虑到数据量大,花费的时间太长。作为演示,我只选取了六个单词作为分类目标,大约 350M 的音频。实际上,整个数据集包含 30 个单词的分类目标,大约 2GB 的音频。第二 :使用的神经网络比较简单,主要是因为分类目标只有 6 个。如果读者有兴趣的话,可以使用更加复杂的神经网络,这样就可以处理更加复杂的分类任务。第三:为了计算机能够更快地处理数据,我并没有选择直接把原始数据‘’喂“给神经网络,而是借助于提取 mfcc 系数的方法,只保留音频的关键信息,减小了运算量,却没有牺牲太大的准确性。

注:本文中涉及 “微信公众号/python高效编程” 的路径都要改成读者保存文件的地址。

简介

传统的语音识别技术,主要在隐马尔可夫模型和高斯混合模型两大”神器“的加持之下,取得了不错的成绩。但是深度学习算法后来者居上,节省了原先耗费在特征提取上的时间,甚至可以直接进行端到端的语音识别任务,大有燎原之势。

今天我们只介绍语音分类任务的简单流程,旨在让读者对语音识别有个初步的认识。本文主要借助 python 的音频处理库 librosa 和非常适合小白使用的深度学习库 keras。通过调用他们的 api ,我们可以快速地实现语音分类任务。

加载标签

首先大家要把从公众号下载来的音频文件保存在一个固定的文件夹中,比如取名为“audio”。我们通过函数os.listdir,获取“audio”文件夹中所有的音频的类别,比如 “bed”,“bird”,“cat” 等等类别。这些标签就是我们需要分类的目标。

# 加载标签
label_path = '微信公众号/python高效编程/audio'
def load_label(label_path):
    label = os.listdir(label_path)
    return label

提取 mfcc 系数

mfcc 系数,全称“Mel Frequency Cepstrum Coefficient”,音译为:梅尔频率倒谱系数,是模仿人类听觉特性而提取的特征参数,主要用于特征提取和降维处理。就像主成分分析方法(PCA),可以将高维度的数据压缩到低维,从而起到减小计算量以及过滤噪声的目的。拿我们这次的音频为例,我们选取了 5000 多个采样点 ,经过提取 mfcc 系数,得到 20 * 11 的矩阵,大大减小了计算量。

首先,第一个函数 librosa.load用于读取音频文件,path 为音频路径,sr 为采样率(也就是一秒钟采样点的个数),设置为None,就按音频本身的采样率进行读取。mono 为双声道,我们读取的音频都是单声道的,所以也要设置为 None。其次,我们并不需要这么高的采样率,所以就每三个选取一个采样点,y=y[::3]

如何提取 mfcc 参数呢?

传统的语音识别预处理,要经过 分帧>>加窗>>快速傅里叶变换 等一系列操作,才能提取 mfcc 参数。但是呢,我们可以调用 librosa.feature.mfcc方法,快速提取 mfcc 系数,毕竟我们只是简单地熟悉下语音处理的流程。这里要注意的是,由于我们拿到的音频文件,持续时间都不尽相同,所以提取到的 mfcc 大小是不相同的。但是神经网络要求待处理的矩阵大小要相同,所以这里我们用到了铺平操作。我们 mfcc 系数默认提取 20 帧,对于每一帧来说,如果帧长小于 11,我们就用 0 填满不满足要求的帧;如果帧长大于 11,我们就只选取前 11 个参数。我们用到的函数numpy.pad(array, pad_width, mode),其中 array 是我们需要填充的矩阵,pad_width是各个维度上首尾填充的个数。举个例子,假定我们设置的 pad_width 是((0,0), (0,2)),而待处理的 mfcc 系数是 20 * 11 的矩阵。我们把 mfcc 系数看成 20 行 11 列的矩阵,进行 pad 操作,第一个(0,0)对行进行操作,表示每一行最前面和最后面增加的数个数为零,也就相当于总共增加了 0 列。第二个(0,2)对列操作,表示每一列最前面增加的数为 0 个,但最后面要增加两个数,也就相当于总共增加了 2 行。mode 设置为 ‘constant’,表明填充的是常数,且默认为 0 。

这样,我们就成功提取了一个音频文件的 mfcc 参数。

# 提取 mfcc 参数
# 更多精彩,关注微信公众号:python高效编程
# train: (6980, 20, 11)   (6980,)
# test:  (4654, 20, 11)	  (4654,)
def wav2mfcc(path, max_pad_size=11):
    y, sr = librosa.load(path=path, sr=None, mono=False)
    y = y[::3]
    # 默认提取 20 帧
    audio_mac = librosa.feature.mfcc(y=y, sr=16000)
    y_shape = audio_mac.shape[1]
    if y_shape < max_pad_size:
        pad_size = max_pad_size - y_shape
        audio_mac = np.pad(audio_mac, ((0, 0), (0, pad_size)), mode='constant')
    else:
        audio_mac = audio_mac[:, :max_pad_size]
    return audio_mac

我们首先要建立两个列表,分别用来存储 mfcc 系数和相应的标签(也就是 bed,bird 等)。然后每提取到一个 mfcc 参数就把它添加到 mfcc_vectors 中,并且在 target 中存储它的标签名。当我们把六个文件夹所有的音频文件 全部处理完毕后,我们要把数据存储用 npy(numpy 矩阵的存储格式) 格式存储起来。读者可能会疑问,为什么要保存起来,我一下子做完整个流程,不就可以了吗?一方面,我们并不一定可以一次性地完成所有的操作,保存阶段性成果是很有必要的,省得我们下次又要从头开始执行程序。特别是这种需要处理大量数据的情况,会造成不必要的时间浪费。另一方面,即使我们可以一次完成所有的操作,得到我们想要的结果。但万一,下次有朋友请教你这方面的问题,你又要从头演示给他看,不划算啊。

保存数据之后,我们就得到了所有音频的 mfcc 系数,以及对应的标签。

# 存储处理过的数据,方便下一次的使用
# 更多精彩,关注微信公众号:python高效编程
def save_data_to_array(label_path, max_pad_size=11):
    mfcc_vectors = []
    target = []
    labels = load_label(label_path=label_path)
    for i, label in enumerate(labels):
        path = label_path + '/' + label
        wavfiles = [path + '/' + file for file in os.listdir(path)]
        for wavfile in wavfiles:
            wav = wav2mfcc(wavfile, max_pad_size=max_pad_size)
            mfcc_vectors.append(wav)
            target.append(i)
    np.save(DATA, mfcc_vectors)
    np.save(TARGET, target)

简单的神经网络

接着我们要为神经网络准备食物了。

我们借助 sklearn 中的train_test_split,把数据集分为训练集和验证集。其中训练集占 6 成,测试集占 4 成。随机状态为 42,随机状态设置为 42 是为了方便优化,如果每次随机结果都不相同的话,那么就没有可比性了。shuffle 是指随机打乱数据集,以获得无序的数据集。

# 获取训练集与测试集
# 更多精彩,关注微信公众号:python高效编程
def get_train_test(split_ratio=.6, random_state=42):
    # 加载保存的 mfcc 系数以及对应的标签
    X = np.load(DATA)
    y = np.load(TARGET)
    assert X.shape[0] == y.shape[0]
    # 把数据集分为训练集和验证集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=(1 - split_ratio), random_state=random_state,shuffle=True)
    return X_train, X_test, y_train, y_test

最后,就到了神经网络大显身手的时候了。

首先,我们要改变 mfcc 系数的 shape,使它变成二维矩阵且第二个维度大小为 220。其次,我们要用到 keras 的One-hot 编码。举个例子,原先的标签为‘bed’,‘bird’,‘cat’,经过编码,凡是对应标签,就编码成 1,反之编码成 0。下图为示例:左边为原矩阵,右边为编码后的矩阵。
在这里插入图片描述

接着,我们就可以向搭建乐高积木一样,搭建我们简单的神经网络模型了。

首先我们选择 keras 的 Sequential 模型 ,也就是序列模型,这是一个线性的层次堆栈。model.add 表示向堆栈中增加一层网络,Dense 的第一个参数是每层网络的节点的个数,也就是输出矩阵第二个维度的大小。假如输入矩阵大小为 5743 * 220,设定节点个数为 64,那么输出的矩阵的大小为 5743 * 64。第二个参数是激活函数的类型。

其中 relu 函数定义如下:

def naive_relu(x):
	if x > 0:
		return x
   	else:
        return 0

在这里插入图片描述

numpy 中有个函数 numpy.maximum(x, 0),也是类似的功能。

对于多元分类问题,最后一层常用 softmax 函数,节点数为 6,表明返回这六个标签的可能性。

# softmax funtion
def softmax(x):
    a = np.max(x)
    c = x - a
    exp_c = np.exp(c)
    sum_c = np.sum(exp_c)
    return exp_c / sum_c
# target是x对应的标签
x = ([0.5,0.6,2.9])
target = (['bird','bed','cat'])
y = softmax(x)
print(y)
# output:[0.08 0.08 0.84] 和为1
# 标签为'bird'的可能性为:0.08
# 标签为'bed'的可能性为:0.08
# 标签为'cat'的可能性为:0.84
# 即 softmax 函数输出三种类别的可能性

接着编译模型,即 model.compile。其中,损失函数使用的是多元分类交叉熵函数,优化器使用 RMSprop,是随机梯度下降法的加强版。metrics 选择 accuracy。

最后我们就可以拟合数据了。我们可以看到,随着 epoch 的增加,训练集的准确度基本上是不断增加的,而测试集很快就收敛了,甚至还会下降。蓝线是训练集的准确性,我跑 20 轮的结果是:0.9381。橘色的线是测试集的准确性,我跑 20 轮的结果是: 0.7823。

在这里插入图片描述

def main():
    # 更多精彩,关注微信公众号:python高效编程
    x_train, x_test, y_train, y_test = get_train_test()
    x_train = x_train.reshape(-1, 220)
    x_test = x_test.reshape(-1, 220)
    y_train_hot = to_categorical(y_train)
    y_test_hot = to_categorical(y_test)
    model = Sequential()
    model.add(Dense(64, activation='relu', input_shape=(220,)))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(6, activation='softmax'))

    model.compile(loss=keras.losses.categorical_crossentropy,
                  optimizer=keras.optimizers.RMSprop(),
                  metrics=['accuracy'])
    history = model.fit(x_train, y_train_hot, batch_size=100, epochs=20, verbose=1,
                        validation_data=(x_test, y_test_hot))
    # plot_history(history)

以上便是本节教程的全部内容了,如果想要获取音频文件和源码,请关注微信公众号:python高效编程,并在在微信后台回复音频

语音识别技术构架

阅读数 878

语音识别基本流程

阅读数 895

语音识别入门介绍

阅读数 724

没有更多推荐了,返回首页