资讯详情

MATLAB语音降噪实验:谱减法、维纳滤波与小波去噪实战指南

📅 2026/10/13 21:14:45 | 华诺云谱 👁 阅读
MATLAB语音降噪实验:谱减法、维纳滤波与小波去噪实战指南
简介语音信号处理实验教程第5章语音降噪的MATLAB源代码包面向学习语音信号处理、语音识别及人工智能方向的初学者和研究人员既适合课堂实验、课程设计也可作为实际项目中的降噪模块参考。压缩包共35个文件包括26个可直接运行的m脚本、4个wav语音样例和5张示意图资源总大小仅409KB便于快速下载与本地运行。代码按自适应滤波、谱减、维纳滤波、小波分解等不同算法分节组织覆盖多种经典降噪方案并附有信噪比计算、语音端点检测、分帧与重叠相加等辅助函数配合语音样例可直观对比不同算法的降噪效果并理解参数变化的影响。已有210人学习下载适合通过动手调试来掌握噪声模型、频域处理与滤波器设计的核心原理也有助于提升语音识别系统在复杂声学环境下的鲁棒性。1. 拿到语音降噪实验包先别急着跑这份教程在讲什么拿到一份“语音信号处理实验教程MATLAB源代码语音降噪”的压缩包解压后里面通常是一批 .m 文件和几个示例音频。跑通 demo 只需要几分钟把每个参数吃透至少要一个下午。这条链路是语音降噪里最经典的一条读入干净语音人为加噪用谱减或维纳滤波把噪声去掉再用信噪比和频谱图说明效果。它适合三类人——正在做语音信号处理课设的学生、刚接触语音增强的从业者、准备复试要现场讲实验思路的同学。要解决的是那个最常见的困惑降噪代码运行完波形看起来光滑了一播放却闷、有滋滋声完全不知道哪个参数出了问题。2. 先把带噪语音造出来数据准备、参数定标与可复现加噪在动手写降噪算法之前得先有一份可靠的实验素材。很多同学把压缩包里的示例音频跑完就算交差但真正要验证自己写的降噪函数必须自己控制“输入信号长什么样”。这段实验里最关键的两件事一是读入语音时把声道、采样率和数据范围搞清楚二是按指定信噪比叠加噪声并且保证每次运行结果一致。2.1 读取干净语音audioread、声道选择与定标细节实验的第一步是读入一段干净语音通常是 wav 文件。用 audioread 读回来的数据默认是 double 类型范围在 [-1, 1]但如果你从 .mat 文件里加载语音片段可能读到 int16 整型数据。这时候直接拿去和噪声叠加会出现严重的截断问题所以要做一个类型判断。% 读取干净语音确认采样率和声道数 [x, fs] audioread(clean.wav); x x(:, 1); % 只取第一声道避免双声道影响后续计算 if isinteger(x) % 从 .mat 读进来可能是 int16归一化到 [-1, 1] x double(x) / double(intmax(class(x))); end disp([采样率: , num2str(fs), Hz, 时长: , num2str(length(x)/fs), s]);这里x(:, 1)是常见做法实验里只要单声道就够了。isinteger判断是为了兜底防止从 .mat 或旧版本工具读入的数据类型不一致。采样率fs一定要保留好后续所有帧长、帧移都要按它换算不能写死。2.2 按信噪比加噪rng 固定随机种子结果才可复现加噪的逻辑是按目标信噪比反推噪声功率。比如想要 10 dB 的带噪语音就是让噪声功率是信号功率的十分之一。高斯白噪声用 randn 生成但 randn 每次调用结果都不同不固定随机种子的话同一个算法在不同次运行中输入都不一样对比实验就没意义了。function y add_noise(x, snr_db) % 给干净语音叠加高斯白噪声返回带噪信号 noise randn(size(x)); % 标准高斯白噪声 p_signal mean(x.^2); % 信号平均功率 p_noise p_signal / (10^(snr_db/10)); % 按信噪比反推噪声功率 noise noise * sqrt(p_noise / mean(noise.^2)); y x noise; end主脚本里调用前先执行rng(2024)这样每次生成的噪声序列完全一致。snr_db是可调参数做实验时建议从 10 dB 开始再往 0 dB、5 dB、15 dB 扩展。信噪比的定义用的是平均功率比不是峰值比注意这里mean(x.^2)是对整段语音求平均功率别写成sum否则不同长度的语音之间没法对比。2.3 分帧加窗与重叠帧帧长、帧移、窗函数的基本选择语音是非平稳信号但短时间20~30 ms内可以看作平稳的所以降噪算法都要先分帧。帧长取 25 ms 是语音处理里的常见默认值帧移取 10 ms相邻帧有 60% 重叠。重叠的目的是让重建时帧与帧之间平滑衔接避免块边界产生咔哒声。窗函数用汉明窗periodic参数让窗函数更适合做频谱分析。win_len round(0.025 * fs); % 帧长 25 ms hop round(0.010 * fs); % 帧移 10 ms重叠 60% n_frames floor((length(y) - win_len) / hop) 1; win hamming(win_len, periodic); frames zeros(n_frames, win_len); for i 1:n_frames idx (i-1)*hop 1 : (i-1)*hop win_len; frames(i, :) y(idx) .* win; % 逐帧加窗 end注意y(idx)取出来是列向量和窗函数相乘前要转置成行向量这是新手最容易报维度错误的地方。帧长不能写死成 256、512 这种点数因为不同采样率下同样的点数对应的时间长度完全不同。用0.025 * fs算出来的帧长在 16 kHz 采样率下是 400 点在 8 kHz 下是 200 点这样算法行为才一致。3. 谱减法实现三个参数决定降噪效果谱减法是语音降噪实验里最核心也最适合手写的算法。它的思路很直白带噪语音的幅度谱等于干净语音的幅度谱加上噪声的幅度谱既然噪声幅度谱可以从无声段估计出来那就直接相减。这个思路实现起来代码量不大但想调出能听的音质必须理解过减因子、噪声下限和噪声估计帧数这三个参数各自在干嘛。3.1 谱减法的计算流程从功率谱相减到语音重建完整的谱减法分四步分帧加窗、逐帧 FFT、幅度谱相减、重叠相加重建。相位不处理直接沿用带噪语音的相位。原因是人耳对相位误差的敏感度远低于幅度误差保留原相位在听感上是可接受的。function x_est spec_sub(y, fs, alpha, beta, n_noise_frames) % 参数: alpha 过减因子, beta 增益下限系数, n_noise_frames 噪声估计帧数 win_len round(0.025 * fs); hop round(0.010 * fs); n_frames floor((length(y) - win_len) / hop) 1; win hamming(win_len, periodic); Nfft win_len; % 逐帧 FFT保存幅度谱和相位谱 Y_mag zeros(n_frames, Nfft); Y_phase zeros(n_frames, Nfft); for i 1:n_frames idx (i-1)*hop 1 : (i-1)*hop win_len; spec fft(y(idx) .* win, Nfft); Y_mag(i, :) abs(spec); Y_phase(i, :) angle(spec); end % 用开头若干帧估计噪声幅度谱取平均降低方差 noise_amp mean(Y_mag(1:n_noise_frames, :), 1); noise_amp repmat(noise_amp, n_frames, 1); % 过减法: |X| max(|Y| - alpha*|N|, beta*|Y|) mag_est max(Y_mag - alpha * noise_amp, beta * Y_mag); % 重叠相加重建时域信号并用窗函数和做归一化 x_est zeros(length(y), 1); w_sum zeros(length(y), 1); for i 1:n_frames idx (i-1)*hop 1 : (i-1)*hop win_len; spec_est mag_est(i, :) .* exp(1j * Y_phase(i, :)); seg_est real(ifft(spec_est, Nfft)); x_est(idx) x_est(idx) seg_est; w_sum(idx) w_sum(idx) win; end x_est x_est ./ (w_sum eps); % eps 防止除零 end这段代码把谱减法的全部要点都包含了。mag_est那一行的max(..., beta * Y_mag)是过减法的关键相减之后如果变成负数就取beta乘以原始幅度作为下限避免出现负频谱。w_sum累积窗函数权重最后做归一化这样重叠区域的信号不会被重复放大。3.2 过减因子与噪声下限两个参数如何影响听感过减因子alpha决定减掉多少噪声。取值太小噪声残留明显取值太大语音会被削掉一块并且产生明显的音乐噪声。增益下限系数beta控制每个频点最少保留多少原始幅度beta太大会让噪声减不干净太小则容易在弱语音段产生孤立的频谱尖峰。噪声估计帧数n_noise_frames取开头没有语音的那段时间通常 5~20 帧就够了。参数常见范围取值偏小的表现取值偏大的表现alpha过减因子1 ~ 4噪声残留多听感嘈杂语音发闷音乐噪声明显beta增益下限0.01 ~ 0.1出现哔哔声、断音噪声减不干净n_noise_frames5 ~ 20噪声估计不准音乐噪声重噪声估计更稳但占用语音段我一般会先把alpha设为 2.5、beta设为 0.02、噪声估计帧数设为 5 跑一版再根据听感微调。注意这三个参数是联动的alpha调大时beta要适当调大否则大量频点被截到下限音乐噪声直接翻车。3.3 压制音乐噪声的两个改进幅度谱平滑与增益下限基础版谱减法最容易出现的问题就是音乐噪声表现为降噪后残留一种有节奏感的“哔哩哔哩”声。原因是逐帧相减时噪声幅度谱的随机波动会被保留某些频点偶尔减过头形成孤立的尖峰。一个有效的改进是相邻帧的幅度谱做平滑让尖峰被周围帧稀释。% 对幅度谱做时间方向的平滑减小孤立尖峰 Y_mag_smooth Y_mag; for i 2:n_frames-1 Y_mag_smooth(i, :) 0.25 * Y_mag(i-1, :) 0.5 * Y_mag(i, :) 0.25 * Y_mag(i1, :); end % 把 Y_mag 替换为 Y_mag_smooth 再走后面的谱减流程这个平滑的本质是低通滤波代价是瞬态细节会变模糊。如果实验要求重点展示音乐噪声的成因和抑制加这一步就够了。另一个改进是把beta * Y_mag的下限改成固定的小常数比如0.001让下限不随信号幅度变化。两种方式可以同时用效果叠加但不会加倍自己跑一遍对比听感会体会更深。4. 维纳滤波与小波去噪替换谱减的两条成熟路线谱减法实现简单但音乐噪声是它的天花板。如果实验要求对比多种方法或者你的数据里噪声不那么平稳维纳滤波和小波去噪是两条值得做的替代路线。前者把降噪问题变成估计一个乘性增益后者从时频分解的角度做阈值收缩。4.1 维纳滤波用先验信噪比计算乘性增益维纳滤波的核心是把带噪语音的幅度谱乘上一个 0 到 1 之间的增益。增益由先验信噪比决定先验信噪比高增益接近 1保留信号先验信噪比低增益接近 0抑制噪声。关键在于先验信噪比怎么估计直接拿当前帧算会抖动剧烈常见做法是判决引导法把上一帧的估计结果和当前帧的后验信噪比做指数平滑。function x_est wiener_filter(y, fs, n_noise_frames) win_len round(0.025 * fs); hop round(0.010 * fs); n_frames floor((length(y) - win_len) / hop) 1; win hamming(win_len, periodic); Nfft win_len; Y_mag zeros(n_frames, Nfft); Y_phase zeros(n_frames, Nfft); for i 1:n_frames idx (i-1)*hop 1 : (i-1)*hop win_len; spec fft(y(idx) .* win, Nfft); Y_mag(i, :) abs(spec); Y_phase(i, :) angle(spec); end noise_amp mean(Y_mag(1:n_noise_frames, :), 1); noise_psd noise_amp.^2; % 噪声功率谱 alpha_w 0.98; % 判决引导平滑系数 prev_mag_est Y_mag(1, :); % 前一帧的估计幅度 mag_est zeros(size(Y_mag)); for i 1:n_frames gamma Y_mag(i, :).^2 ./ (noise_psd eps); % 后验信噪比 xi alpha_w * (prev_mag_est.^2 ./ (noise_psd eps)) ... (1 - alpha_w) * max(gamma - 1, 0); % 先验信噪比 gain xi ./ (1 xi); % 维纳增益 mag_est(i, :) gain .* Y_mag(i, :); prev_mag_est mag_est(i, :); end % 重叠相加重建与 spec_sub 相同此处省略重复代码 x_est overlap_add_reconstruct(mag_est, Y_phase, y, win, hop, Nfft); endalpha_w取 0.98 意味着当前帧的估计主要由上一帧决定平滑力度大增益变化慢音乐噪声自然就少。代价是语音起始段会有拖尾听感上像轻微的混响。维纳滤波比谱减法的实现多一点推导但调参压力小很多主要就调alpha_w和噪声估计帧数。4.2 小波去噪wden 一行实现与阈值选择的玄学小波去噪在 MATLAB 里可以用 wden 一行搞定适合快速出对比结果。但这一行背后有四个参数要理解阈值选择规则、软/硬阈值、噪声尺度、分解层数和小波基。% 常用配置: 启发式阈值 软阈值 不按层估计噪声 5 层分解 db4 小波 x_est wden(y, heursure, s, one, 5, db4);阈值规则选哪种有一定玄学成分。rigrsure基于 Stein 无偏风险估计保留的细节最多适合信噪比较低的情况sqtwolog是通用阈值噪声压得最干净但轻声、气声容易被削掉heursure在两者之间自动选择是默认不翻车的选项。软阈值s会把系数往零收缩硬阈值h直接截断。语音用软阈值更合适因为硬阈值在系数过零处不连续重建后容易有咔哒声。4.3 三条路线怎么选复杂度、适用条件和翻车点对照做实验对比时不用三条路线都从零编码。谱减法适合自己实现全流程维纳滤波可以基于谱减的框架改增益计算小波去噪直接用 wden 出结果。选哪条路线看你的噪声类型和评估目标。路线适用场景实现代价最容易翻车的地方谱减法平稳噪声、SNR 5 dB低代码清晰音乐噪声维纳滤波平稳噪声、SNR 0~15 dB中需估计先验 SNR语音发闷、起始段拖尾小波去噪非平稳噪声、瞬态噪声低wden 一行调用阈值选错导致轻音被削我一般建议实验以谱减法为主因为它的每一步都可以用频谱图解释适合写进实验报告。维纳滤波作为对比方法展示“改进后的音乐噪声更少”小波去噪作为“第三方工具”快速出结果。三条路线共用同一套分帧、加窗、重叠相加代码能省很多事。5. 语音降噪实验的 5 个高频踩坑与排查方法这部分是血泪经验。语音降噪实验跑通容易跑出能听的音质难。下面五条是我在调实验时反复遇到的坑每一条都按现象、原因、解决的顺序说清楚。5.1 降噪后出现“哔哩哔哩”的音乐噪声现象用谱减法降噪后语音本身安静了但背景里有一种有节奏感的“哔哩”声像老收音机调台的声音。原因噪声幅度谱的帧间波动没有被平均掉过减因子alpha偏大时某些频点被减成负值再被beta * |Y|的下限截断形成随机孤立尖峰听感就是音乐噪声。解决把alpha降到 1.5~2对幅度谱做时间方向的平滑或把噪声估计帧数增加到 15 帧以上。如果还压不住就换成维纳滤波它的增益是平滑变化的天生不容易产生音乐噪声。5.2 语音发闷、高频细节全没了现象降噪后背景确实安静但说话声像隔了一层被子辅音、齿音几乎消失。原因高频段能量本来就弱幅度谱相减时高频部分被当成噪声减掉了另一种可能是帧长拉得太大频谱分辨率虽高但时间分辨率下降高频瞬态被抹平。解决帧长保持在 20~30 ms不要为了“FFT 点数更规整”去拉长帧alpha降到 1.5 以下beta提到 0.05 左右让弱语音频点有更多保留。检查时用频谱图看高频区域是不是被整体压平了。5.3 降噪后音调不对采样率写死惹的祸现象降噪后的 wav 文件播放出来语速和音调跟原文对不上像开了倍速。原因分帧时有用例把帧长写死成 256 或 512没有按fs换算或者降噪完成保存时audiowrite用了错误的采样率。我见过最典型的翻车是读入 8 kHz 文件代码里写死 25 ms 帧长对应 200 点但另一处又按 16 kHz 的 400 点算导致后续所有频率都对不上。解决全流程只用fs这一个变量帧长一律写成round(0.025 * fs)保存和播放时都传同一个fs。5.4 播放时爆音、断裂数据没归一化现象波形图看着正常但播放时隔几秒出现“啪”的一声或整体有削波失真。原因重叠相加重建后局部区域的幅度可能超过 [-1, 1]播放器或audiowrite直接截断产生爆音也可能是帧边界处信号不连续导致的。解决重建时用w_sum做归一化保存前做一次安全的幅值缩放x_est x_est / max(abs(x_est)) * 0.98; % 峰值归一到 0.98留一点余量这个处理不会改变听感上的相对强度但能彻底消除削波爆音。注意别用x_est x_est / max(abs(x_est))就把峰值拉到 1接近满幅的采样点仍然可能触发播放器限幅。5.5 同样的参数换个环境结果不同现象同一份代码在自己机器上跑得好好的换个环境跑结果不一样甚至报错。原因最常见的两类一是旧版函数wavread、wavwrite被新版audioread、audiowrite取代旧代码直接报“未定义函数”二是wden在不同环境下对默认参数的处理有差异导致降噪结果不稳定。解决检查代码里有没有wavread这类旧函数换成当前推荐写法wden这类的黑匣子函数全部显式指定参数不要省略任何一项脚本开头加which wden这类自查语句确认调用的确实是预期路径下的函数。6. 验证降噪效果先看频谱图再谈 SNR6.1 三个客观指标SNR、分段SNR与频谱图跑完降噪不能只看一个总 SNR 数字。整段 SNR 计算的是全局平均静音帧会把误差稀释掉导致数值好看但听感一般。分段 SNR 逐帧计算再平均更贴近人耳感知。% 整段 SNR snr_in 10*log10(sum(x.^2) / sum((y - x).^2)); snr_out 10*log10(sum(x.^2) / sum((x_est - x).^2)); % 分段 SNR逐帧计算后取平均避免静音帧掩盖真实误差 frame_len round(0.025 * fs); hop round(0.010 * fs); seg_snr []; for i 1:hop:length(x)-frame_len idx i:iframe_len-1; e x_est(idx) - x(idx); seg_snr(end1) 10*log10(sum(x(idx).^2) / (sum(e.^2) eps)); end seg_snr_avg mean(seg_snr);频谱图比数字更直接。用spectrogram(y, 256, 128, 512, fs, yaxis)看带噪语音噪声会铺满整个时频面降噪后理想的结果是噪声底被压低语音共振峰形成的横条纹保持连续。如果看到零散的高亮小点说明音乐噪声没压干净横条纹断裂说明帧衔接或窗函数有问题。6.2 听感验证与控制变量的实验习惯客观指标之外一定要做盲听对比。把干净语音、带噪语音、降噪语音用固定顺序播放不看波形只凭耳朵判断噪声是否让人烦躁、语音是否自然、有没有爆破音和金属感。我现在的习惯是先看频谱图再算指标最后盲听定结论这三个结果互相印证。做参数对比时固定随机种子、固定语音、固定噪声类型一次只动一个变量否则出了问题根本不知道是谁引起的。某次我把帧长从 20 ms 拉到 50 msSNR 涨了 2 dB但高频细节明显丢了因为只看数字差点把这个效果写成正向改进。从那以后数字只作为参考频谱图和听感才是最终裁判。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑