2014-05-19 10:37:22 u012809352 阅读数 2015
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    54 人正在学习 去看看 马龙

首先导入科大讯飞工具包

MainActivity源代码:

package com.example.viocedemo;


import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;


import com.iflytek.cloud.speech.RecognizerResult;
import com.iflytek.cloud.speech.SpeechConstant;
import com.iflytek.cloud.speech.SpeechError;
import com.iflytek.cloud.speech.SpeechListener;
import com.iflytek.cloud.speech.SpeechSynthesizer;
import com.iflytek.cloud.speech.SpeechUser;
import com.iflytek.cloud.speech.SynthesizerListener;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;


public class MainActivity extends Activity implements OnClickListener,
SynthesizerListener {
private EditText editText;
private Button button1;
private Button button2;
// 合成对象
private SpeechSynthesizer speechSynthesizer;
// 识别窗口
private RecognizerDialog recognizerDialog;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


// appid换成自己申请的
SpeechUser.getUser().login(MainActivity.this, null, null,
"appid=12345678", listener);


init();
setParam();


}


/**
* 初始化UI
*/
public void init() {
editText = (EditText) findViewById(R.id.editText1);
button1 = (Button) findViewById(R.id.button1);
button2 = (Button) findViewById(R.id.button2);
button1.setOnClickListener(this);
button2.setOnClickListener(this);
}


/**
* 初始化语音类别
*/
public void setParam() {
speechSynthesizer = SpeechSynthesizer.createSynthesizer(this);
speechSynthesizer.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
speechSynthesizer.setParameter(SpeechConstant.SPEED, "50");
speechSynthesizer.setParameter(SpeechConstant.VOLUME, "50");
speechSynthesizer.setParameter(SpeechConstant.PITCH, "50");
}


/**
* 识别语音的弹出框
*/
public void setDialog() {
recognizerDialog = new RecognizerDialog(this);
recognizerDialog.setParameter(SpeechConstant.DOMAIN, "iat");
recognizerDialog.setParameter(SpeechConstant.SAMPLE_RATE, "16000");
editText.setText(null);
// 显示Dialog
recognizerDialog.setListener(dialogListener);
recognizerDialog.show();
}


/**
* 识别回调监听器
*/
private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
// 识别结果回调
@Override
public void onResult(RecognizerResult arg0, boolean arg1) {
// TODO Auto-generated method stub
String text = JsonParser.parseIatResult(arg0.getResultString());
editText.append(text);
editText.setSelection(editText.length());
}


// 识别结束回调
@Override
public void onError(SpeechError arg0) {
// TODO Auto-generated method stub


}
};


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}


@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button1:// 语音播放按钮
String text = editText.getText().toString();
speechSynthesizer.startSpeaking(text, this);
break;
case R.id.button2:// 语音识别
setDialog();
break;
default:
break;
}
}


// 缓冲进度回调通知
@Override
public void onBufferProgress(int arg0, int arg1, int arg2, String arg3) {
// TODO Auto-generated method stub


}


// 结束回调
@Override
public void onCompleted(SpeechError arg0) {
// TODO Auto-generated method stub

}


// 开始播放
@Override
public void onSpeakBegin() {
// TODO Auto-generated method stub
}


// 暂停播放
@Override
public void onSpeakPaused() {
// TODO Auto-generated method stub

}


// 播放进度
@Override
public void onSpeakProgress(int arg0, int arg1, int arg2) {
// TODO Auto-generated method stub

}


// 继续播放
@Override
public void onSpeakResumed() {
// TODO Auto-generated method stub

}


/**
* 通用回调接口
*/
private SpeechListener listener = new SpeechListener() {


// 消息回调
@Override
public void onEvent(int arg0, Bundle arg1) {
// TODO Auto-generated method stub


}


// 数据回调
@Override
public void onData(byte[] arg0) {
// TODO Auto-generated method stub


}


// 结束回调(没有错误)
@Override
public void onCompleted(SpeechError arg0) {
// TODO Auto-generated method stub

}
};


}


JsonParser类源代码:

package com.example.viocedemo;


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


import android.text.TextUtils;


//import com.iflytek.speech.ErrorCode;
//import com.iflytek.speech.SpeechError;
/**
 * 对云端返回的Json结果进行解析
 * @author iFlytek
 * @since 20131211
 */
public class JsonParser {

/**
* 听写结果的Json格式解析
* @param json
* @return
*/
public static String parseIatResult(String json) {
if(TextUtils.isEmpty(json))
return "";

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");
JSONObject obj = items.getJSONObject(0);
ret.append(obj.getString("w"));
}
} catch (Exception e) {
e.printStackTrace();
} 
return ret.toString();
}

}


UI布局:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >


    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="250dip"
        android:gravity="top"
        android:ems="10" >


        <requestFocus />
    </EditText>


    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="语音播放" />


    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="语音听写" />


</LinearLayout>

源代码下载

语音
2017-06-23 10:19:40 wuxianfeng1987 阅读数 3029
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    54 人正在学习 去看看 马龙

这也引出了声纹识别,同时也是传统的语音识别框架下的一个很合理的假设:将语音拆分到音素(phone)的级别,狭义的现代汉语只需要32个音素就已经足够用了。

如果考虑到每个音素的形态还会受到前后音素的影响,构建三音素模型(tri-phone)的话,那至多也只有几千个备选的三音素集合(不是简单的32的三次方,我们也会去掉一些稀有的和根本不会出现的搭配),而不同说话人的三音素样本尽管有明显的差异,但都能在空间中的某个区域内聚类。由语音和对应的声学特征的这些性质启发,1995年DA Reynolds首次将混合高斯模型(Gaussian Mixture Model,GMM)成功地应用于文本无关的声纹识别任务,至此之后的20多年,奠定了GMM在声纹识别中地基的地位,后续声纹的发展演进都是以GMM作为基础进行改进和拓展的。

 

实际应用中,从用户体验和成本的角度上考虑,针对目标用户可采集到的语料是极其有限的(按照学术上的定义,实际可用的语音是稀疏(sparse)),如何在有限的数据中完成稳定的建模与识别。

对于同一个用户,即便采集到的两段语音内容都是相同的,但由于情绪、语速、疲劳程度等原因,语音都会有一些差异性。如何补偿这种说话人自身语音的差异性

声音是通过录音设备进行采集的,不同的型号的录音设备对语音都会造成一定程度上的畸变,同时由于背景环境和传输信道等的差异,对语音信息也会造成不同程度的损伤,一般在研究中将这些外界影响语音的效应称为信道易变性(Channel Variability)

 

有研究表明,当GMM中高斯分量的数量足够多的时候,GMM可以模拟任意的概率分布。

主流企业

科大讯飞

出门问问

云知声

思必驰

百度(度秘)

腾讯小鲸(消息较少)

Google(Google Now)

微软(小冰、小娜Cortana)

苹果(Siri)

开源语音识别软件

HTK

李开复团队实现,老牌的基于HMM的语音识别工具箱

Juicer

Julius

Kaldi

据说是目前最好的开源工具箱,而且可以商业化,有用到DNN

Alize

说话人识别框架

http://mistral.univ-avignon.fr/mediawiki/index.php/Main_Page

http://alize.univ-avignon.fr/

https://github.com/ibillxia/VoicePrintReco/tree/master/Demo

https://www.researchgate.net/publication/242655641_ALIZESpkDet_a_state-of-the-art_open_source_software_for_speaker_recognition

http://dblp.l3s.de/d2r/directory/Publications

目前主流的方法

DNN-ivector

基于端到端深度学习的说话人信息提取

声纹识别主流方法

gmm-ubm

i-vevtor-plda

deep feature

bottleneck feature

d-vector

声纹特征的不同的原因

特征主要由两个因素决定,第一个是 声腔的尺寸 ,具体包括咽喉、鼻腔和口腔等,这些器官的形状、尺寸和位置决定了声带张力的大小和声音频率的范围。因此不同的人虽然说同样的话,但是声音的频率分布是不同的,听起来有的低沉有的洪亮。每个人的发声腔都是不同的,就像指纹一样,每个人的声音也就有独特的特征。

第二个决定声音特征的因素是 发声器官被操纵的方式 ,发声器官包括唇、齿、舌、软腭及腭肌肉等,他们之间相互作用就会产生清晰的语音。而他们之间的协作方式是人通过后天与周围人的交流中随机学习到的。人在学习说话的过程中,通过模拟周围不同人的说话方式,就会逐渐形成自己的声纹特征

 

文本相关及文本无关的声纹识别

从语音字典是否受限的角度上来区分声纹文本无关识别和文本相关识别,文本无关就是说系统对于输入的语音的内容并不做限制,识别系统应当可以克服语音中字典信息的多变性和差异性,对语音背后的身份做出准确判断;而文本相关识别,意思是我们预先会限制语音的字典集合的规模。

如下图是说话人A,B分别说“四”和“九”的声谱图

A对应“四”的语音波形          B对应“四”的语音波形      A对应“九”的语音波形

A说“四”和“九”的声谱图看上去比A说“四”B说“四”的差异还大。

语音特性

短时平稳特性

10--30ms内可以认为语音信号近似不变

声音的基本概念

音素

语音中最小的基本单位是音素,音素是人类能区别一个单词和另一个单词的基础。音素构成音节,音节又构成不同的词和短语。音素又分为元音和辅音。狭义的现代汉语只需要32个音素就已经足够用了。如果考虑到每个音素的形态还会受到前后音素的影响,构建三音素模型(tri-phone)[a1] 的话,那至多也只有几千个备选的三音素集合(不是简单的32的三次方,我们也会去掉一些稀有的和根本不会出现的搭配)

元音,辅音

元音,又称母音,是音素的一种,与辅音相对。元音是在发音过程中由气流通过口腔而不受阻碍发出的音。不同的元音是由口腔不同的形状造成的。(元音和共振峰关系密切)

辅音,气流在口腔或咽头受到阻碍而形成的音叫做辅音,又叫子音。不同的辅音是由发音部位和发音方法的不同造成的。

清音,浊音

清音:清音和浊音的概念在文献中涉及较多。严格来讲,很多特征的提取都需要区分清音和浊音。当气流通过声门时,如果声道中某处面积很小,气流高速冲过此处时产生湍流,当气流速度与横截面积之比大于某个临界速度便产生摩擦音,即清音。简单来说,发清音时声带不振动,因此清音没有周期性。清音由空气摩擦产生,在分析研究时等效为噪声。

浊音:语音学中,将发音时声带振动的产生音称为浊音。辅音有清有浊,而多数语言中的元音均为浊音。浊音具有周期性。

发清音时声带完全舒展,发浊音时声带紧绷在气流作用下作周期性动作

响度,音高,音色

响度,音高,音色都是一种主观心理量

目前自己的理解:

不同频率上的能量分布决定了音色

不同的频率高低决定了音调

不同的振幅决定了响度

声谱图

语音信号——短时傅里叶变换à频率和能量(幅度)——幅度映射到一个灰度级表示à形成声谱图


共振峰formants

共振峰是指在声音的频谱中能量相对集中的一些区域,共振峰不但是音质的决定因素,而且反映了声道(共振腔)的物理特征。声音在经过共振腔时,受到腔体的滤波作用,使得频域中不同频率的能量重新分配,一部分因为共振腔的共振作用得到强化,另一部分则受到衰减。由于能量分布不均匀,强的部分犹如山峰一般,故而称之为共振峰。在语音声学中,共振峰决定着元音的音质。

共振峰是表征语音信号特征的基本参数之一。它在语音信号合成、语音识别和语音编码等方面起着重要作用。共振峰可以等效为声道系统函数的复极点对,由于人的声道平均长度为17cm,而语音信号的能量主要集中在0-5kHz。因此语音通常包含4到5个稳定的共振峰,一般只需要研究前三个共振峰。

共振峰的一个示意图

从图中可以看到:1语音信号的能量在频率上存在频谱倾斜;2共振峰位置与谱包络位置很一致(这也是谱包络法提取共振峰的原理)


普通话10个元音共振峰均值数据表(Hz)

基音频谱及基音周期

声带振动的频率称为基音频率,相应的周期就成为基音周期。

语音信号分为清音跟浊音,浊音又称有声语言,携带者语言中大部分的能量,浊音在时域上呈现出明显的周期性;而清音类似于白噪声,没有明显的周期性。发浊音时,气流通过声门使声带产生张弛震荡式振动,产生准周期的激励脉冲串。这种声带振动的频率称为基音频率,相应的周期就成为基音周期。基音周期信息在语音识别、说话人识别、语音分析与语音合成,以及低码率语音编码、发音系统疾病诊断、听觉残障者的语言指导等多个领域有着广泛的应用。(因为女性基频比男性高,所以有些算法中使用基频来区分性别,还挺准的)

频谱的包络

基音,泛音

一般的声音都是由发音体发出的一系列频率、振幅各不相同的振动复合而成的。这些振动中有一个频率最低的振动,由它发出的音就是基音(fundamental tone),其余为泛音

基音帧的能量

基音共振峰的出现频率

基音共振峰的轨迹

音频采样率

音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然。在当今的主流采集卡上,采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级,22.05KHz只能达到FM广播的声音品质,44.1KHz则是理论上的CD音质界限,48KHz则更加精确一些,关于大小的计算:


实际上大小是26460552字节,:

44100(每秒采样率)*4(左右声道,每个声道两字节)*150(音频长为150)

Nyquist奈奎斯特定理

任何模拟信号由各种不同频率的成份所组成。最简单的情形是正弦波,其中所有的信号能量在某一频率集中。实际上,模拟信号通常有复杂的波形,有许多频率的成分。模拟信号的最高频率成份决定那个信号的带宽。如果所有的其他因素不变,则频率愈高,带宽也愈大。

假设给定的模拟信号的最高的频率成份(用赫兹表示)是 fmax。依照 Nyquist 定理,取样率必须至少是 2 fmax,或最高的模拟频率成份的两倍如果那个取样率少于 2 fmax,模拟输入信号的一些最高的频率成份将不能在数字输出中正确表现。当一个这样的数字信号被数模转换器转换回模拟形式的时候,错误的频率成份看起来不是最初的模拟信号中的频率成分。这种不合需要的情况是失真的一种形式叫做混淆现象。

如果采样率为44100 = 2*fmax,则可以得到fmax = 44100/2;

MFCC 梅尔频率倒谱系数

人的发声系统大致如下:


对应的语音信号产生的时域模型如下:

气流通过声道摩擦形成清音,类似于噪声,声带开关闭合震动形成浊音,我们听到的声音是清音和浊音的混合,而浊音因为不同人的声带,发音方式,声道共振相异而不一,其周期,通过声道形成的共振锋,轨迹等是语音识别,声纹识别的重要特征,频率倒谱系数是将浊音和清音频率分开,并进一步分析共振峰,轨迹,周期的一种方法。

频率倒谱系数

       清音+浊音在时域表现为“+”,在频率表现为“*”,在时域我们能观测到的是清音+浊音的结果,无法将他们分离,因此需要转换到频域,即所谓的声谱图。下面是一段音频采样的频谱。


这里会与图像不同,图像系统的噪点很大程度是与有效信息并存,并可观测到,影响我们对图像内容的判断,这种噪声很多在高频内,因此在频域用低通滤波就可以去除噪点(同时边缘等有用的高频信息也会受影响)


语音由于清音(噪声)与浊音混合在一起,需要更进一步的处理,如果原始信号为 ,浊音为 ,噪声为 有:


频率取log后,叫做倒谱,英文Cepstrum(频谱spectrum的前面四个字母顺序倒过来就是倒谱了),进一步,我们将频率再看成时域,经过傅里叶变换得到倒谱的频域(伪频域pseudo-frequency),将伪频域的低频和高频分离再做IFFT逆傅里叶变换,即可得到分离的 。

梅尔频率转换

对于人类听觉感知的实验表明,人类听觉的感知只聚焦在某些特定的区域,而不是整个频谱包络。

而Mel频率分析就是基于人类听觉感知实验的。实验观测发现人耳就像一个滤波器组一样,它只关注某些特定的频率分量(人的听觉对频率是有选择性的)。也就说,它只让某些频率的信号通过,而压根就直接无视它不想感知的某些频率信号。但是这些滤波器在频率坐标轴上却不是统一分布的,在低频区域有很多的滤波器,他们分布比较密集,但在高频区域,滤波器的数目就变得比较少,分布很稀疏。

       人的听觉系统是一个特殊的非线性系统,它响应不同频率信号的灵敏度是不同的。在语音特征的提取上,人类听觉系统做得非常好,它不仅能提取出语义信息, 而且能提取出说话人的个人特征,这些都是现有的语音识别系统所望尘莫及的。如果在语音识别系统中能模拟人类听觉感知处理特点,就有可能提高语音的识别率。

梅尔频率倒谱系数(Mel Frequency CepstrumCoefficient, MFCC)考虑到了人类的听觉特征,先将线性频谱映射到基于听觉感知的Mel非线性频谱中,然后转换到倒谱上。

将普通频率转化到Mel频率的公式是:2595*log10(1+f/700)

由下图可以看到,它可以将不统一的频率转化为统一的频率,也就是统一的滤波器组。

在Mel频域内,人对音调的感知度为线性关系。举例来说,如果两段语音的Mel频率相差两倍,则人耳听起来两者的音调也相差两倍。

 

梅尔频率滤波原理(三角帶通濾波器)计算过程:

三角帶通濾波器在「梅爾頻率」(Mel Frequency)上是平均分佈的,先将频率转到梅尔频率,然后按滤波器组个数平均分布得到每个滤波器的两端的梅尔频率,再转换到普通频率域计算每个滤波器的端点频率。

三角帶通濾波器有兩個主要目的: 
對頻譜進行平滑化,並消除諧波的作用,突顯原先語音的共振峰。(因此一段語音的音調或音高,是不會呈現在 MFCC 參數內,換句話說,以 MFCC 為特徵的語音辨識系統,並不會受到輸入語音的音調不同而有所影響。

http://neural.cs.nthu.edu.tw/jang/books/audiosignalprocessing/speechFeatureMfcc_chinese.asp?title=12-2%20MFCC

梅尔频率倒谱系数计算流程

1)先对语音进行预加重、分帧和加窗;

2)对每一个短时分析窗,通过FFT得到对应的频谱;

3)将上面的频谱通过Mel滤波器组得到Mel频谱;

4)在Mel频谱上面进行倒谱分析(取对数,做逆变换,实际逆变换一般是通过DCT离散余弦变换来实现,取DCT后的第2个到第13个系数作为MFCC系数),获得Mel频率倒谱系数MFCC,这个MFCC就是这帧语音的特征;

这时候,语音就可以通过一系列的倒谱向量来描述了,每个向量就是每帧的MFCC特征向量。

DTW动态时间规整

语音信号具有相当大的随机性,即使同一个人在不同时刻发同一个音,也不可能具有完全的时间长度,大部分情况下,两个序列整体上具有非常相似的形状,但是这些形状在x轴上并不是对齐的。

所以我们在比较他们的相似度之前,需要将其中一个(或者两个)序列在时间轴下warping扭曲,以达到更好的对齐。而DTW就是实现这种warping扭曲的一种有效方法。DTW通过把时间序列进行延伸和缩短,来计算两个时间序列性之间的相似性。动态时间规整DTW是一个典型的优化问题,其使用满足一定条件的时间规整函数W(n)描述测试模板和参考模板的时间对应关系,求解两模板匹配时累计距离最小所对应的规整函数。DTW的约束条件为:

1.边界条件:任何一种语音的发音快慢都有可能变化,但是其各部分的先后次序不可能改变,因此所选的路径必定是从左下角出发,在右上角结束;

2.连续性:一个特征点不可能跨过某个点去匹配,只能和自己相邻的点对齐;

3.单调性:点必须是随着时间单调进行的。

DTW过程大致如下:

设有两个时间序列:

Q =q1,q2,…,qi,…,qn;

C =c1,c2,…,cj,…,cm;

为了对齐这两个序列,DTW需要构造一个n x m的矩阵网格,其中n,m为两个序列的长度,矩阵元素(i, j)表示序列Q的每一个点和序列C的每一个点之间的相似度,距离越小则相似度越高,一般采用欧式距离表示。每一个矩阵元素(i, j)表示点qi和cj的对齐。DP算法可以归结为在约束条件下寻找一条通过此网格中若干格点的路径,路径通过的格点即为两个序列进行计算的对齐的点。

 

i-vector特征(Identity Vector)

理解I-vector需要先理解:

1、  Total-Variability Modeling全变异模型

2、  JFA算法

3、  信号的子空间表示

 

信道补偿-LDA、PLDA、WCCN、NAP

线性鉴别分析LDA

信道补偿算法有很多,先说一下LDA。关于LDA的资料很多,这里简单说一下为什么LDA能够用在说话人识别而且是如何进行信道补偿的。

首先,LDA至多可生成C-1维子空间(C为分类类别数),LDA降维后的度区间在 [1,C-1] ,与原始特征数n无关,因此二分类问题至多可以降维到一维,导致有些线性不可分数据分布情况无法分类。在说话人识别中,大部分情况是二分类问题,所以这里LDA的作用是将原始高维特征数据降维到一维,这样每一个样本特征向量映射为一维上的一个点,这个点的数值表示该点到原点的距离。

当一个说话人有很多语音时,表现为这些语音在说话人空间中聚集为一簇。如果这些语音收到信道的影响,那么就表现为这个说话人的语音的方差很大。然后,LDA尝试着找到一个新的方向,将原来的所有数据投影到这个方向,使得在这个方向中同一说话人的数据具有最小的类内方差,同时不同说话人之间的距离尽量大。这样,就达到减小信道差异的影响了。

LDA其实也是一种降维方法。它尽量去移除不需要的方向,最小化类内的方差信息量。也就是,LDA寻找一个新的方向去更好地对不同的类做出分类。可见,LDA非常适合作为说话人识别系统的信道补偿算法。

当使用LDA对测试数据和模型的I-vector进行重新投影后,然后计算它们之间的cosine距离,就可以作为最后的得分。

最后简单说一下,LDA如何找到这个映射方向。前面不是说LDA的目的是最大化类间距离,最小化类内距离么。我们很容易认为只要定义一个目标函数然后去用一般的最优化方法去求近似解就好了。其实,不用这么麻烦的,LDA是可以直接求取解析解的。具体方法就是对目标函数求导并且令导数为零。但是在求导之前,记得对分母进行归一化,因为如果不归一化的话,映射方向扩大任意倍等成立,所以不好确定映射方向的具体值。

OK,求解析解的过程中,经过一系列变化,我们最终只要求出原始样本的均值和方差就可以最佳映射方向 w,这就是Fisher 于 1936年提出的线性判别分析。

WCCN 类内方差归一化

预处理---分帧,加抖动,去均值,预加重,加窗

分帧

因为短时平稳特性,所以进行分帧

加抖动

随机加高斯噪声,这样处理后,训练出的模型抗干扰性要强点

去均值

一定程度的解决音量归一化问题

预加重

语音信号的预加重,目的是为了对语音的高频部分进行加重,去除口唇辐射的影响,增加语音的高频分辨率。一般通过传递函数为 一阶FIR高通滤波器来实现预加重,其中a为预加重系数,0.9<a<1.0。设n时刻的语音采样值为x(n),经过预加重处理后的结果为y(n)=x(n)-ax(n-1),这里取a=0.98。

加窗

将语音信号分为一些较短片段来进行处理,这就是分帧,通常使用加窗来实现。分帧是避免在傅里叶变换的过程中损失时域信息,因为如果对整个音频信息进行傅里叶变换的话,时域信息是没有保存的,小波也是使用类似的方法保存时域信息。另外就是语音固有的短时平稳特性,即语音信号一般在10ms到30ms之间,我们可以把它看成是平稳的明智的解决办法就是每次取一段数据,进行分析,然后再取下一段数据,再进行分析。

窗口函数有:

1.矩形窗

2.三角窗

3.Hanning窗

4.Hamming窗

5.Blackman窗

6.Kaiser窗

等,语音常用的是Hamming窗,函数如下:


在傅里叶变换的过程中,因为是假设处理的数据在时间维度上是周期信号,这一假设实际上是引入了误差,和频率泄露,窗口函数一定程度的减少了这种情况,窗口函数的选择主要取决于窗口函数频谱的主瓣宽度和旁瓣衰减强度。

基音包络计算

 

声纹识别的两种模式

说话人确认和说话人辨认

 

疑问

1、左右声道对识别的影响

2、20ms的分帧,这20ms对应说话内容的什么呢?

我们说话是连续的,20ms是构成说话内容的音素。例如说“我们”,时间上在时间维度上是【我-我-我-们-们…】

3、短时平稳特性更进一步的理解

4、GMM的训练流程

5、8k语音文件训练可能会影响结果

2016-02-24 16:03:51 u013892356 阅读数 2946
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    54 人正在学习 去看看 马龙

以在线命令词识别为例:

主要步骤、易产生的错误其解决办法如下:

1、下载SDK,解压;

2、在ROS工作空间下创建一个Package;

   catkin_create_pkg xfei_asr roscpp rospy std_msgs
3、将SDK文件夹中的头文件,即下载的SDK文件夹中include文件夹下的 .h文件拷贝至xfei_asr/include/下;

4、将SDK文件夹中sample/asr_sample中的asr_sample.c文件拷贝至xfei_asr/src/下;

5、将SDK文件夹中libs/x64/libmsc.so文件拷贝至xfei_asr/lib/下;

6、打开asr_sample.c文件,将#include "qish.h"等修改为:

    #include "xfei_asr/qisr.h"
    #include "xfei_asr/msp_cmn.h"
    #include "xfei_asr/msp_errors.h"

7、打开CMakelist,

    1)修改include_directories为:即包括拷贝的头文件所在的include目录

         include_directories(
                                     ${catkin_INCLUDE_DIRS}
                                     include
                                     )

    2)添加:

        add_executable(asr_sample src/asr_sample.c) # 生成可执行文件
        target_link_libraries(asr_sample ${catkin_LIBRARIES} /home/luyh/catkin_ws02/src/xfei_asr/lib/libmsc.so -ldl -pthread) # 添加链接库,其中动态链接库路径按实际修改

2016-11-25 09:25:18 sparkexpert 阅读数 3456
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    54 人正在学习 去看看 马龙

前段时间,微软开源了认知服务的工具箱,直到近期才有时间进行测试。


看了文档,这个CNTK工具包还是非常厉害的,可以支持语音识别,图像分类,机器翻译等多种任务。里面也集成了多种深度学习的模型。such as deep neural networks (DNNs), convolutional neural networks (CNNs), recurrent neural networks (RNNs), long short term memory (LSTM), logistic regression, and maximum entropy model, that can be illustrated as a series of computational steps


基本上将主流的深度学习框架都涵盖在里面了。


(1) 安装步骤:

准备工作

1、 现在编译好的win下的安装包:https://github.com/Microsoft/CNTK/releases,(有不同版本)

2、 解压到下面文件夹:E:\\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD


安装必要环境:

E:\cntk安装\CNTK-2-0-beta3-0-Windows-64bit-GPU-1bit-SGD \cntk\prerequisites所有文件,以及安装python3.4以上版本,并安装https://repo.continuum.io/archive/Anaconda3-4.1.1-Windows-x86_64.exe

说明:本文Anaconda3安装目录为:E:\Anaconda3


创建cntk python环境

进入目录E:\Anaconda3\scripts

 

使用命令:conda env create --file E:\zhangxiong\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\Scripts\install\windows\conda-windows-cntk-py34-environment.yml

注:这个过程可能无法一次成功,使用命令conda clean –-lock解锁之后,重新执行上述命令


pipinstall  E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\cntk\Python\cntk-2.0.beta4.0-cp34-cp34m-win_amd64.whl

 

安装CNTK python 环境

安装上述教材会报错:xxxx.whl is not asupported wheel on this platform

这是由于官方教程中python版本不一致(可能是更新所致),因此需要将cntk-2.0.beta3.0-cp34-cp34m-win_amd64.whl文件改名为cntk-2.0.beta3.0-cp35-cp35m-win_amd64.whl

再执行上述代码:


设置环境变量:

setx PATH=E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\cntk;%PATH%


测试验证安装是否成功

进入example目录:E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\Examples\Tutorials\LogisticRegressionAndMultiClass

 使用命令:

cntkconfigFile=lr_bs.cntk makeMode=false command=Train


训练成功:目录中生成model 文件夹,如图所示



训练并测试一个算法:

cntk configFile=lr_bs.cntk makeMode=false command=Train :Output:DumpNodeInfo:Test

根据测试文件生成了MC.txt.z测试结果文件(在Linux下可打开查看)


语音测试speech

进入目录E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\Examples\Speech\AN4\Data

输入命令:

cntk configFile=../Config/FeedForward.cntk


同样可以得到训练模型


后续根据该训练模型就可以实现语音的识别。


2018-06-15 18:10:52 dxh1994 阅读数 1114
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    54 人正在学习 去看看 马龙

验证码介绍

        现在的验证码主要分为4类:识图验证码、计算验证码、滑块验证码、语音验证码。本文只针对taobao的识图验证码进行识别。目标验证码如下:

   

        可以看到这类验证码都是变形、粘连、大小不一、位置不固定的,不再像以前的可分割的验证码了,也就增加了机器识别的难度。这类粘连验证码就是本文主要的研究对象。

识别步骤

(1)图片预处理

        灰度化、 二值化、去噪声(本文验证码无噪声,没有此步骤)等

(2)字符切割

        CFS连通域分割、滴水法

(3)分类模型训练

        支持向量机或神经网络。本文训练的是卷积神经网络

图片预处理

        图片预处理之后的效果:


(1)灰度化

        彩色图片的像素点是由R、G、B三个原色组成,其值在0-255之间,有三个通道。灰度图是指只含有亮度信息,不含彩色信息的图像,即只有一个通道。灰度化就是将彩色图像转为灰度图像的过程。本文采用的灰度化方法是加权平均法。

def rgb2gray(img): # 求灰度值
    return np.dot(img[...,:3], [0.299, 0.587, 0.114])

(2)二值化

        二值化是指将灰度图像转换成只有黑白两色的图像,通常做法是选择一个阈值,如果灰度图像上的像素大于该阈值则为白色,否则为黑色。

def binary(gray):# 二值化
    height,width=gray.shape
    bin = gray.copy()
    for i in range(height):
        for j in range(width):
            if (gray[i,j]<=130):
                bin[i,j]=0
            else:
                bin[i,j]=1
    return bin

字符切割

        由于粘连的字符不好识别,所以我们将粘连的字符切割为单个字符,再训练模型进行识别。

(1)竖直投影法

        竖直投影法是最简单的字符分割方法,根据相邻的波谷或者极大极小值来确定字符边界,常用于字符没有粘连的情况,如车牌识别。但是由于目标验证码倾斜程度不小并且大多粘连,对于倾斜严重但实际并未粘连的两个字符,竖直投影的效果不太好,因此未采用这种方法。

(2)CFS连通域分割法

        假定每个字符由单独的一个连通域组成,因此只需要将不同连通域加以标记,就可分割出字符。此方法要求字符不粘连,无论旋转或扭曲。找到一个黑色像素并开始判断,直到所有相连的黑色像素都被遍历标记过后即可判断出字符的分割位置。

其算法原理:

        1.对二值化之后的图像,从左往右,从上往下扫描图片,遇到黑点,并且该点没有被访问,则将该点压栈,并标记为已经访问。

        2.如果栈不空,则继续探测周围4个点(四邻域,也可选择八邻域),并执行步骤1;如果栈空,则代表已经探测完一个字符块,并将该字符块的最左最右、最上最下坐标保存(本文只保存左右)。

        3.探测结束,最后通过每个字符的坐标进行切割。

代码(复制别人的:https://www.cnblogs.com/qqandfqr/p/7866650.html):

# ---------------------------------------CFS连通域------------------------------------------
import queue
def cfs(im,x_fd,y_fd): # 用队列和集合记录遍历过的像素坐标代替单纯递归以解决cfs访问过深问题
    xaxis=[]
    yaxis=[]
    visited =set()
    q = queue.Queue()
    q.put((x_fd, y_fd))
    visited.add((x_fd, y_fd))
    offsets=[(1, 0), (0, 1), (-1, 0), (0, -1)]#四邻域

    while not q.empty():
        x,y=q.get()
        for xoffset,yoffset in offsets:
          x_neighbor,y_neighbor = x+xoffset,y+yoffset

          if (x_neighbor,y_neighbor) in (visited):
              continue  # 已经访问过了

          visited.add((x_neighbor, y_neighbor))

          try:
              if im[x_neighbor, y_neighbor] == 0:
                  xaxis.append(x_neighbor)
                  yaxis.append(y_neighbor)
                  q.put((x_neighbor,y_neighbor))
          except IndexError:
              pass
    # print(xaxis)
    if (len(xaxis) == 0 | len(yaxis) == 0):
        xmax = x_fd + 1
        xmin = x_fd
        ymax = y_fd + 1
        ymin = y_fd
    else:
        xmax = max(xaxis)
        xmin = min(xaxis)
        ymax = max(yaxis)
        ymin = min(yaxis)
        #ymin,ymax=sort(yaxis)
    return ymax,ymin,xmax,xmin

def detectFgPix(im,xmax): # 搜索区块起点
    h,w = im.shape[:2]
    for y_fd in range(xmax+1,w):
      for x_fd in range(h):
          if im[x_fd,y_fd] == 0:
              return x_fd,y_fd

def CFS(im): # 切割字符位置

    zoneL=[]#各区块长度L列表
    zoneWB=[]#各区块的X轴[起始,终点]列表
    zoneHB=[]#各区块的Y轴[起始,终点]列表

    xmax=0#上一区块结束黑点横坐标,这里是初始化
    for i in range(10): #几个字符,最终运行几次
      try:
          x_fd,y_fd = detectFgPix(im,xmax)
          # print(y_fd,x_fd)
          xmax,xmin,ymax,ymin=cfs(im,x_fd,y_fd)
          L = xmax - xmin
          H = ymax - ymin
          zoneL.append(L)
          zoneWB.append([xmin,xmax])
          zoneHB.append([ymin,ymax])
      except TypeError:
          return zoneL,zoneWB,zoneHB
    return zoneL,zoneWB,zoneHB

def cutting_img(im,im_position,xoffset = 1,yoffset = 1):
    cut = []
    # filename =  './out_img/' + img.split('.')[0]
    # 识别出的字符个数
    im_number = len(im_position[1])
    # 切割字符
    for i in range(im_number):
        im_start_X = im_position[1][i][0]
        im_end_X = im_position[1][i][1]
        # im_start_Y = im_position[2][i][0]
        # im_end_Y = im_position[2][i][1]
        # 判断一下
        # if im_start_X > 0 : # 非边界
        #     im_start_X = im_start_X - xoffset
        # if im_end_X < im.shape[1]:  # 非边界
        #     im_end_X = im_end_X + xoffset
        # if im_start_Y > 0: # 非边界
        #     im_start_Y = im_start_Y -yoffset
        # if im_end_Y < im.shape[0]: # 非边界
        #     im_end_Y = im_end_Y + yoffset
        cropped = im[0:im.shape[0]+1, im_start_X:im_end_X]
        # cv2.imwrite(filename + '-cutting-' + str(i) + '.jpg',cropped)
        cut.append(cropped)
    return cut

(3)滴水算法

        滴水算法主要是模仿水滴从高处向低处落的过程来对粘连字符进行切割。水滴从字符串顶部在重力的作用下,只能沿字符轮廓向下滴落或水平滚动(周围5个点,具有优先级)。最终水滴所经过的轨迹就构成了字符的切割路径。

        滴水法决定因素主要包括:起始点、移动规则以及方向的确定。原理自行百度。

代码(复制别人的,参考链接https://www.jianshu.com/p/deee3e7e463b,好像原作者已经删除):

# --------------------------------------------------滴水法--------------------------------------------------------------
def binImgHist(img, bin_width=1, direction=1): # 二值化图像在y轴或者x轴方向的投影统计
    height, width = img.shape
    bins = None
    if direction == 0:# 在y轴方向上统计
        bins = int(height / bin_width)
    else:
        bins = int(width / bin_width)
    # 获取非零元素坐标
    nonzero_points = np.nonzero(img != 0)
    # 获取非零元素坐标中的x坐标集合或者y坐标集合
    nonzero_idx_x = nonzero_points[direction]
    # 返回的统计直方图
    hist = np.histogram(np.int64(nonzero_idx_x), bins=bins)[0]
    return hist

COLOR_BACK = 1 # 背景颜色
COLOR_PEN = 0 # 笔触的颜色

# 首先将原来的图片转换成布尔矩阵
# bool_img = gray == COLOR_PEN
# print(bool_img[15:25,15:25])

# 定义邻居坐标 的位置关系
# NEIGHBOR_IDX = {1: (-1, 0), 2: (-1, 1), 3: (0, 1), 4: (1, 1), 5: (1, 0)}

# 根据不同优先级情况,对应的下一个点的位置 n_i
# CASE_NEXT_POINT = {1: 3, 2: 2, 3: 3, 4: 4, 5: 5, 6: 1}

def is_case_p1(n1, n2, n3, n4, n5):
    '''
    优先级 :1
    下落方向: 正下方
    下落位置: n3
    备注: 全为背景或者全为笔迹
    '''
    if n1 and n2 and n3 and n4 and n5 == True:
        # 全为数字部分
        return True
    elif not (n1) and not (n2) and not (n3) and not (n4) and not (n5) == True:
        # 全为背景部分
        return True
    return False

def is_case_p2(n1, n2, n3, n4, n5):
    '''
    优先级 :2
    下落方向: 左下方
    下落位置: n2
    备注: n2点为背景,且其他点至少有一个为笔迹的颜色
    '''
    return not (n2) and (n1 or n3 or n4 or n5)

def is_case_p3(n1, n2, n3, n4, n5):
    '''
    优先级 :3
    下落方向: 正下方
    下落位置: n3
    备注: 左下角为笔迹的颜色,正下方为背景色
    '''
    return n2 and not (n3)

def is_case_p4(n1, n2, n3, n4, n5):
    '''
    优先级 :4
    下落方向:右下方
    下落位置: n4
    备注: 左下角跟正下方为笔迹的颜色,右下方为背景色
    '''
    return n2 and n3 and not (n4)

def is_case_p5(n1, n2, n3, n4, n5):
    '''
    优先级 :5
    下落方向:右边
    下落位置: n5
    备注:下方全为笔迹颜色,且左边为背景色
    '''
    return n2 and n3 and n4 and not (n5)

def is_case_p6(n1, n2, n3, n4, n5):
    '''
    优先级 6
    下落方向:左边
    下落点:n1
    备注 除了左边是背景色,其他均为笔迹颜色
    '''
    return not (n1) and n2 and n3 and n4 and n5

def drop_fall(neighbors,CASE_NEXT_POINT = {1: 3, 2: 2, 3: 3, 4: 4, 5: 5, 6: 1}):
    '''
    传统滴水算法  核心代码
    根据优先级实现
    neighbors = [n1, n2, n3, n4, n5]

    返回 :下落点的邻居编号
    '''
    for priority in range(1, 7):
        if eval('is_case_p{}(*neighbors)'.format(priority)):
            return CASE_NEXT_POINT[priority]

def is_legal_pt(img, x, y):
    '''
    是否为合法的坐标
    判断是否超出正常的数值范围
    '''
    h, w = img.shape
    if x < 0 or y < 0 or x >= w or y >= h:
        return False
    return True

def get_neighbors(bool_img, x, y,NEIGHBOR_IDX = {1: (-1, 0), 2: (-1, 1), 3: (0, 1), 4: (1, 1), 5: (1, 0)}):
    '''
    给定逻辑图跟坐标, 返回邻居的有序布尔数组
    '''
    neighbors = []
    for n_idx in range(1, 6):
        dx, dy = NEIGHBOR_IDX[n_idx]
        new_x, new_y = x + dx, y + dy
        if not is_legal_pt(bool_img, new_x, new_y):
            # 如果坐标不合法 填充背景
            neighbors.append(False)
        else:
            neighbors.append(bool_img[new_y][new_x])
    # print(neighbors)
    return neighbors

def get_split_path(bool_img,start_x,height,width,NEIGHBOR_IDX = {1: (-1, 0), 2: (-1, 1), 3: (0, 1), 4: (1, 1), 5: (1, 0)}): # 给出分割路径
    min_x = 0  # x坐标的左边界
    max_x = width - 1  # x坐标的右边界
    max_y = height - 1
    # 当前的点
    cur_pt = (start_x, 0)
    # 最近的点
    last_pt = cur_pt
    # 分割路径
    split_path = [cur_pt]

    while cur_pt[1] < max_y:
        neighbors = get_neighbors(bool_img, cur_pt[0], cur_pt[1])
        n_idx = drop_fall(neighbors)
        dx, dy = NEIGHBOR_IDX[n_idx]
        next_pt = None
        next_x, next_y = cur_pt[0] + dx, cur_pt[1] + dy
        if not is_legal_pt(bool_img, next_x, next_y):
            # x/y越界 向下渗透
            next_pt = (cur_pt[0], cur_pt[1] + 1)
        else:
            next_pt = (next_x, next_y)
        # 判断点是否重复出现,左右往复平移
        if next_pt in split_path:
            # 已经判断重复,进行渗透
            next_pt = (cur_pt[0], cur_pt[1] + 1)

        cur_pt = next_pt
        split_path.append(cur_pt)
    return split_path

切割后效果:

分类模型

        将验证码切割好,手动打标10000张单个验证码字符,再利用keras训练一个类似于mnist例子的卷积模型。在测试集(400张验证码,260张有粘连)上测试,单个验证码字符准确率有0.88,整体验证码准确率有0.68,结果还算勉强。实践证明,这类验证码有粗有细、倾斜粘连,不像mnist那么规范,样本量还是太少,泛性不高,但是人工打标又太费时间。

数字验证码

对于taobao的6位数字验证码也进行了尝试,目标验证码及切割效果如下:


切割过程及效果如下图:


taobao这种验证码很坑的地方在于,第一个字符或最后一个字符只出现部分,甚至偶尔只有5位字符,如下图:


参考链接:

        https://www.cnblogs.com/qqandfqr/p/7866650.html

        https://www.jianshu.com/p/deee3e7e463b

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