简介:面向语音信号处理初学者和研究者的一份MATLAB实验教程资源包,聚焦实际环境中常见的语音降噪问题,适合高校学生、科研人员以及人工智能相关从业者在学习语音识别、大数据分析时通过代码实践掌握核心算法。压缩包共35个文件,内含26个可直接运行的MATLAB源码文件、4个用于测试的wav语音样本和5张说明示意图,整体大小仅409KB,结构清晰,下载后即可按章节依次运行。内容围绕第5章语音降噪主题,系统覆盖自适应滤波器法(LMS)、谱减法、维纳滤波法、小波分解法四大模块,每种方法均配套完整MATLAB代码与语音样例;谱减法通过估计噪声功率谱去除加性噪声,维纳滤波在频域实现最优估计,小波分解则利用软硬阈值保留语音细节,读者可在实际调试中深入理解不同算法的适用条件及SNR、PESQ等评估指标。目前已有210人学习下载,非常适合希望提升复杂环境下语音识别准确率、并愿意通过动手实验巩固理论的研究者和学生。
1. 语音降噪实验包:把谱减法跑通之前,先想清楚这三件事
语音降噪在MATLAB里跑通不难,难的是把参数调到听着自然又不失真。手头这份语音信号处理实验教程,把从音频读取、分帧加窗到谱减降噪,再到MFCC特征提取的完整链路做成了一段段可运行的源码。它不是让你背原理的PPT,而是让你在键盘上跟着走一遍,亲眼看到噪声谱被削掉的过程。拿到这份资源,我建议你先别急着往下跑,先把采样率、帧长和过减因子这三个参数的关系想明白,否则每一步都可能翻车。适合两类人:做语音信号处理课设的学生,以及在实际项目里被环境噪声反复折腾的算法工程师。
2. MATLAB基础:音频读取与预处理的三个关键环节
语音信号处理的起点不是算法,而是数据怎么进内存、怎么变成时频表示。很多照着实验包跑挂的人,问题都出在读取和预处理阶段。这章把三个一定要亲手敲一遍的环节拆开讲,每个都配了可以直接跑的代码。
2.1 音频文件读取:audioread与采样率参数
在MATLAB里读音频,现在官方推荐的函数是audioread,老教程里常见的wavread在新版本中已经标记为待移除。两者的返回值格式基本一致,第一个返回音频数据,第二个返回采样率。我一开始用wavread跑老代码,报错之后换到audioread才顺畅。这份实验包里的脚本也统一用了audioread,你在自己的数据上复现时不需要改读取这层。
[y, fs] = audioread('speech.wav'); disp(['采样率: ', num2str(fs)]); sound(y, fs); % 播放原始音频这段代码的作用是把wav文件读入内存,并输出采样率供后续处理参考。注意audioread返回的y是列向量,如果文件是双声道会返回Nx2矩阵,后续的buffer分帧函数要求输入是列向量,所以读到立体声数据时得先取单声道,常见的做法是直接y = mean(y, 2)。采样率fs决定了后续帧长的物理含义,比如fs=16000时,320点对应20ms;如果文件实际是44100Hz而代码默认16kHz,帧长对应的时长就全错了。这也是我在第5章避坑部分会重点讲的采样率不匹配问题。
读取之后还有一步经常被忽略,就是预加重。语音信号的高频分量通常能量偏低,直接做频谱分析会低估高频段噪声的影响。
y = filter([1, -0.97], 1, y);预加重是语音信号处理里几乎必做的第一步,目的是提升高频分量。滤波器系数-0.97是常见的经验值,如果换成0.95高频提升更少,对清音声母的影响会更小。预加重之后的信号再去做分帧,频谱会相对平坦,特征提取和噪声估计都更稳定。这里我一般会把y先转成double再滤波,如果原始数据是int16类型,直接filter可能会产生意外的截断。
2.2 分帧加窗:为什么非这么做不可
语音是短时平稳信号,这个说法在教科书里反复出现,落到代码上就是对连续采样点切块。一帧取多少点直接关系到频率分辨率:frame_len=320(20ms@16kHz),配512点FFT,每个频点间隔约31.25Hz。这个分辨率对人声基频的精细结构够用,但对噪声谱的细节就显得粗糙。如果把帧长加到640点,频率分辨率会更好,但语音的短时平稳假设就变弱,导致帧内频谱被“平滑”掉。折中方案还是320到640之间。
frame_len = 320; % 20ms @ 16kHz hop = 160; % 帧移10ms,相邻帧重叠50% frames = buffer(y, frame_len, frame_len - hop, 'nodelay'); win = hamming(frame_len); frames_win = frames .* repmat(win, 1, size(frames, 2));buffer函数的参数含义:y是输入列向量,frame_len是每帧长度,第三个参数是重叠样本数。frame_len - hop = 160表示当前帧前160点和上一帧重叠,这样既覆盖完整信号,又避免帧边缘幅值跳变。加窗选择hamming,主瓣和旁瓣折中最好;如果只是演示可以用rectwin,但rectwin在频域会产生明显泄漏,噪声估计会因此偏高。repmat把窗向量复制成和frames相同的列数,保证点乘时维度匹配。这里最容易踩的坑是buffer得到的frames有NaN填充的尾巴,处理时最好手工截掉最后一帧。
2.3 短时傅里叶变换:让频谱成为可见的物理量
加窗之后就可以对每一帧做FFT。这里我推荐先用MATLAB内置的spectrogram函数直接看语谱图,确认数据没有问题,再进入手动实现:
nfft = 512; spectrogram(y, win, hop, nfft, fs, 'yaxis');spectrogram的参数从左到右依次是信号、窗、重叠点数、FFT点数、采样率。yaxis让频率轴以kHz为单位显示。如果语谱图上高频区域有持续的细带,说明原始数据里有周期性噪声,这类噪声靠谱减法很难干净移除,要先用陷波器处理。看语谱图是每个做语音降噪的人都要养的直觉,否则后面参数调得再漂亮都不清楚问题出在哪。
手动实现STFT则更接近这份代码包的内部逻辑:
spec = fft(frames_win, nfft, 1); mag = abs(spec(1:nfft/2+1, :)); phase = angle(spec(1:nfft/2+1, :));fft第二维指定1,表示对每一列做FFT。取前nfft/2+1个点是因为实数信号的幅度谱关于奈奎斯特频率对称,后半段没有额外信息。phase变量在谱减法里必须被保存,因为最终重建时要用原始相位加上修正后的幅度谱。人耳对相位不敏感,但不保留相位就无法通过逆FFT重建出像样的时域信号。很多新手的第一个翻车点就是把复数谱直接abs扔掉,重建出来的声音全是噪声。我在这份实验包里看到它保留了相位,这个细节值得学习。跑完这段流程,你就有了一个完整的时频表示,后面的谱减法和MFCC特征提取,操作对象都不是原始波形,而是这个mag矩阵和对应的相位矩阵。
3. 谱减法降噪:从原理到可运行的MATLAB代码
谱减法是语音增强里最经典的入门算法,也是这份实验包的核心模块。它的数学形式简单,但真正调试起来有不少需要拿捏的地方。这一章从原理讲到完整实现,代码可直接抄进你自己的工程。
3.1 谱减法的数学原理与适用边界
谱减法的出发点非常直观:假设噪声是加性的、相对平稳的,那么带噪语音的幅度谱减去噪声幅度谱的估计,就能得到干净语音的幅度谱估计。写成程序就是带噪幅度谱减去过减因子乘噪声谱。
% 幅度谱减法: |Y| - alpha * |N_hat| % 其中 |Y| 是带噪语音幅度谱, |N_hat| 是噪声幅度谱估计 mag_clean = mag - alpha * noise_mag;做这一步之前必须先有noise_mag,通常取信号起始的若干帧,假设语音还没开始、只有噪声,用它们的平均幅度谱当作噪声谱估计。适用边界:平稳噪声效果好,非平稳噪声效果差;信噪比低于5dB时,谱减法容易引入“音乐噪声”。这一点我在实验包的说明文档里也看到过类似的表述,实际跑下来确实如此。如果背景是键盘敲击声这种瞬态噪声,谱减法基本束手无策,得换维纳滤波或者深度学习方法。
3.2 过减因子与地板值的参数实验
参数是这个算法真正的玄学所在。我跑过的经验是:过减因子alpha取2.0到3.0之间,alpha越大对噪声抑制越强,但语音的幅度谱被过度减去,重构出来的声音会发闷、像蒙了一层布。floor_val(地板值)取0.01到0.1,目的是防止某些频点幅值变成负数或零,负幅值会导致重建信号出现刺耳爆破音。两个参数配合起来才能平衡噪声抑制和语音失真。
| 参数 | 取值区间 | 取值偏大的表现 | 取值偏小的表现 |
|---|---|---|---|
| 过减因子 alpha | 2.0 ~ 3.0 | 语音失真明显、气声感重 | 噪声残留明显 |
| 地板系数 floor_val | 0.01 ~ 0.1 | 背景噪声呈持续沙沙声 | 部分频点幅值被削成零,出现爆破音 |
配合代码来看:
alpha = 2.5; floor_val = 0.05; noise_mag = mean(mag(:, 1:10), 2); % 取前10帧平均作为噪声估计 mag_est = max(mag - alpha * noise_mag, floor_val * noise_mag);第一行设置过减因子,第二行设置地板值,第三行是噪声估计,只用前10帧。关键参数在于取前几帧这个窗口长度:窗口太短,噪声估计方差大;太长,可能把语音起始部分也平均进去,导致语音头被削弱。对一般录音数据,10到20帧是个安全范围。小实验我通常会写两层循环扫alpha和floor_val,但凡是批量跑,就得结合后面的信噪比验证,不能只看一两个文件的听感。
3.3 完整降噪函数与重建流程
把前面所有步骤串成一个完整函数,方便批量调用:
function [y_clean] = spec_sub(y, fs, alpha, floor_val) frame_len = 320; hop = 160; nfft = 512; y = y(:); % 转为列向量 frames = buffer(y, frame_len, frame_len - hop, 'nodelay'); win = hamming(frame_len); frames_win = frames .* repmat(win, 1, size(frames, 2)); spec = fft(frames_win, nfft, 1); mag = abs(spec(1:nfft/2+1, :)); phase = angle(spec(1:nfft/2+1, :)); noise_mag = mean(mag(:, 1:10), 2); mag_est = max(mag - alpha * noise_mag, floor_val * noise_mag); spec_est = mag_est .* exp(1i * phase); % 保留原相位 frames_est = real(ifft(spec_est, nfft, 1)); y_clean = overlap_add(frames_est, hop); end逻辑说明:先对带噪语音分帧加窗和FFT,得到幅度谱与相位。用前10帧平均估计噪声幅度谱,再按谱减法公式得到干净的幅度谱估计,乘上原始相位构成新的复数谱。对每一帧做IFFT,最后用overlap-add方式把帧序列重建成时域信号。overlap-add实现时,相邻帧相加的重叠区域要除以窗函数的平方和,否则音量会周期性起伏。我一般不在实验里手动写overlap-add,直接用MATLAB的istft相关工具,但如果你要在老版本上跑,最好自己写一版并验证重建信号的能量和原始信号一致。
参数说明:alpha和floor_val作为函数入口,控制降噪强度。调用时把函数放在单独的m文件里,或者直接追加在脚本末尾,注意函数名和文件名要一致。跑通一次之后,把alpha改成2.0、2.5、3.0,分别听一遍,你就能直观感受到这个参数对音色的影响有多大。
4. 语音特征提取与识别联动:MFCC计算流程
降噪算法最终往往是为了语音识别或声学分析服务的。这份实验包在降噪之外还带了一套MFCC特征计算脚本,正好把“降噪之后怎么用”落到了代码上。
4.1 MFCC提取五步流程
MFCC是语音识别里最经典的特征,它模拟人耳对频率的非线性感知。完整流程是预加重、分帧加窗、计算功率谱、经过Mel滤波器组、取对数后做DCT。前两步和第2章完全一致,从第三步开始有区别。
mel_bank = designMelFilterbank(fs, nfft, 26); power_spec = mag .^ 2; mel_spec = mel_bank * power_spec; log_mel = log(mel_spec + eps); mfcc_all = dct(log_mel'); mfcc = mfcc_all(:, 2:13);designMelFilterbank就是Mel滤波器组的设计函数,26代表滤波器个数,常用范围24到40。滤波器在低频分布密、高频分布稀,这和人对频率的感知是对数的相匹配。power_spec用幅度谱的平方得到功率谱。mel_spec是把滤波器组和功率谱做矩阵乘法,得到每个Mel频带的能量。第四步加eps是为了防止log(0)产生负无穷,这个细节漏掉的话,静音帧会出现NaN,后面分类器直接崩。最后DCT把高维的滤波器组输出压缩到低维,取第2到第13列是因为第一维是整体能量,对录音响度敏感,在识别任务里反而会引入干扰。
4.2 特征参数与识别效果的关系
MFCC维数不是越多越好。13维是经典配置,加上一阶差分delta得到26维,二阶差分得到39维。我实践中看到的情况是:在干净语音上,13维和39维差别不大;在降噪后的有损语音上,多出来的差分维经常被噪声放大,反而拉低识别率。所以单独评估降噪效果时用13维就够了。
如果你用的是MATLAB的Audio Toolbox,可以直接调用mfcc函数,它会返回每个帧的特征矩阵。但内置函数隐藏了滤波器组细节,调试时不容易定位问题。这份实验包用手动实现的好处就是每个环节都可以打印中间矩阵的尺寸和数值,方便讲原理也方便自己排查。我建议你至少手写一遍,再换成内置函数验证结果。我在自己的模拟项目X里就是先跑手动MFCC,再对比工具箱输出,确认误差在1e-6量级之后才放心把特征喂给分类器。
5. 避坑指南:语音信号处理中的常见问题与排查
这一章是血泪经验的汇总。我在拆这个实验包和用它处理真实录音时,遇到过几个非常典型的问题,每条都按现象、原因、解决的顺序列出来,你照着排查就行。
5.1 现象:降噪后语音发闷,像蒙在被子里
原因:过减因子偏大,或者对高频频段的幅度谱减得太多。人耳对中高频的衰减非常敏感,高频一塌,立刻觉得闷。解决方式是把alpha从2.5降到2.0,同时观察频谱图,高频部分如果基本贴到floor_val,说明减过头了。我一般还会把floor_val从0.01提到0.05,让高频保留一点点噪声,反而会让听感更自然。注意alpha合适值跟采样率也有关系,16kHz下2.0好用的参数,放到8kHz数据上要重新调。
5.2 现象:报错“数组维度不匹配”
原因:分帧后的帧数和噪声幅度谱的维度对不上。noise_mag是nfft/2+1行1列,mag是nfft/2+1行K列,MATLAB矩阵减法可以广播,如果噪声估计长度不足或帧数不够,维度就对不上。解决方式是在任何一步之后加上size检查:
size(mag) size(noise_mag)我在跑实验包时遇到过buffer产生的帧数比预期多一帧,因为nodelay模式下信号末尾不足一帧也会被填充。处理方式是把y截断到帧长的整数倍加上hop偏移,或者忽略最后一帧。更稳妥的做法是用assert检查:
assert(size(mag, 1) == size(noise_mag, 1), '维度不匹配');5.3 现象:静音段噪声反而变大,出现“音乐噪声”
原因:谱减法对每个频点独立操作,估计的噪声与真实噪声存在偏差,部分频点幅值被过减后产生随机谱线,听感上像背景里有断续的“水声”或“电子音”。解决方式:一是对谱减后的幅度谱做时域递归平滑,比如:
mag_smooth = zeros(size(mag_est)); mag_smooth(:, 1) = mag_est(:, 1); for i = 2:size(mag_est, 2) mag_smooth(:, i) = 0.8 * mag_smooth(:, i-1) + 0.2 * mag_est(:, i); end二是对floor_val做更保守的设置,不要低于0.03;三是用随信噪比自适应的过减因子,信噪比高时alpha小一点,信噪比低时alpha大一点。音乐噪声是谱减法的老毛病,完全消除不现实,但可以把它控制在“几乎听不见”的程度。
5.4 现象:不同采样率下处理结果完全不对
原因:代码里写死了frame_len=320,nfft=512,这些数值只在16kHz下是正确的。如果wav文件是8kHz或44.1kHz,320点对应的时长就会加倍或减半,语谱图的频率轴也会错位。解决方式是在程序开头加一行断言:
assert(fs == 16000, '本实验包默认16kHz采样率');或者多写一组8kHz的参数分支。另一个相关坑是audioread返回的采样率有时带小数点,比如一些wav自带44100.0,传给spectrogram没问题,手动计算频率轴时要先转成double再参与运算。采样率不匹配的问题用户经常忽略,但一出错就是全局翻车,建议所有工程入口都做一致性检查。
6. 进阶:批量降噪脚本与信噪比验证
当实验数据不止一个文件,手动逐个跑肯定不现实。我会写一个批量脚本,扫描目录下的所有wav文件,分别做谱减降噪并保存:
files = dir('*.wav'); for k = 1:numel(files) [y, fs] = audioread(files(k).name); if fs ~= 16000 fprintf('跳过 %s,采样率 %d\n', files(k).name, fs); continue; end y_clean = spec_sub(y, fs, 2.5, 0.05); out_name = ['enhanced_', files(k).name]; audiowrite(out_name, y_clean, fs); end这段脚本对采样率不一致的文件直接跳过,避免报错中断。audiowrite写入时默认输出double数组,如果原始音频是PCM16位,转成double会引入很小的量化噪声,但不影响实验结论。如果想保持位深一致,可以指定输出数据类型。
降噪效果不能只靠耳朵判断,要量化。最简单的方法是计算降噪前后的信噪比改善:
noise = y - y_clean; % 残差噪声 SNR_before = 10 * log10(sum(y_clean.^2) / sum(noise.^2));更严谨的做法是准备干净参考信号。如果你手头只有带噪语音没有干净参考,一个替代思路是把降噪前后的高能量段和低能量段分别计算,观察低能量段的噪声底是否被压低。我一般会同时记录过减因子、地板值、噪声估计帧数和最终的信噪比改善值,做成一张表。同一组数据上,alpha从2.0升到3.0时信噪比改善通常先升后降,当改善值开始下降时,就说明语音失真已经超过噪声抑制带来的收益,这个拐点就是你这次实验的最优参数。
从踩坑经历说:某次我批量处理一批真实录音,前几帧正好有人翻书的声音混在“静音段”,噪声估计被污染,导致全段降噪结果都有咔嗒声。从那以后我每次跑批量脚本前,都会先随机抽两个文件用spectrogram看一眼前500ms是不是真的只有平稳噪声,再决定要不要用自动选择静音段的方法重新估计噪声。这套流程帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取