语⾳信号处理之(⼆)基⾳周期估计(PitchDetection)
下⾯总结的是第⼆个知识点:基⾳周期估计。我们⽤C++实现了基于⾃相关函数法的基⾳周期检测,并且结合了来显⽰语⾳波形。因为花的时间不多,所以可能会有不少说的不妥的地⽅,还望⼤家指正。谢谢。
⼀、概述
1.1、基⾳与基⾳周期估计
⼈在发⾳时,根据声带是否震动可以将语⾳信号分为清⾳跟浊⾳两种。浊⾳⼜称有声语⾔,携带者语⾔中⼤部分的能量,浊⾳在时域上呈现出明显的周期性;⽽清⾳类似于⽩噪声,没有明显的周期性。发浊⾳时,⽓流通过声门使声带产⽣张弛震荡式振动,产⽣准周期的激励脉冲串。这种声带振动的频率称为基⾳频率,相应的周期就成为基⾳周期。
通常,基⾳频率与个⼈声带的长短、薄厚、韧性、劲度和发⾳习惯等有关系,在很⼤程度上反应了个⼈的特征。此外,基⾳频率还跟随着⼈的性别、年龄不同⽽有所不同。⼀般来说,男性说话者的基⾳频率较低,⽽⼥性说话者和⼩孩的基⾳频率相对较⾼。
基⾳周期的估计称谓基⾳检测,基⾳检测的最终⽬的是为了出和声带振动频率完全⼀致或尽可能相吻合的轨迹曲线。
基因周期作为语⾳信号处理中描述激励源的重要参数之⼀,在语⾳合成、语⾳压缩编码、语⾳识别和说话⼈确认等领域都有着⼴泛⽽重要的问题,尤其对汉语更是如此。汉语是⼀种有调语⾔,⽽基因周期的变化称为声调,声调对于汉语语⾳的理解极为重要。因为在汉语的相互交谈中,不但要凭借不同的元⾳、辅⾳来辨别这些字词的意义,还需要从不同的声调来区别它,也就是说声调具有辨义作⽤;另外,汉语中存在着多⾳字现象,同⼀个字的不同的语⽓或不同的词义下具有不同的声调。因此准确可靠地进⾏基⾳检测对汉语语⾳信号的处理显得尤为重要。
1.2、基⾳周期估计的现有⽅法
到⽬前为⽌,基⾳检测的⽅法⼤致上可以分为三类:
1)时域估计法,直接由语⾳波形来估计基⾳周期,常见的有:⾃相关法、并⾏处理法、平均幅度差法、数据减少法等;
2)变换法,它是⼀种将语⾳信号变换到频域或者时域来估计基⾳周期的⽅法,⾸先利⽤同态分析⽅法将声道的影响消除,得到属于激励部分的信息,然后求取基⾳周期,最常⽤的就是倒谱法,这种⽅法的缺点就是⽐较复杂,但是基⾳估计的效果却很好;
3)混合法,先提取信号声道模型参数,然后利⽤它对信号进⾏滤波,得到⾳源序列,最后再利⽤⾃相关法或者平均幅度差法求得基因⾳周期。
三、基于⾃相关的基⾳周期检测
3.1、⾃相关函数
能量有限的语⾳信号x(n)的短时⾃相关函数定义为:
此公式表⽰⼀个信号和延迟m点后该信号本⾝的相似性。如果信号x(n)具有周期性,那么它的⾃相关函数也具有周期性,⽽且周期与信号x(n)的周期性相同。⾃相关函数提供了⼀种获取周期信号周期的⽅法。在周期信号周期的整数倍上,它的⾃相关函数可以达到最⼤值,因此可以不考虑起始时间,⽽从⾃相关函数的第⼀个最⼤值的位置估计出信号的基⾳周期,这使⾃相关函数成为信号基⾳周期估计的⼀种⼯具。
3.2、短时⾃相关函数法
语⾳信号是⾮稳态信号它的特征是随时间变化的,但在⼀个很短的时间段内可以认为具有相对稳定的特征即短时平稳性。因此语⾳具有短时⾃相关性。这个时间段约5ms-50ms。为其统计特性和频谱特性都是对短时段⽽⾔的。这使得要对语⾳信号作数字处理必须先按短时段对语⾳信号分帧。这样每⼀帧信号都具有短时平稳性从⽽进⾏短时相关分析。
能量有限的语⾳信号s(n)的短时⾃相关函数定义为:
⼀般要求⼀帧⾄少包含2个以上的周期。⼀般,基频最低50Hz,故周期最长为20ms。⽽且相邻帧之间要有⾜够的重叠。具体应⽤时,窗⼝长度根据采样率确定帧长。
该帧的⾃相关函数中,除去第⼀个最⼤值后(0处),最⼤值Kmax= 114,那么该帧对应的基频16kHz/114=140Hz。
四、基于⾃相关的基⾳周期检测算法实现
这个实现课程要求是⽤C++来实现的。然后为了画波形,我⽤到了我⽐较熟悉的OpenCV。OpenCV画出来的波形还是不错的,⽽且如果是动态的波形平移,挺好看的,就像⼼电图那么动⼈。
实验采⽤⼀段男声读“播放”两个字的声⾳wav⽂件,其为16KHz采样率,16bit量化。整段语⾳长656.7ms,节点共10508个。
我们先要确定帧长。下⾯分别是帧长200,320和400个节点时所包含的周期数。200时只有⼀个周期,⽽400有三个周期,所以我们采⽤400的帧长。
通过计算短时能量区分voice和unvoice。语⾳信号{x(n)}的某帧信号的短时平均能量En的定义为:
语⾳中浊⾳段的短时平均能量远远⼤于清⾳段的短时平均能量。因此,短时平均能量的计算给出了区分清⾳段与浊⾳段的依据,即En(浊)>En(清)。
计算每⼀帧的过程中,会显⽰在原来波形中的位置,并且实时显⽰该帧得到的基⾳周期。另外还会在另⼀个窗⼝实时显⽰该帧的原始波形。
该帧的原始波形图(以下为不同时间的两帧,会动态变化):
下⾯左边的图是计算该语⾳的所有帧对应的基⾳周期的点,由图可以看出存在不少的野点。因为,需要对此进⾏进⼀步的处理,即去除野点。这⾥通过中值滤波来除去野点,滤波结果见右图。
C++程序如下:(每按⼀次空格进⼊下⼀个步骤)
[cpp]
1. // Description : Pitch detection
2. // Author : Zou Xiaoyi
3. // HomePage : blog.csdn/zouxy09
4. // Date : 2013/06/08
5. // Rev. : 0.1
6.
7. #include <iostream>
8. #include <fstream>
9. #include "opencv2/opencv.hpp"
10. #include "ReadWriteWav.h"
11. #include <string>
12.
13. using namespace std;
14. using namespace cv;
15.
16. #define MAXLENGTH 1000
17.
18. void wav2image(Mat &img, vector<short> wavData,int wav_start, int width,int max_amplitude)
19. {
20. short max(0), min(0);
21. for (int i = 0; i < wavData.size(); i++)
22. {
23. if (wavData[i] > max)
24. max = wavData[i];
25. if (wavData[i] < min)
26. min = wavData[i];
27. }
28. cout<<max<<'\t'<<min<<endl;
29.
30. max_amplitude = max_amplitude > 480 ? 480 : max_amplitude;
31.
32. // normalize
resized33. for (int i = 0; i < wavData.size(); i++)
34. {
35. wavData[i] = (wavData[i] - min) * max_amplitude / (max - min);
36. }
37.
38. int j = 0;
39. Point prePoint, curPoint;
40. if (width >= 400)
41. {
42. ate(max_amplitude, width, CV_8UC3);
43. img.setTo(Scalar(0, 0, 0));
44. for (int i = wav_start; i < wav_start + width; i++)
45. {
46. prePoint = Point(j, ws - (int)wavData[i]);
47. if (j)
48. line(img, prePoint, curPoint, Scalar(0, 255, 0), 2);
49. curPoint = prePoint;
50. j++;
51. }
52.
53. if (width > MAXLENGTH)
54. {
55. cout<<"The wav is too long to show, and it will be resized to 1200"<<endl;
56. resize(img, img, Size(MAXLENGTH, ws));
57. }
58. }
59. else
60. {
61. ate(max_amplitude, 400, CV_8UC3);
62. img.setTo(Scalar(0, 0, 0));
63. for (int i = wav_start; i < wav_start + width; i++)
64. {
65. prePoint = Point(j*400/width, ws - (int)wavData[i]);
66. circle(img, prePoint, 3, Scalar(0, 0, 255), CV_FILLED);
67. j++;
68. }
69. cout<<"The wav is too small to show, and it will be resized to 400"<<endl;
70. }
71. }
72.
73. short calOneFrameACF(vector<short> wavFrame,int sampleRate)
74. {
75. vector<float> acf;
76. pty();
77.
78. // calculate ACF
79. for (int k = 0; k < wavFrame.size(); k++)
80. {
81. float sum = 0.0;
82. for (int i = 0; i < wavFrame.size() - k; i++)
83. {
84. sum = sum + wavFrame[i] * wavFrame[ i + k ];
85. }
86. acf.push_back(sum);
87. }
88.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。