
简介面向语音信号处理与语音增强方向的学习者资源围绕加性噪声下的带噪语音处理需求给出基于人耳掩蔽效应的谱减增强算法及信噪比计算实现可用于从混合语音中提取纯净信号、改善听感质量并对算法原理做了一定讨论。压缩包共7个文件含4个Matlab源码文件、1个CAA算法说明文档、1个测试音频和1张运行结果图整体仅83KB结构轻量适合快速复现与二次开发。目前已有406人学习下载。源码涵盖主程序、掩蔽谱减处理、分段帧变换与分段信噪比计算等模块配合北京话测试音频和结果图可完整走通从带噪输入到增强输出的流程对于理解人耳掩蔽效应在语音增强中的应用、对比信噪比指标变化尤其有用可作为课程设计或科研复现的起点。1. 语音增强与掩蔽效应从信噪比数字到真实听感的跳跃做信号处理的人大概都遇到过这种场景跑完一段谱减语音增强信噪比从 8dB 提到 12dB数字好看得不行一听却全是吱吱呀呀的音乐噪声听感甚至不如没增强前。这就是典型的“指标赢了、耳朵输了”。这份资源给的正是另一条路线——基于人耳掩蔽效应的语音增强算法它不跟你硬拼信噪比数字而是把噪声压到人耳听不见的掩蔽阈值之下让听感优先级高于指标优先级。资源里除了完整 Matlab 源码还配了论文、测试语音 beijing.wav 和 SegSNR.m 分段信噪比计算工具适合正在做语音增强课设、入门语音信号处理或需要对比谱减改进效果的从业者直接动手复现。2. 核心原理为什么 Masking 能Hold住音乐噪声2.1 谱减法与掩蔽效应的本质差异经典谱减法Spectral Subtraction的逻辑非常直白带噪语音的功率谱减去估计的噪声功率谱得到纯净语音谱再结合相位重构时域信号。它计算简单、实时性好但在信噪比低或噪声非平稳时谱估计误差会被放大出现“音乐噪声”Musical Noise——那些随机出现的窄带尖峰听感上像断续的金属摩擦声。掩蔽效应则来自心理声学的一个基本事实人耳对某个频率成分的感知阈值会被附近能量更强的频率成分抬高。换句话说强音会“盖住”弱音。人耳掩蔽效应算法Masking-based Enhancement正是利用这一点——不追求把噪声彻底减掉而是把噪声功率压低到掩蔽阈值之下让耳朵“听不见”它。这类算法在低信噪比场景下往往比纯谱减更稳因为它在“减多少”这个问题上增加了一个听感约束。这个资源里的 MaskingSub.m 就是这种思路的载体它先做常规谱减再用心理声学模型计算出各频带的掩蔽阈值对低于阈值的残留噪声不再处理、高于阈值的噪声残余施加额外衰减。相比“一刀切”的谱减这种两段式处理能明显减少音乐噪声。2.2 从语音生成到谱减的完整链路资源里 vec2frames.m 是前期处理工具它负责把一维语音信号切成带重叠的短时帧这是所有时域语音增强算法的前提。语音信号是非平稳的但短时窗内可以看成平稳过程。帧长一般取 20~30ms帧移取帧长的 50% 左右——既要保证频谱分辨率又要保证帧间平滑衔接。vec2frames 输出的是二维矩阵每列一帧后续的谱减和掩蔽计算都在这个矩阵上做逐帧处理最终再通过重叠相加OLA合成回连续语音。整个流程是读入带噪语音 → vec2frames 分帧加窗 → 对每帧做 FFT 得到幅度谱和相位 → 用前导静音段估计噪声谱或直接取最小值跟踪 → MaskingSub 做掩蔽阈值计算和谱减 → IFFT 还原帧信号 → 重叠相加合成为输出语音 → SegSNR 对比增强前后信噪比。Main.m 把这个链路串成一条线同时绘制增强前后的时域波形和语谱图方便肉眼观察噪声抑制效果。3. 源码走读main 主流程与 MaskingSub 的内外参数3.1 从 Main.m 看算法调用链路大多数入门者拿到资源后第一个动作是直接跑 Main.m我也建议这么做。代码结构通常长这样% Main.m 主流程 [x, fs] audioread(beijing.wav); % 读入带噪语音 x x(:, 1); % 取单声道避免通道干扰 win_len 0.032 * fs; % 帧长 32ms单位转为采样点 win_shift win_len / 2; % 帧移 16ms50% 重叠 frame_mat vec2frames(x, win_len, win_shift, hanning); enhanced MaskingSub(frame_mat, win_len, fs); % 掩蔽谱减核心 snr_before SegSNR(x, x); % 注意SegSNR需要参考干净语音这里 audioread 读到的是带噪语音 beijing.wav它本身就是含噪信号所以 SegSNR 的第二个参数不应该是它自己否则信噪比恒为无穷大。正确做法是用增强前的带噪语音作为“参考”来评估抑制程度或者在资源配套说明里找到干净的参考语音否则 SegSNR 的结果只能说明“增强后和原带噪信号差了多少”不能说明真实信噪比提升。参数部分帧长 32ms 在语音增强里是一个偏保守但稳定的选择太长如 50ms会让频谱在时域上太平均瞬态噪声的起始段会被磨平太短如 16ms则频率分辨率不足低频部分容易被过度衰减。如果你处理的语音主要是女声或播音类可以把帧长调到 20ms如果是男声低频为主32ms 更合适。3.2 vec2frames参数解析与实现逻辑vec2frames.m 的核心任务有两个切帧和加窗。从资源运行结果图来看它默认按列输出帧配合窗函数能有效降低频谱泄漏。代码逻辑大致是function frames vec2frames(x, win_len, shift, winfun) n length(x); n_frames floor((n - win_len) / shift) 1; frames zeros(win_len, n_frames); for k 1:n_frames idx (k - 1) * shift 1 : (k - 1) * shift win_len; frames(:, k) x(idx) .* winfun(win_len); end endn_frames 的计算决定了最后一帧是否被丢弃。如果语音总长度不是帧移的整数倍末尾的样本会直接截掉这在噪声估计时会有影响——如果噪声刚好集中在尾部截断会导致噪声低估。我一般会在分帧前对信号做一下边缘扩展用 replic 方式补零或延拓确保最后一帧有完整数据避免因截断引起的频谱边缘不连续。窗函数的选择也有讲究。hanning 窗主瓣宽、旁瓣衰减快适合谱减这种对频谱泄漏敏感的场景hamming 窗在同样主瓣宽度下旁瓣更低但靠近窗边缘的样本权重更小对瞬态信号不够友好。如果为了简单用矩形窗频谱泄漏会明显增强后的语音容易带上“毛刺感”。3.3 MaskingSub掩蔽阈值的计算与两段式衰减MaskingSub.m 是整个资源的核心。它接收分帧矩阵输出增强后的时域帧矩阵。内部流程大致分四步功率谱计算、噪声谱估计、掩蔽阈值计算、谱减与增益控制。常见实现里掩蔽阈值会经过 Bark 域映射——把线性频率映射到人耳临界频带各频带的掩蔽阈值由信号能量的扩展函数叠加获得。function out_frames MaskingSub(frames, win_len, fs) [nfft, n_frames] size(frames); spec fft(frames, nfft, 1); mag abs(spec); phase angle(spec); noise_est mean(mag(:, 1:ceil(n_frames*0.1)), 2); % 前10%帧作为噪声段 mask_thr masking_threshold(mag, fs); % 心理声学掩蔽阈值 alpha 2; % 过减因子 mag_enhanced max(mag - alpha * noise_est, 0); residual mag - mag_enhanced; % 残留噪声 gain 1 ./ (1 exp(-2 * (residual - mask_thr))); % 阈值下衰减 out_spec mag_enhanced .* gain .* exp(1i * phase); out_frames ifft(out_spec, nfft, 1); end这段逻辑的核心在于 gain 的计算普通谱减对全部频带使用同一过减因子而这里引入了掩蔽阈值 mask_thr——残余噪声低于阈值的频带gain 逼近于 1意味着不额外处理高于阈值的部分gain 小于 1 继续衰减。这样既保留了谱减的抑制能力又不会把残余噪声的“底噪”整体放大。参数 alpha过减因子默认取 2 是经验值。alpha 越大噪声抑制越激进但语音畸变也越大alpha 太小则音乐噪声压不下来。如果运行后发现语音“发闷”或高频细节丢失多半是 alpha 偏大可以降到 1.2~1.5如果音乐噪声仍旧明显则需要把 alpha 提上去同时留意掩蔽阈值的缩放系数。function mask_thr masking_threshold(mag, fs) % 简化的掩蔽阈值估计 % 1. 将频率轴映射到 Bark 域 % 2. 计算每个 Bark 带的能量 % 3. 用扩展函数展开得到全局掩蔽阈值 bark 13 * atan(0.00076 * f) 3.5 * atan((f/7500).^2); e accumarray(round(bark)1, mag.^2); % 各频带能量 spread spreading_function(); % 扩展函数模拟频带间掩蔽 mask_thr 10 * log10(conv(e, spread)); % dB 域阈值 mask_thr 10 .^ (0.05 * mask_thr); % 转回线性值 end掩蔽阈值计算是这类算法里最“玄学”的地方。不同论文用的扩展函数不同有的用 Johnston 模型有的直接简化成三角扩展。资源里的实现大概率是简化版适合课设展示但如果要做严格的听感对比建议把 Bark 域分辨率从 24 个频带提高到 32 个阈值会更平滑语音失真更小。4. 信噪比计算SegSNR 怎么评估增强效果4.1 分段信噪比与整体信噪比的差异SegSNR.m 计算的是分段信噪比Segmental SNR它先把语音按帧切分逐帧计算 SNR 再取平均。相比全局 SNR对整段信号算一次SegSNR 更能反映语音各时间段的质量波动——因为一段语音里清音段、浊音段、静音段的能量差异很大全局 SNR 会偏向高能量段而 SegSNR 对每个短时段的噪声抑制效果都单独评价。% SegSNR.m 核心计算 function seg_snr SegSNR(clean, enhanced, frame_len, shift) if nargin 4, shift frame_len / 2; end n min(length(clean), length(enhanced)); clean clean(1:n); enhanced enhanced(1:n); n_frames floor((n - frame_len) / shift) 1; sum_snr 0; valid_frames 0; for k 1:n_frames idx (k - 1) * shift 1 : (k - 1) * shift frame_len; noise clean(idx) - enhanced(idx); noise_pow sum(noise.^2) 1e-12; clean_pow sum(clean(idx).^2); if clean_pow 1e-6 noise_pow 1e-9 sum_snr sum_snr 10 * log10(clean_pow / noise_pow); valid_frames valid_frames 1; end end seg_snr sum_snr / valid_frames; end这里有个容易被忽略的点clean 和 enhanced 必须逐样本对齐。如果增强算法因为帧移和重叠相加引入了延迟SegSNR 的值会严重失真——看上去信噪比骤降其实只是对齐漂移了十几个采样点。我一般会在计算前先跑一下 xcorr 做互相关对齐确保两个数组起点一致。4.2 参数怎么设帧长、静音帧剔除与边界值SegSNR 默认帧长建议和增强算法保持一致这样评估口径统一。例如 MaskingSub 用 32ms 帧SegSNR 也取 32ms。需要特别处理的是静音帧一段语音的开头结尾常有低能量帧这些帧里 clean_pow 很小计算出的 SNR 会是一个极大的负数拉低均值。常见做法是跳过 clean_pow 低于某个阈值如总能量的 1/100的帧资源里的 clean_pow 1e-6 判断也是这个目的但如果语音幅值本身偏小这个阈值需要相应调低。运行结果.PNG 里通常会有增强前后的 SegSNR 对比曲线我建议你跑完源码后自己换上另一段语音试一下同一组参数看看 SegSNR 是否稳定。这样能间接判断算法的泛化能力——如果换语音后 SegSNR 波动超过 3dB说明参数过拟合了当前测试文件。5. 避坑与常见问题跑通增强前先绕过这五个坎5.1 audioread 读不出 wav 或路径带中文现象Main.m 运行时提示文件找不到或者 audioread 返回空的 x。原因beijing.wav 和 Main.m 不在同一目录Windows 下中文路径被部分版本的 Matlab 解析失败。解决用 which beijing.wav 确认文件路径把整个工程文件夹挪到纯英文路径下或者在 Main.m 顶部用 absolute_path fileparts(mfilename(fullpath)); cd(absolute_path); 强制切换工作目录。5.2 SegSNR 结果为 Infinity 或 NaN现象SegSNR 输出 inf或者出现 NaN波形图一片空白。原因SegSNR 中噪声功率 noise_pow 为 0 或 clean 信号为零向量增强后语音和参考语音完全相同除以 0 得到无穷大。解决给噪声功率加上一个接近 0 的常数代码里的 1e-12 就是这个作用确认参考语音不是增强后信号本身如果是 NaN检查输入信号里是否有 NaN 值用 isnan 定位后做线性插值替换。5.3 增强后语音“爆炸音”或幅度突增现象增强后的时域波形出现明显尖峰听感刺耳。原因谱减里 max(mag - alpha * noise_est, 0) 的硬截断在噪声低估时会把某些频点减到负数再归零产生频谱空洞IFFT 后这些空洞会变成时域上的瞬态尖峰。解决把硬截断改成软截断mag_enhanced max(mag - alpha * noise_est, beta * mag)beta 取 0.01~0.1保证每个频点至少保留原幅度的 1%频谱不会出现极端空洞。5.4 前 10% 帧的噪声估计不准现象音乐噪声压制不彻底尤其语音开头几帧明显。原因屏蔽噪声段取前 10% 帧但这部分可能包含语音起始的爆破音或摩擦音噪声估计偏大/偏小都会影响后续全部帧。解决改用最小说数统计取每帧功率谱的最小值而不是平均值或者只在语音活动检测VAD标注为噪声的帧上更新噪声谱。最简单的方式是把前 10% 的比例降到 5%并排除能量突增的帧。5.5 增强后语音“发闷”高频发虚现象听感尚可但不够通透语谱图高频区域一片漆黑细节被过度衰减。原因掩蔽阈值在高频段通常较低算法对高频噪声的衰减不足以保证阈值不被超过过减因子 alpha 对高频一律套用导致高频语音也受损。解决按 Bark 频带区分 alpha低频用 1.5高频用 2.5把过减力度向噪声能量更高的频段倾斜。另外可以做一个简单的高频补偿滤波器对 4kHz 以上频段增益加 1~2dB。6. 把算法调到能用的水平三步验证与参数微调技巧拿到源码后我建议别急着改算法结构先建立一套自己的验证流程。第一步跑通原始资源用 SegSNR 和语谱图各留一份基线数据。第二步换一段自己的测试语音建议用手机在安静房间录一段 5 秒的句子再叠加高斯白噪声到 5dB/10dB/15dB 三种信噪比用相同参数跑一遍。第三步对比增强前后 SegSNR 的变化量和语谱图上音乐噪声残留的浓度。在参数调优上优先级是alpha过减因子→ 噪声估计帧比例 → 掩蔽阈值缩放系数。alpha 从 2.0 往下试到 1.2每次间隔 0.2用同一段语音听对比。你会发现一个规律alpha 减小时 SegSNR 通常会升一截因为语音畸变减少但音乐噪声会回来一些。找到一个“音乐噪声尚可接受、SegSNR 最高”的临界值通常这就是当前信噪比下的最优值。掩蔽阈值缩放系数则控制“保守程度”——系数越大算法越保守对语音的改动越小适合高信噪比场景系数越小降噪越激进适合 0dB 以下的恶劣环境。整个资源最值得借鉴的其实是它的评估闭环不是光有一个增强算法而是把 SegSNR 计算单独拆成一个模块和主算法解耦。这个习惯让我后来跑所有降噪 demo 都受益——从那以后我每次跑语音增强都强制走一遍“先看波形、再听残留、再看分段信噪比曲线”三步先量化再调参而不是凭感觉调。希望帮到你。本文还有配套的精品资源点击获取