《MATLAB语音信号分析与合成(第二版)》是中科院声学所的大佬宋知用老师数十年经验积累下的呕心之作,对于语音信号处理相关感兴趣的同学,日后希望在语音信号分析、处理与合成相关领域进行一定研究的话,可以以此进行入门。
语音信号处理是数字信号处理的一个重要分支。本书含有许多数字信号处理的方法和 MATLAB函数。 全书共10章。第1-4章介绍语音信号处理的一些基本分析方法和手段,以及相应的MATLAB函数;第5-9章介绍语音信号预处理和特征的提取,包括消除趋势项和基本的减噪方法,以及端点检测、基音的提取和共 振峰的提取,并利用语音信号处理的基本方法,给出了多种提取方法和相应的 MATLAB程序;第10章结合 各种参数的检测介绍了语音信号的合成、语音信号的变速和变调处理,还介绍了时域基音同步叠加( TD PSOLA)的语音合成,并给出了相应的MATLAB程序。附录A中给出了调试复杂程序的方法和思路。 本书可作为从事语音信号处理的本科高年级学生、研究生或科研工程技术人员的辅助读物,也可作为从 事信号处理研究与应用的科研工程技术人员的参考用书。
我的研究生导师的主攻方向就是语音信号处理相关,虽然自己研究生期间的大论文方向是数字图像处理,但所谓语音图像不分家,自己在老师的研究生主讲课小波变换上虽然划水,但在后期导师的语音信号处理的课程设计和工程应用上自己在语音上还算入了一点小门道,在结课测试中拿到了小组第一,导师还特地发了三百大洋的伙食经费以资鼓励。
这次重新捡起语音识别,正好入手了宋老师的这本书,算是自己重新复习一遍吧,主要以介绍各章节中源码为主,这是本书的第五章的8个仿真应用实例,话不多说,开始!
书中经常会调用的一些函数(自编函数或取自其他应用工具箱中的函数)已集中在basic_tbx工具箱中,在运行本书的程序前请把该工具箱设置(用set path设置)在工作路径下;
当要运行EMD处理时,要把emd工具箱设置在工作路径下;
当要运行主体延伸基音检测时,要把Pitch_ztlib工具箱设置在工作路径下;
当要进行时域基音同步叠加语音合成时,要把psola_lib工具箱设置在工作路径下;
当要应用本书提供的语音数据时,最好把speech_signal设置在工作路径下。
本书的所有函数和程序都在MATLAB R2009a版本下调试通过。(我用的是MATLAB2015b,有些函数已经更新,所以我会进行修改,以便调试通过)
路径设置的方法如下:
打开MATLAB,点击“主页”,找到设置路径
将上述文件夹路径全部添加到MATLAB搜索路径中
添加完毕,保存,开始仿真。
%
% pr5_3_1
clear all; clc; close all;
filedir=[]; % 指定文件路径
filename='bluesky3.wav'; % 指定文件名
fle=[filedir filename];
%[s,fs]=wavread(fle); % 读入数据文件
[s,fs]=audioread(fle); % 读入数据文件
s=s-mean(s); % 消除直流分量
s=s/max(abs(s)); % 幅值归一化
N=length(s); % 求出数据长度
time=(0:N-1)/fs; % 求出时间刻度
subplot 411; plot(time,s,'k'); % 画出纯语音信号的波形图
title('纯语音信号'); ylabel('幅值')
SNR=[15 5 0]; % 信噪比的取值区间
for k=1 : 3
snr=SNR(k); % 设定信噪比
[x,noise]=Gnoisegen(s,snr); % 求出相应信噪比的高斯白噪声,构成带噪语音
subplot(4,1,k+1); plot(time,x,'k'); ylabel('幅值'); % 作图
snr1=SNR_singlech(s,x); % 计算出带噪语音中的信噪比
fprintf('k=%4d snr=%5.1f snr1=%5.4f\n',k,snr,round(snr1*1e4)/1e4);
title(['带噪语音信号 设定信噪比=' num2str(snr) 'dB 计算出信噪比=' ...
num2str(round(snr1*1e4)/1e4) 'dB']);
end
xlabel('时间/s')
function [y,noise] = Gnoisegen(x,snr)
% Gnoisegen函数是叠加高斯白噪声到语音信号x中
% [y,noise] = Gnoisegen(x,snr)
% x是语音信号,snr是设置的信噪比,单位为dB
% y是叠加高斯白噪声后的带噪语音,noise是被叠加的噪声
noise=randn(size(x)); % 用randn函数产生高斯白噪声
Nx=length(x); % 求出信号x长
signal_power = 1/Nx*sum(x.*x); % 求出信号的平均能量
noise_power=1/Nx*sum(noise.*noise);% 求出噪声的能量
noise_variance = signal_power / ( 10^(snr/10) ); % 计算出噪声设定的方差值
noise=sqrt(noise_variance/noise_power)*noise; % 按噪声的平均能量构成相应的白噪声
y=x+noise; % 构成带噪语音
function snr=SNR_singlech(I,In)
% 计算带噪语音信号的信噪比
% I 是纯语音信号
% In 是带噪的语音信号
% 信噪比计算公式是
% snr=10*log10(Esignal/Enoise)
I=I(:)'; % 把数据转为一列
In=In(:)';
Ps=sum((I-mean(I)).^2); % 信号的能量
Pn=sum((I-In).^2); % 噪声的能量
snr=10*log10(Ps/Pn); % 信号的能量与噪声的能量之比,再求分贝值
%
% pr5_3_2
clear all; clc; close all;
filedir=[]; % 指定文件路径
filename='bluesky3.wav'; % 指定文件名
fle=[filedir filename];
% [s,fs]=wavread(fle); % 读入数据文件
[s,fs]=audioread(fle); % 读入数据文件
s=s-mean(s); % 消除直流分量
s=s/max(abs(s)); % 幅值归一化
N=length(s); % 求出数据长度
time=(0:N-1)/fs; % 求出时间刻度
subplot 411; plot(time,s,'k'); % 画出纯语音信号的波形图
title('纯语音信号'); ylabel('幅值')
SNR=[5 0 -5]; % 信噪比的取值区间
for k=1 : 3
snr=SNR(k); % 设定信噪比
data=sin(2*pi*100*time); % 产生一个正弦信号
[x,noise]=add_noisedata(s,data,fs,fs,snr); % 按信噪比构成正弦信号叠加到语音上
subplot(4,1,k+1); plot(time,x,'k'); ylabel('幅值'); % 作图
ylim([-2 2]);
snr1=SNR_singlech(s,x); % 计算出带噪语音中的信噪比
fprintf('k=%4d snr=%5.1f snr1=%5.4f\n',k,snr,snr1);
title(['带噪语音信号 设定信噪比=' num2str(snr) 'dB 计算出信噪比=' ...
num2str(round(snr1*1e4)/1e4) 'dB']);
end
xlabel('时间/s')
function [signal,noise]=add_noisedata(s,data,fs,fs1,snr)
s=s(:); % 把信号转换成列数据
s=s-mean(s); % 消除直流分量
sL=length(s); % 求出信号的长度
if fs~=fs1 % 若纯语音信号的采样频率与噪声的采样频率不相等
x=resample(data,fs,fs1); % 对噪声重采样,使噪声采样频率与纯语音信号的采样频率相同
else
x=data;
end
x=x(:); % 把噪声数据转换成列数据
x=x-mean(x); % 消除直流分量
xL=length(x); % 求噪声数据长度
if xL>=sL % 如果噪声数据长度与信号数据长度不等,把噪声数据截断或补足
x=x(1:sL);
else
disp('Warning: 噪声数据短于信号数据,以补0来补足!')
x=[x; zeros(sL-xL,1)];
end
Sr=snr;
Es=sum(s.*s); % 求出信号的能量
Ev=sum(x.*x); % 求出噪声的能量
a=sqrt(Es/Ev/(10^(Sr/10))); % 计算出噪声的比例因子
noise=a*x; % 调整噪声的幅值
signal=s+noise; % 构成带噪语音
%
% pr5_3_3
clear all; clc; close all;
filedir=[]; % 指定文件路径
filename='bluesky3.wav'; % 指定文件名
fle=[filedir filename];
% [s,fs]=wavread(fle); % 读入数据文件
[s,fs]=audioread(fle); % 读入数据文件
s=s-mean(s); % 消除直流分量
s=s/max(abs(s)); % 幅值归一化
N=length(s); % 求出数据长度
time=(0:N-1)/fs; % 求出时间刻度
subplot 411; plot(time,s,'k'); % 画出纯语音信号的波形图
title('纯语音信号'); ylabel('幅值')
filepath_name='factory1.wav';
SNR=[5 0 -5]; % 信噪比的取值区间
for k=1 : 3
snr=SNR(k); % 设定信噪比
[x,noise]=add_noisefile(s,filepath_name,snr,fs); % 按信噪比构成噪声叠加到语音上
subplot(4,1,k+1); plot(time,x,'k'); ylabel('幅值'); % 作图
ylim([-2 2]);
snr1=SNR_singlech(s,x); % 计算出带噪语音中的信噪比
fprintf('k=%4d snr=%5.1f snr1=%5.4f\n',k,snr,snr1);
title(['带噪语音信号 设定信噪比=' num2str(snr) 'dB 计算出信噪比=' ...
num2str(round(snr1*1e4)/1e4) 'dB']);
end
xlabel('时间/s')
function [y,noise] = add_noisefile(s,filepath_name,SNR,fs)
s=s(:); % 把信号转换成列数据
s=s-mean(s); % 消除直流分量
% [wavin,fs1,nbits]=wavread(filepath_name); %读入噪声文件的数据
[wavin,fs1]=audioread(filepath_name); %读入噪声文件的数据
wavin=wavin(:); % 把噪声数据转换成列数据
if fs1~=fs % 纯语音信号的采样频率与噪声的采样频率不相等
wavin1=resample(wavin,fs,fs1); % 对噪声重采样,使噪声采样频率与纯语音信号的采样频率相同
else
wavin1=wavin;
end
wavin1=wavin1-mean(wavin1); % 消除直流分量
ns=length(s); % 求出s的长度
noise=wavin1(1:ns); % 把噪声长度截断为与s等长
noise=noise-mean(noise); % 噪声去除直流分量
signal_power = 1/ns*sum(s.*s); % 求出信号的能量
noise_power=1/ns*sum(noise.*noise); % 求出噪声的能量
noise_variance = signal_power / ( 10^(SNR/10) ); % 求出噪声设定的方差值
noise=sqrt(noise_variance/noise_power)*noise; % 调整噪声幅值
y=s+noise; % 构成带噪语音
%
% pr5_4_1
clear all; clc; close all;
% [x,fs,nbit]=wavread('bluesky31.wav'); % 读入bluesky31.wav文件
[x,fs]=audioread('bluesky31.wav'); % 读入bluesky31.wav文件
t=(0:length(x)-1)/fs; % 设置时间
y=detrend(x); % 消除线性趋势项
y=y/max(abs(y)); % 幅值归一化
subplot 211; plot(t,x,'k'); % 画出带有趋势项的语音信号x
title('带趋势项的语音信号');
xlabel('时间/s'); ylabel('幅值');
subplot 212; plot(t,y,'k'); % 画出消除趋势项的语音信号y
xlabel('时间/s'); ylabel('幅值');
title('消除趋势项的语音信号');
%
% pr5_4_2
clear all; clc; close all;
% [x,fs,nbit]=wavread('bluesky32.wav'); % 读入bluesky32.wav文件
[x,fs]=audioread('bluesky32.wav'); % 读入bluesky31.wav文件
[y,xtrend]=polydetrend(x, fs, 2); % 调用polydetrend消除趋势项
t=(0:length(x)-1)/fs; % 设置时间
subplot 211; plot(t,x,'k'); % 画出带有趋势项的语音信号x
line(t,xtrend,'color',[.6 .6 .6],'linewidth',3); % 画出趋势项曲线
ylim([-1.5 1]);
title('带趋势项的语音信号');
legend('带趋势项的语音信号','趋势项信号',4)
xlabel('时间/s'); ylabel('幅值');
subplot 212; plot(t,y,'k'); % 画出消除趋势项的语音信号y
xlabel('时间/s'); ylabel('幅值');
title('消除趋势项的语音信号');
%
% pr5_5_1
clear all; clc; close all;
fp=500; fs=750; % 设置滤波器的通带和阻带频率
Fs=8000; Fs2=Fs/2; % 采样频率
Wp=fp/Fs2; Ws=fs/Fs2; % 把通带和阻带频率归一化
Rp=3; Rs=50; % 通带波纹和阻带衰减
[n,Wn]=cheb2ord(Wp,Ws,Rp,Rs); % 求取滤波器阶数
[b,a]=cheby2(n,Rs,Wn); % 设计契比雪夫II型低通滤波器系数
[db,mag,pha,grd,w]=freqz_m(b,a); % 求滤波器的频率响应曲线
filedir=[]; % 指定文件路径
filename='bluesky3.wav'; % 指定文件名
fle=[filedir filename] % 构成路径和文件名的字符串
% [s,fs]=wavread(fle); % 读入数据文件
[s,fs]=audioread(fle); % 读入数据文件
s=s-mean(s); % 消除直流分量
s=s/max(abs(s)); % 幅值归一化
N=length(s); % 求出信号长度
t=(0:N-1)/fs; % 设置时间
y=filter(b,a,s); % 把语音信号通过滤波器
wlen=200; inc=80; nfft=512; % 设置帧长,帧移和nfft长
win=hann(wlen); % 设置窗函数
d=stftms(s,win,nfft,inc); % 原始信号的STFT变换
fn=size(d,2); % 获取帧数
frameTime=(((1:fn)-1)*inc+nfft/2)/Fs; % 计算每帧对应的时间--时间轴刻度
W2=1+nfft/2; % 计算频率轴刻度
n2=1:W2;
freq=(n2-1)*Fs/nfft;
d1=stftms(y,win,nfft,inc); % 滤波后信号的STFT变换
% 作图
figure(1)
plot(w/pi*Fs2,db,'k','linewidth',2)
grid; axis([0 4000 -100 5]);
title('低通滤波器的幅值响应曲线')
xlabel('频率/Hz'); ylabel('幅值/dB');
figure(2)
subplot 211; plot(t,s,'k');
title('纯语音信号:男声“蓝天,白云”')
xlabel(['时间/s' 10 '(a)']); ylabel('幅值')
subplot 212; imagesc(frameTime,freq,abs(d(n2,:))); axis xy
title('纯语音信号的语谱图')
xlabel(['时间/s' 10 '(b)']); ylabel('频率/Hz')
m = 256;
LightYellow = [0.6 0.6 0.6];
MidRed = [0 0 0];
Black = [0.5 0.7 1];
Colors = [LightYellow; MidRed; Black];
colormap(SpecColorMap(m,Colors));
figure(3)
subplot 211; plot(t,y,'k');
title('滤波后的语音信号')
xlabel(['时间/s' 10 '(a)']); ylabel('幅值')
subplot 212; imagesc(frameTime,freq,abs(d1(n2,:))); axis xy
title('滤波后语音信号的语谱图')
xlabel(['时间/s' 10 '(b)']); ylabel('频率/Hz')
m = 256;
LightYellow = [0.6 0.6 0.6];
MidRed = [0 0 0];
Black = [0.5 0.7 1];
Colors = [LightYellow; MidRed; Black];
colormap(SpecColorMap(m,Colors)); ylim([0 1000]);
%
% pr5_5_2
clear all; clc; close all;
As=50;Fs=8000; Fs2=Fs/2; % 阻带最小衰减和采样频率
fp=75; fs=60; % 通带阻带频率
df=fp-fs; % 求取过渡带
M0=round((As-7.95)/(14.36*df/Fs))+2; % 按式(5-5-4)求凯泽窗长
M=M0+mod(M0+1,2); % 保证窗长为奇数
wp=fp/Fs2*pi; ws=fs/Fs2*pi; % 转为圆频率
wc=(wp+ws)/2; % 求取截止频率
beta=0.5842*(As-21)^0.4+0.07886*(As-21);% 按式(5-5-5)求出beta值
fprintf('beta=%5.6f\n',beta); % 显示beta的数值
w_kai=(kaiser(M,beta))'; % 求凯泽窗
hd=ideal_lp(pi,M)-ideal_lp(wc,M); % 求理想滤波器的脉冲响应(高通滤波器的组合)
b=hd.*w_kai; % 理想脉冲响应与窗函数相乘
[h,w]=freqz(b,1,4000); % 求频率响应
db=20*log10(abs(h));
filedir=[]; % 指定文件路径
filename='bluesky3.wav'; % 指定文件名
fle=[filedir filename] % 构成路径和文件名的字符串
% [s,fs]=wavread(fle); % 读入数据文件
[s,fs]=audioread(fle); % 读入数据文件
s=s-mean(s); % 消除直流分量
s=s/max(abs(s)); % 幅值归一化
N=length(s); % 求出信号长度
t=(0:N-1)/fs; % 设置时间
ns=0.5*cos(2*pi*50*t); % 计算出50Hz工频信号
x=s+ns'; % 语音信号和50Hz工频信号叠加
snr1=SNR_singlech(s,x) % 计算叠加50Hz工频信号后的信噪比
y=conv(b,x); % FIR带陷滤波,输出为y
% 作图
figure(1)
plot(w/pi*Fs2,db,'k','linewidth',2); grid;
axis([0 150 -100 10]);
title('幅频响应曲线');
xlabel('频率/Hz');ylabel('幅值/dB');
figure(2)
subplot 311; plot(t,s,'k');
title('纯语音信号:男声“蓝天,白云”')
xlabel('时间/s'); ylabel('幅值')
axis([0 max(t) -1.2 1.2]);
subplot 312; plot(t,x,'k');
title('带50Hz工频信号的语音信号')
xlabel('时间/s'); ylabel('幅值')
axis([0 max(t) -1.2 1.2]);
z=y(fix(M/2)+1:end-fix(M/2)); % 消除conv带来的滤波器输出延迟的影响
snr2=SNR_singlech(s,z) % 计算滤波后语音信号的信噪比
subplot 313; plot(t,z,'k');
title('消除50Hz工频信号后的语音信号')
xlabel('时间/s'); ylabel('幅值')
axis([0 max(t) -1.2 1.2]);
%
% pr5_5_3
clear all; clc; close all;
As=50;Fs=8000; Fs2=Fs/2; % 最小衰减和采样频率
fs1=49; fs2=51; % 阻带频率
fp1=45; fp2=55; % 通带频率
df=min(fs1-fp1,fp2-fs2); % 求过渡带宽
M0=round((As-7.95)/(14.36*df/Fs))+2; % 按式(5-5-4)求凯泽窗长
M=M0+mod(M0+1,2); % 保证窗长为奇数
wp1=fp1/Fs2*pi; wp2=fp2/Fs2*pi; % 转换成归一化圆频率
ws1=fs1/Fs2*pi; ws2=fs2/Fs2*pi;
wc1=(wp1+ws1)/2; wc2=(wp2+ws2)/2; % 求截止频率
beta=0.5842*(As-21)^0.4+0.07886*(As-21);% 按式(5-5-5)求出beta值
fprintf('beta=%5.6f\n',beta);
M=M-1; % 阶次和窗长差1
b=fir1(M,[wc1 wc2]/pi,'stop',kaiser(M+1,beta)); % 计算FIR滤波器系数
[h,w]=freqz(b,1,4000); % 求幅值的频率响应
db=20*log10(abs(h));
filedir=[]; % 指定文件路径
filename='bluesky3.wav'; % 指定文件名
fle=[filedir filename] % 构成路径和文件名的字符串
% [s,fs]=wavread(fle); % 读入数据文件
[s,fs]=audioread(fle); % 读入数据文件
s=s-mean(s); % 消除直流分量
s=s/max(abs(s)); % 幅值归一化
N=length(s); % 求出信号长度
t=(0:N-1)/fs; % 设置时间
ns=0.5*cos(2*pi*50*t); % 计算出50Hz工频信号
x=s+ns'; % 语音信号和50Hz工频信号叠加
snr1=SNR_singlech(s,x) % 计算叠加50Hz工频信号后的信噪比
y=conv(b,x); % FIR带陷滤波,输出为y
z=y(fix(M/2)+1:end-fix(M/2)); % 消除conv带来的滤波器输出延迟的影响
snr2=SNR_singlech(s,z) % 计算滤波后语音信号的信噪比
% 作图
figure(1)
plot(w/pi*Fs2,db,'k','linewidth',2);
title('幅频响应曲线');
xlabel('频率/Hz');ylabel('幅值/dB');grid on; axis([0 100 -60 5])
figure(2)
subplot 311; plot(t,s,'k');
title('纯语音信号:男声“蓝天,白云”')
xlabel('时间/s'); ylabel('幅值')
axis([0 max(t) -1.2 1.2]);
subplot 312; plot(t,x,'k');
title('带50Hz工频信号的语音信号')
xlabel('时间/s'); ylabel('幅值')
axis([0 max(t) -1.2 1.2]);
subplot 313; plot(t,z,'k');
title('消除50Hz工频信号后的语音信号')
xlabel('时间/s'); ylabel('幅值')
axis([0 max(t) -1.2 1.2]);
语音信号的滤波预处理是显示生活中我们最常遇见的,实际应用场景下,噪声无处不在,而且各种各样的噪声,千奇百怪,非常让人头疼,本章就是介绍常见的噪声类型,以及最常用的滤波处理方法,实现语音滤波噪声,进而实现语音增强的效果。
最近正好在处理一个环境噪声影响语音识别的问题,确实比较头疼,只能一步一步先从简单的滤波开始,后期在进行比较深入的复杂环境的符合噪声处理吧。对本章内容感兴趣或者想充分学习了解的,建议去研习书中第五章节的内容。后期会对其中一些知识点在自己理解的基础上进行讨论补充,欢迎大家一起学习交流。
关于宋老师:宋知用——默默传授MATLAB与信号处理知识的老人家
本系列文章列表如下:
《MATLAB语音信号分析与合成(第二版)》:第2章 语音信号的时域、频域特性和短时分析技术
《MATLAB语音信号分析与合成(第二版)》:第3章 语音信号在其他变换域中的分析技术和特性
《MATLAB语音信号分析与合成(第二版)》:第4章 语音信号的线性预测分析
《MATLAB语音信号分析与合成(第二版)》:第5章 带噪语音和预处理
《MATLAB语音信号分析与合成(第二版)》:第6章 语音端点的检测(1)
《MATLAB语音信号分析与合成(第二版)》:第6章 语音端点的检测(2)
《MATLAB语音信号分析与合成(第二版)》:第7章 语音信号的减噪
《MATLAB语音信号分析与合成(第二版)》:第8章 基音周期的估算方法
《MATLAB语音信号分析与合成(第二版)》:第9章 共振峰的估算方法
《MATLAB语音信号分析与合成(第二版)》:第10章 语音信号的合成算法