语音处理

语音处理（Speech processing），又称语音讯号处理、人声处理，是研究语音信号及其处理方法的学科。通常，这些信号是以数字形式进行处理的，因此语音处理可以被视为数字信号处理的一个特殊案例，专门应用于语音信号。语音处理涉及语音信号的获取、操控、存储、传输和输出等多个方面。不同的语音处理任务包括语音识别、语音合成、说话人分离、语音增强、说话人识别等。

## 语音的相关常识

### 一般声音档格式

- 取样频率：22050Hz
- 单声道或双声道
- 每笔资料用8个bit来表示
- 电脑中没有经过任何压缩的声音档：*.wav

### 声音的频率

- 频率范围

人耳可以辨识频率：20Hz ~ 20000Hz

说话：150~2000Hz

电话系统频域：小于3500Hz

电脑声卡取样频率：44100Hz (最新技术可达192K)(一般用22050Hz、11025Hz 即可)

- 超音波(ultrasound)与次声波(infrasound)

超音波(ultrasound)：> 20000Hz

次声波(infrasound)：< 20Hz

- 人对于频率的分辨能力，是由于频率的"比"决定

对人类而言，300Hz和400Hz之间的差别，与3000Hz和4000Hz之间的差别是相同的

### 声音的波长

- 波长较长：传播距离较远，但容易散射
- 波长较短：衰减较快，但传播方向较接近直线

### 声音的速度

- 声音在空气中传播速度：每秒340 米(15°C 时)
- 一般人，耳翼到鼓膜之间的距离： 2.7公分。所以，人类对3000Hz 左右频率的声音最敏感
- 每增加1°C，声音的速度增加0.6 m/sec
- 声音在水中的传播速度是1500 m/sec，在铝棒中的传播速度是5000 m/sec

### 分贝

- 分贝(dB)：$10 log_{\text{10}} (P/C) \,\!$

其中P为音强(正比于振福的平方)，C为0dB时的音强

- 每增加10dB，音强增加10倍，振幅增加 $10^{0.5} \,\!$ 倍；每增加3dB，音强增加2倍，振幅增加 $2^{0.5} \,\!$ 倍

### 音乐讯号

低音Do: 131.32 Hz

中音Do: 261.63 Hz

高音Do: 523.26 Hz

更高音Do: 1046.52 Hz

- 音乐每增加八度音，频率变为2 倍
- 每一音阶有12个半音。增加一个半音，频率增加$2^{1/12} \,\!$ 倍(1.0595 倍)
- 音乐通常会出现“和弦”(chord) 的现象。除了基频$f_{\text{0}} \,\!$ Hz 之外，也会出现2$f_{\text{0}} \,\!$Hz, 3$f_{\text{0}} \,\!$Hz, 4$f_{\text{0}} \,\!$Hz , ...... 的频率

### 语音处理的工作

- 语音编码(Speech Coding)：将语音讯号以较低的位元率表示，以利储存或传输。工程上常利用语音产生模型、线性预测或量化方法，在降低资料量的同时维持可懂度与音质。
- 语音合成(Speech Synthesis)：根据文字、音素序列或其他语言资讯产生语音波形，可用于文字转语音、辅助沟通与人机互动等应用。
- 语音增强(Speech Enhancement)：在噪声、回响或通道失真存在时，改善语音讯号的品质或可懂度。常见方法包括滤波、频谱减法及统计式噪声抑制等。
- 语音辨认(Speech Recognition)：将语音讯号转换为音素、音节、词或句子。一般而言，辨认流程可由“音素→音节→词→句→整段话”逐步建立，并常结合声学模型与语言模型。
- 说话人辨识(Speaker Recognition)：根据语音讯号判断或验证说话者身份，可分为说话人识别与说话人验证。
- 其他相关任务：包括语意分析、语言分析、情绪辨识及说话人分离等。

### 子音和母音

- 母音：依唇型而定。母音的能量大，频率偏低，时间较长，出现在子音后或独立出现
- 子音：在口腔，鼻腔中某些部位将气流暂时堵住后放开。子音的能量小，频率偏高，时间较短，出现在母音前

### 语意学的角色

- 以“语意学”或“几率”来补足语音辨识的不足

例如：经过判定，一个声音可能是

ㄅㄧ ㄖㄢ ㄆㄧ ㄖㄢ

ㄅㄧ ㄌㄢ ㄆㄧ ㄌㄢ

这个声音是“必然”的几率比较大。

ㄅㄛ ㄅㄛ ㄆㄛ ㄆㄛ

可能是“伯伯”，也可能是“婆婆”，看上下文

梅尔频率倒频谱(Mel-Frequency Cepstrum) + 语意分析 + 机器学习(Machine Learning)

## 语音档的处理(Matlab)

### 读取声音档

- 电脑中，没有经过压缩的声音档都是*.wav 的型态，有经过压缩的声音档是*.mp3的型态
- 读取： audioread (2015版本以后的Matlab，wavread 将改为audioread)
- 例： x, fs = audioread('C:\WINDOWS\Media\ringin.wav');

可以将ringin.wav 以数字向量x来呈现。fs:取样频率(sampling frequency)

这个例子当中size(x) = 122868 2 (2指的是双声道(Stereo，俗称立体声))，fs = 22050

- .wav 档中所读取的资料，值都在 -1 和 +1 之间
- 画出声音的波型

time = 0:size(x,1)-1/fs; (x 是用audioread 所读出的向量)

subplot(2,1,1); plot(time, x(:,1)); xlim(time(1),time(end))

subplot(2,1,2); plot(time, x(:,2)); xlim(time(1),time(end))

### 绘出频谱

X = fft(x(:,1));

X=X.';

N=length(X); N1=round(N/2);

dt=1/fs;

X1=X(N1+1:N),X(1:N1)*dt; (shifting for spectrum)

f=N1:N-1-N,0:N1-1/N*fs; (valid f)

plot(f, abs(X1));

### 声音的播放

- sound(x): 将x以8192Hz 的频率播放
- sound(x, fs): 将x以fs Hz 的频率播放

注：x必须是1 个column (或2个columns)，且x的值应该介于 -1 和 +1 之间

- soundsc(x, fs): 自动把x的值调到 -1 和 +1 之间再播放

### 制作.wav档：audiowrite

audiowrite(filename, x, fs)

将数据x变成一个*.wav 档，取样频率为fsHz

注：x必须是1 个column (或2个columns)，且x的值应该介于 -1 和 +1

### 录音的方式

#### 基本指令说明

以下这三个指令要并用才可以录音

- recorder = audiorecorder(Fs, nb, nch); (提供录音相关的参数)

Fs: sampling frequency

nb: using nb bits to record each data

nch: number of channels (1 or 2)

- recordblocking(recorder, Sec); (录音的指令)

recorder: the parameters obtained by the command audiorecorder

Sec: the time length for recording

- audioarray = getaudiodata(recorder);

(将录音的结果，变成audioarray 这个column vector，如果是双声道，则audioarray 是两个column vectors)

#### 范例

Sec = 3; (录音的时间为三秒)

Fs = 8000; (sampling frequency 为8000 Hz)

recorder = audiorecorder(Fs, 16, 1);

recordblocking(recorder, Sec);

audioarray = getaudiodata(recorder); (录音结果为audioarray，是一个column vector (如果是双声道，则是两个column vectors))

sound(audioarray, Fs); (播放录音的结果)

t = 0:length(audioarray)-1./Fs;

plot (t, audioarray); (将录音的结果用图画出来)

xlabel('sec','FontSize',16);

audiowrite('test.wav', audioarray, Fs) (将录音的结果存成*.wav 档)

## 语音档的处理(Python)

### 安装模组

- pip install numpy
- pip install scipy
- pip install matplotlib # plot
- pip install pipwin
- pipwin install simpleaudio # vocal files
- pipwin install pyaudio

### 读音讯档

要先import 相关模组: import wave

#### 读取音档

wavefile = wave.open('C:/WINDOWS/Media/Alarm01.wav', 'rb')

获得音档取样频率和音讯长度:

fs =wavefile.getframerate # sampling frequency

num_frame = wavefile.getnframes # length of the vocal signal

>>> fs

22050

>>> num_frame

122868

#### 读取波形与数据

要先import 相关模组: import numpy as np

- str_data = wavefile.readframes(num_frame)
- wave_data = np.frombuffer(str_data, dtype=np.int16) # 转成整数型态
- wave_data = wave_data / max(abs(wave_data)) # normalization
- n_channel = 2
- wave_data = np.reshape(wave_data, (num_frame, n_channel)) # 若为双声道音档需要做reshape

#### 画出音讯波形图

要先import 相关模组: import matplotlib.pyplot as plt

- time = np.arange(0, num_frame)*1/fs
- plt.plot(time, wave_data)
- plt.show

### 画出频谱

要先import 相关模组: from scipy.fftpack import fft

- fft_data = abs(fft(wave_data:,1))/fs # only choose the 1st channel # 注意要乘上1/fs
- n0=int(np.ceil(num_frame/2))
- fft_data1=np.concatenate(fft_datan0:num_frame,fft_data0:n0) # 将频谱后面一半移到前面
- freq=np.concatenate(range(n0-num_frame,0),range(0,n0))*fs/num_frame # 频率轴跟着调整
- plt.plot(freq,fft_data1)
- plt.xlim(-1000,1000) # 限制频率的显示范围
- plt.show

### 播放声音

要先import 相关模组: import simpleaudio as sa

- n_bytes =2 # using two bytes to record a data
- wave_data = (2**15-1)* wave_data # change the range to -215 ~ 215
- wave_data = wave_data.astype(np.int16)
- play_obj = sa.play_buffer(wave_data, n_channel, n_bytes, fs)
- play_obj.wait_done

### 制作音档

- f = wave.open('testing.wav', 'wb')
- f.setnchannels(2) # 设定声道数
- f.setsampwidth(2) # 每个samples 有几个字节
- f.setframerate(fs) # 设定取样频率
- f.writeframes(wave_data.tobytes)
- f.close

### 录音

要先import 相关模组: import pyaudio

import pyaudio

pa=pyaudio.PyAudio

fs = 44100

chunk = 1024

stream = pa.open(format=pyaudio.paInt16, channels=1,rate=fs, input=True, frames_per_buffer=chunk)

vocal=

count=0

## 时频分析结果分析

### Matlab

#### 画出时频分析结果

可采行两种方式：

- 使用mesh 指令画出立体图(但结果不一定清楚，且执行时间较久)
- 将amplitude 变为gray-level，用显示灰阶图的方法将结果表现出来

假设y 是时频分析计算的结果

image(abs(y)/max(max(abs(y)))*C) % C 是一个常数，可以选C=400

或image(t, f, abs(y)/max(max(abs(y)))*C)

colormap(gray(256)) % 变成gray-level 的图

set(gca,'Ydir','normal') % 若没这一行, y-axis 的方向是倒过来的

set(gca,'Fontsize',12) % 改变横纵轴数值的font sizes

xlabel('Time (Sec)','Fontsize',12) % x-axis

ylabel('Frequency (Hz)','Fontsize',12) % y-axis

title('STFT of x(t)','Fontsize',12) % title

#### 计算程式执行时间的指令

tic (这指令如同按下码表)

toc (show 出码表按下后已经执行了多少时间)

注：通常程式执行第一次时，由于要做程式的编译，所得出的执行时间会比较长。程式执行第二次以后所得出的执行时间，是较为正确的结果

### Python

#### 事前安装模组

- pip install numpy
- pip install matplotlib

#### 画出时频分析结果

假设y为时频分析结果(应为二维的矩阵数列)，将y 以灰阶方式画出来

import numpy as np

import matplotlib.pyplot as plt

C = 400

y = np.abs(y) / np.max(np.abs(y)) * C

plt.imshow(y, cmap='gray', origin='lower') # 加上origin='lower' 避免上下相反

plt.xlabel('Time (Sec)')

plt.ylabel('Frequency (Hz)')

plt.show

若要加上座标轴数值(在plt.show之前加上以下程式码)

x_label = '0', '10', '20', '30' # 横轴座标值

y_label = '-5', '0', '5' # 纵轴座标值

plt.xticks(np.arange(0, x_max, step=int(x_max/(len(x_label)-1)), x_label)

plt.yticks(np.arange(0, y_max, step=int(y_max/(len(y_label)-1)), y_label)

## 语音讯号的来源

人声是由于声带震动，而产生声音。当运动肌肉挤压，使肺脏中的空气通过声带时，空气流动使得声带做周期性的震动，又再一次震动了空气，接着，带着动能的空气离开气管到达口腔或鼻腔，在腔室中震动，最后离开在嘴唇传到人耳变成声音。

若调整口腔中舌头的位置，会产生不同种类的声音，如果舌头没有做太多的动作，空气只有在口腔中共振，接着直接流出嘴唇，会产生母音，若提起舌头，使口鼻腔相通，则会出现鼻音。

## 语音讯号分类

从中文发音的观点来说，声音仍可分为子音与母音，母音和子音可以用两种方式区分：

- 发声方式：一般而言，母音跟嘴唇形状有关，而且不与鼻腔共振。相对而言，在发出子音时，就会运用到鼻腔配合发声。
- 频谱分析：从频谱上观察可以发现子音的讯号频率较高，持续时间较短，且会在母音之前出现。而母音的频率较低，持续时间较长，在子音后或独立出现，另外，母音的能量也会比子音大。

下面列出中文注音符号中的母音、子音及其拼音。

- 母音：ㄚ ㄛ ㄜ ㄝ ㄞ ㄟ ㄠ ㄡ ㄢ ㄣ ㄤ ㄥ ㄦ 一 ㄨ ㄩ

母音｜ㄚ｜ㄛ｜ㄜ｜ㄝ｜ㄞ｜ㄟ｜ㄠ｜ㄡ

汉语拼音｜a｜o｜e｜e｜ai｜ei｜ao｜ou

通用拼音｜a｜o｜e｜e｜ai｜ei｜ao｜ou

母音｜ㄢ｜ㄣ｜ㄤ｜ㄥ｜ㄦ｜一｜ㄨ｜ㄩ

汉语拼音｜an｜en｜ang｜eng｜er｜i,y｜u,w｜yu,ju

通用拼音｜an｜en｜ang｜eng｜er｜i,y｜u,w｜yu,ju

- 子音：ㄅ ㄆ ㄇ ㄈ ㄉ ㄊ ㄋ ㄌ ㄍ ㄎ ㄏ ㄐ ㄑ ㄒ ㄓ ㄔ ㄕ ㄖ ㄗ ㄘ ㄙ

子音｜ㄅ｜ㄆ｜ㄇ｜ㄈ｜ㄉ｜ㄊ｜ㄋ

汉语拼音｜b｜p｜m｜f｜d｜t｜n

通用拼音｜b｜p｜m｜f｜d｜t｜n

子音｜ㄌ｜ㄍ｜ㄎ｜ㄏ｜ㄐ｜ㄑ｜ㄒ

汉语拼音｜l｜g｜k｜h｜j｜q｜x

通用拼音｜l｜g｜k｜h｜j｜q｜x

子音｜ㄓ｜ㄔ｜ㄕ｜ㄖ｜ㄗ｜ㄘ｜ㄙ

汉语拼音｜zh｜ch｜sh｜r｜z｜c｜s

通用拼音｜zh｜ch｜sh｜r｜z｜c｜s

## 共振峰与母音辨识

在语音频谱中，母音常会出现数个明显的频谱峰值，称为“共振峰”，通常以 $F_1$、$F_2$、$F_3$ 表示。不同母音的共振峰位置不同，因此可作为辨别母音的重要声学线索。一般而言，第一共振峰 $F_1$，与舌位高低、口腔开合程度有关，第二共振峰 $F_2$，则常与舌位前后有关。

从工程观点来看，共振峰可视为声道滤波器在特定频率附近产生的共振结果。当声带或气流所产生的激发讯号通过声道时，某些频率成分会被加强，因而在频谱上形成峰值。双母音的频谱则会随时间改变，通常会由一个母音的共振峰型态逐渐移动到另一个母音的型态。

## 语音的架构

要分析语音讯号前，必须先了解其架构，语音的要素从小到大分别是：音素→音节→词汇→句子→整段话。

音素是声音的最小单位，例如“呵”这个字的音素，就是“ㄏ”和“ㄜ”，但是音素和注音符号并不相等，例如“鸥”虽然只有“ㄡ”这个母音，但是由于是双母音，所以会把他拆成两个音素。音节在中文而言，就是只一个字，例如：“天天开心”就有四个音节。词汇是文字组成的有意义片段，各种不同的词汇集结成句子，最后变成整段话，这就是语音的架构。

## 语音处理方法

用麦克风或其他装置收到的类音声音讯号，经由类比数位转换装置，将资料数据化进行处理，最后再经过数位类比转换装置输出。因此，我们在处理时是针对数字信号，语音讯号是一种离散时间讯号。其讯号处理流程如下：

- 收取并取样讯号：利用麦克风或各种收音装置，收取类比语音讯号，再用ADC装置(如类比数位转换卡)把类比讯号变成数字信号，接着根据奈奎斯特理论作取样，若不符合理论则会造成讯号失真。
- 量化及编码：由于电脑中的记忆都是0和1，因此要将所收到的数据用一段适合的0跟1去储存，这个动作就称为量化，所使用的0与1越多，所需的内存越多。接着利用编码器将数值以波形呈现，因此虽然是数字信号，但是在电脑中所见到的是类比。
- 讯号标准化：将语音讯号标准化，使其数值都落在同一个范围。
- 音框选择：由于语音讯号是一段很长的讯号，因此会针对想要处理的部分取音框。
- 端点侦测：端点侦测的目的是使讯号处理的范围更精确，只要设定一个音量阈值，若讯号小于阈值，则将其视为没讯号，但是若噪声过高，则会产生误差。
- 去噪声：由于噪声多集中在高频的部分，因此利用简单的高频滤波器，就可以去掉部分噪声。

## 发音模型与短时分析

从工程观点来看，语音讯号可用简化的发音模型描述。若以离散时间讯号表示，可写成：

$$x[n]=e_{\mathrm{p}}[n]\ast g[n]\ast h[n]\ast r[n]$$

其中，$\ast$ 表示折积(convolution)，$e_{\mathrm{p}}[n]$ 可视为激发讯号，$g[n]$ 表示声带模型，$h[n]$ 表示口腔或声道模型，$r[n]$ 表示唇部辐射模型。

转换到 Z 域后，可表示为：

$$X(z)=E_{\mathrm{p}}(z)G(z)H(z)R(z)$$

此模型可用来说明语音中音量、音高、子音与母音等特征的形成。

由于语音讯号会随时间变化，但在足够短的时间内可近似为平稳讯号，因此语音处理常使用短时傅立叶转换（Short-Time Fourier Transform, STFT）进行时频分析：

$$G(t,f)=\int_{-\infty}^{\infty} w(t-\tau)g(\tau)e^{-j2\pi f\tau}\,d\tau$$

其中，$w(t-\tau)$ 为窗函数(window function)，用来强调时间 $t$ 附近的讯号。短时傅立叶转换可形成频谱图，用于观察语音能量在时间与频率上的分布。

## 基本处理方法

语音讯号是属于离散时间系统，因此会用离散时间的傅立叶转换去做处理，除此之外，折积、窗函数都是一定会使用到的处理方法。

$$x[n] = \frac{1}{2\pi}\int\limits_{-\pi}^\pi X(\omega) e^{-j\omega n}\,d\omega.$$

$$X(\omega) = \sum_{k=0}^{N-1} x[n] e^{-j \omega n}$$

两讯号做convolution等于，两讯号先做傅立叶转换，相乘后再做反傅立叶转换，借此可以更快速的处理讯号。

$$\mathcal{F}\{h*x\} = \mathcal{F}\{h\} \cdot \mathcal{F}\{x\}$$

在语音辨识、语音编码与说话人辨识中，原始波形通常不会直接作为模型输入，而是先转换成能描述语音频谱、声道特性或时间变化的特征。常见方法包括线性预测编码、梅尔频率倒频谱系数与隐马可夫模型等。如下：

- 线性预测编码 (linear predictive coding, LPC) 是一种以线性模型描述语音讯号的方法。其基本假设是目前的语音样本可以由前面若干个样本的线性组合近似预测：$\hat{s}[n]=\sum_{k=1}^{p}a_k s[n-k]$，其中，为模型阶数，为线性预测系数。预测误差可写成 $e[n]=s[n]-\hat{s}[n]$。LPC 可用少数参数描述语音讯号的频谱包络，因此常用于语音编码、语音合成与语音分析。从发音模型的角度来看，LPC 也可视为对声道滤波器的近似描述。
- 梅尔频率倒频谱系数 (Mel-frequency cepstral coefficients, MFCC) 是语音辨识与说话人辨识中常见的声学特征。MFCC 的设计考虑人耳对频率的非线性感知，常使用梅尔尺度转换： $m=2595\log_{10}(1+f/700)$ 计算流程通常包括分帧、加窗、傅立叶转换、梅尔滤波器组、取对数与离散余弦转换。若第 $m$ 梅尔滤波器的能量为 $E_m$，则倒频谱系数可表示为：$c_n=\sum_{m=1}^{M}\log(E_m)\cos\left[\frac{\pi n}{M}(m-\frac{1}{2})\right]$, MFCC 能有效表示短时间语音频谱的包络形状，因此常被用于语音辨识、说话人辨识与语音分类等任务。
- 隐马可夫模型 (hidden Markov model, HMM) 是传统语音辨识中常用的统计模型。语音讯号具有明显的时间序列特性，例如音素会依时间顺序组成音节、词与句子。HMM 可用隐藏状态表示音素或语音单位的时间变化，并以观测几率描述声学特征与状态之间的关系。在语音辨识中，常将辨识问题表示为寻找最可能的词序列： $\hat{W}=\arg\max_W P(W|X)$, 其中 $X$ 为观测到的声学特征序列，$W$ 为可能的词序列。利用贝氏定理，可进一步写成：$\hat{W}=\arg\max_W P(X|W)P(W)$, 其中 $P(X|W)$ 可视为声学模型，描述词序列与声学特征之间的关系；$P(W)$ 则可视为语言模型，描述词序列本身出现的几率。这种架构说明语音辨识不只依赖声音讯号，也会结合语言与语意资讯。

## 语音处理的应用

语音处理主要有两个目的：

- 减少讯号噪声，做出想要的讯号模组。
- 进行语音辨识，使人可以利用语言与电脑沟通。