
简介面向语音信号处理学习者的语音增强算法Matlab实现资料包围绕基于人耳掩蔽效应的谱减法展开针对现实环境中加性噪声干扰下的语音质量改善问题提供完整可运行的实用源码与信噪比计算方案。压缩包共7个文件以Matlab脚本为主包括主程序Main.m、掩蔽谱减MaskingSub.m、分段信噪比计算SegSNR.m、分帧工具vec2frames.m另含beijing.wav测试语音、运行结果截图PNG及一篇算法研究文档整体仅83KB轻量易获取。目前已有406人学习下载。借助这份资料读者可直接运行主程序复现基于人耳掩蔽效应的语音增强流程理解谱减算法如何结合掩蔽阈值抑制残留音乐噪声掌握从分帧加窗、掩蔽计算到信噪比评估的完整实现细节适合用于课程设计、算法对比或入门语音增强研究。1. 语音增强的新维度人耳掩蔽效应不是黑匣子而是一套可调参的增益决策器做语音增强的人往往有个执念噪声要压得越低越好。直到我对比了两条路线——传统谱减法把残留噪声压到接近零而基于人耳掩蔽效应的语音增强算法只把噪声压到“听不见了”的阈值以下结果后者听感反而干净得多语音也几乎没有失真感。原因在于人耳对掩蔽阈值之下的噪声天然不敏感与其花动态范围去消除听不见的成分不如让增益决策直接服从掩蔽阈值。这篇文章围绕“基于人耳掩蔽效应的语音增强算法与信噪比计算”展开给出从临界频带划分、掩蔽阈值计算、增益整形到分段信噪比评估的完整链路并附上可运行的 Matlab 源码实现思路。适合正在调谱减参数、被音乐噪声困扰或者要给语音识别前端做降噪的开发者。2. 掩蔽阈值怎么算临界频带、谱平坦度与偏移量的三步链路2.1 为什么固定增益下限会让语音增强翻车传统谱减的核心公式是“带噪谱减去噪声谱估计”为了防止减出负值常规做法是加一个增益下限比如 -20dB。问题在于这个下限是全局常量语音能量低的频段被迫保留一块“底噪”能量高的频段又未必减得干净。最终结果就是音乐噪声——残留噪声在频点上忽大忽小听起来像水声或电流声。人耳掩蔽效应的介入方式不是替代谱减而是改造它的增益决策。掩蔽效应说的是当一个频段存在较强的信号分量掩蔽声时人耳对邻近频段中低于某个阈值的微弱信号被掩蔽声不敏感。对应到语音增强就是每个频点存在一个“允许残留噪声上限”。只要增益下限不低于这个上限对应的衰减量残留噪声就落在掩蔽阈值之下听感上等于消失了。这样一来增益下限从固定常量变成了随帧变化的数组。每一帧的语音能量分布不同、噪声估计不同允许残留的上限也不同。这就是“基于人耳掩蔽效应的语音增强”最核心的差异点不是做更狠的减法而是做更聪明的减法。2.2 从 Hz 到 Bark临界频带为什么影响阈值走势人耳的掩蔽效应不是按线性频率均匀分布的而是按临界频带Critical Band划分。临界频带的宽度随频率升高而变宽低频段分辨率高高频段分辨率低。这个特性直接决定了我们计算掩蔽阈值时不能直接对 FFT 的每个 bin 独立处理而要先映射到 Bark 域。常见的 Bark 频率映射公式是z 13 * arctan(0.76 * f_kHz) 3.5 * arctan((f_kHz / 7.5)^2)其中 z 的单位是 Bark。0~20kHz 大约对应 0~24 Bark。做增强时我们只关心 0~fs/2对 16kHz 采样率来说就是 0~8kHz大概覆盖 0~21 Bark。另一个关键点是扩展函数spreading function。掩蔽不是只发生在同一个临界频带内部还会向邻近频带扩散尤其是向高频方向扩散更明显。常见的简化做法是用一个指数衰减的稀疏向量对 Bark 带能量做加权叠加模拟掩蔽的跨带扩散。扩散范围不需要取很大正负 3~4 个 Bark 带已经能覆盖主要效应。这个扩散步骤会让掩蔽阈值曲线变得平滑避免相邻频带之间增益突变。2.3 掩蔽阈值计算的完整步骤与公式解读单帧掩蔽阈值的典型计算链路分四步Bark 带能量累加、谱平坦度计算、偏移量确定、扩散与展开。先看带能量累加把每个 FFT bin 的功率按 Bark 编号分组求和得到每个临界频带的能量。再算谱平坦度 SFMSpectral Flatness Measure公式是几何平均除以算术平均转成 dB。SFM 决定了这一帧内容更像噪声还是更像纯音SFM 接近 0dB 表示频谱平坦类噪声SFM 越负表示频谱越尖类纯音。由此计算音调系数 alpha再用 alpha 在“类语音偏移量”和“类噪声偏移量”之间插值。类纯音的掩蔽阈值高偏移量小类噪声的掩蔽阈值低偏移量大这是掩蔽效应的经典规律。最后把每帧算出的 Bark 带阈值做扩散卷积再插值回每个 FFT bin。需要注意绝对听阈下限以防止阈值过低导致增益被压到离谱。完整公式如下T_bark(i) E_bark(i) - offset(i) 扩散项 offset(i) alpha * (14.5 i) (1 - alpha) * 5.5其中 i 是 Bark 带索引。alpha 越接近 1类纯音偏移量越大阈值越低alpha 越接近 0类噪声偏移量越小阈值越高。这个规律是整篇算法的灵魂后面调参也都是围绕它展开。3. 把掩蔽阈值写进增益Matlab 程序结构、核心函数与参数设置3.1 主程序骨架分帧、加窗、估计噪声一个都不少Matlab 实现里整体流程并不复杂读音频、分帧、加窗、FFT、噪声估计、逐帧计算掩蔽阈值与增益、ISTFT 重叠相加、写回音频。噪声估计常见做法是取音频开头若干帧做平均平稳噪声场景足够用如果音频开头就是语音要改用最小值统计法否则前几帧的语音能量会污染噪声谱估计。下面的主程序是一个可以跑通的骨架采用 50% 重叠的汉宁窗%% 主程序基于人耳掩蔽效应的谱减增强 [x, fs] audioread(noisy_speech.wav); x x(:,1); % 强制单声道 frameLen 512; % 16kHz 下 32ms hop frameLen / 4; % 帧移 8ms75% 重叠 win hann(frameLen, periodic); nfft 512; frameMat buffer(x, frameLen, frameLen - hop, nodelay); nFrames size(frameMat, 2); % 平稳噪声场景取前 10 帧平均作为噪声谱估计 noiseFrames frameMat(:, 1:10); Nk mean(abs(fft(noiseFrames, nfft)), 2); Nk Nk(1:nfft/21); % 只保留单边谱 X zeros(nfft/21, nFrames); for i 1:nFrames framed frameMat(:, i) .* win; Yk fft(framed, nfft); Yk Yk(1:nfft/21); Pk abs(Yk).^2; Tk compute_mask_threshold(Pk, fs, nfft); G compute_gain(abs(Yk), Nk, Tk); X(:, i) G .* Yk; end这段代码的核心在循环体里每一帧先算功率谱再算掩蔽阈值最后根据阈值计算增益。buffer的分帧方式决定了重叠率hop frameLen / 4时相邻帧有 75% 重叠ISTFT 还原时窗函数的平方叠加会更平稳减少块效应。如果你习惯 50% 重叠把 hop 改成frameLen / 2即可但重建时要确保窗函数满足 COLA 条件否则会出现幅度调制噪声。3.2 掩蔽阈值计算函数与增益函数的代码实现掩蔽阈值计算函数需要把频域功率谱映射到 Bark 带再逐带计算阈值。实现时注意accumarray要求分组索引是正整数并且低频段多个 bin 可能落到同一个 Bark 带这是正常的累加即可。如果某些 Bark 带没有分配到任何 bin直接用邻带插值补上避免后续几何平均出现 0。function Tk compute_mask_threshold(Yk, fs, nfft) % 输入Yk 为当前帧功率谱线性幅度平方长度 nfft/21 % 输出Tk 为逐频点掩蔽阈值线性幅度长度与 Yk 一致 f (0:nfft/2) * fs / nfft; z 13 * atan(0.76 * f / 1000) 3.5 * atan((f / 7500).^2); bandIdx round(z) 1; % 转正整数索引 nBands max(bandIdx); Eb accumarray(bandIdx(:), Yk(:), [nBands 1], sum); Eb(Eb 0) 1e-12; % 防几何平均退化 % 谱平坦度 SFM单位 dB SFM 10 * log10(geomean(Eb) / mean(Eb)); alpha min(1, max(0, SFM / -60)); offset alpha * 14.5 (1 - alpha) * 5.5; % 扩展函数向邻近 Bark 带扩散 Tb zeros(nBands, 1); for i 1:nBands for j max(1, i-3):min(nBands, i4) Tb(i) Tb(i) Eb(j) * 10^(-0.3 * abs(i-j)); end end Tb 10 * log10(Tb) - offset; % 转 dB 域减去偏移量 % 插值回 FFT bin Tk interp1(1:nBands, Tb, bandIdx, linear, extrap); Tk max(Tk, 1e-12); % 绝对下限 end这里的offset是单帧全局标量容易忽略的是它在不同 Bark 带上的表现差异。严格做法是每个 Bark 带单独算偏移量但对大多数噪声场景全局偏移量配合扩展函数已经能给出稳定阈值。SFM / -60中的 -60 是经验值表示 SFM 低于 -60dB 时按纯音处理你可以把它放宽到 -40 或收紧到 -80会影响阈值整体高低。增益函数比阈值计算简单得多但决策逻辑要注意。噪声谱高于掩蔽阈值的频点需要衰减目标是把残留噪声压到阈值之下噪声谱已经低于阈值的频点则保持增益为 1不去动它从而避免不必要的声音失真function G compute_gain(Yk, Nk, Tk) % Yk: 带噪语音幅度谱线性 % Nk: 噪声幅度谱估计线性 % Tk: 掩蔽阈值线性幅度 % G: 逐频点增益 G ones(size(Yk)); for k 1:length(Yk) if Nk(k) Tk(k) G(k) max(sqrt(Tk(k) / Nk(k)), 0.02); end % 噪声低于阈值的频点G 保持 1 end end这段代码把增益下限从固定常数换成了“阈值/噪声谱”平方根的动态值。0.02是安全下限防止极端情况下增益变 0 导致频谱断裂。实际调试时可以把这个安全下限提高比如 0.05听感差异不大但频谱更连续。3.3 关键参数表帧长、Bark 带数、偏移量上下界怎么定参数之间是联动的单独调某一个容易顾此失彼。下面几个参数是我跑通这套流程后认为最值得先固定的参数建议值作用与调整方向帧长32ms512 点 16kHz过短则频率分辨率差掩蔽阈值不平滑过长则语音瞬态被平滑掉帧移8ms~16ms决定 ISTFT 重叠率影响重建质量和算法延迟FFT 点数与帧长一致不需要大于帧长补零不增加有效频率分辨率偏移量下限5.5dB类噪声帧的掩蔽阈值越低越容易触发抑制偏移量上限14.5dB类纯音帧的掩蔽阈值越高抑制越弱安全增益下限0.02~0.05防止频谱断裂低于此值会产生明显可听失真偏移量的上下界我一般固定为 5.5 和 14.5这是经典的掩蔽模型经验值。如果你处理的语音以清辅音为主可以把 14.5 提高到 16辅音能量低但瞬态强阈值稍高能保住辅音不被吞掉。反过来如果背景是平稳空调噪声把 5.5 降到 4抑制会更彻底。4. 信噪比计算先过对齐这关分段 SNR 与全局 SNR 的 Matlab 实现4.1 延迟对齐算 SNR 前一定要做的互相关校正很多人算增强前后的信噪比直接拿原件和增强件逐点相减结果 SNR 低得吓人但听感明明还可以。问题大概率出在延迟上。增强系统的分帧、滤波、重叠相加都会引入时延而且不同实现的延迟还不一样几十个采样点的偏差足以让分段 SNR 掉好几个 dB。我一般会在算任何 SNR 指标之前先做一次互相关延迟估计。Matlab 工具箱里有现成的finddelay没有工具箱也可以自己用xcorr找峰值。校正逻辑很简单用延迟对齐后的序列做后续所有计算否则后面的数字全是自欺欺人。4.2 分段 SN R去掉静音帧再平均才有参考价值分段 SNR 的做法是把信号按帧计算信噪比再对所有帧取平均。它比全局 SNR 更贴近主观听感因为语音的响度是随时间变化的静音段和语音段混在一起算全局值会把噪声段的贡献稀释掉。分段 SNR 的另一个好处是能观察到增强算法在哪些帧翻车——如果某几帧 SNR 特别低往往就是音乐噪声或语音失真的位置。下面是包含延迟对齐和静音帧剔除的完整实现function snr_out segmental_snr(clean, enh, fs) % 计算分段信噪比含延迟对齐与静音帧剔除 frameLen round(0.03 * fs); % 30ms hop round(frameLen / 2); % 延迟对齐 [d, lag] finddelay(clean, enh); if d 0 enh enh(d1:end); clean clean(1:end-d); elseif d 0 clean clean(-d1:end); enh enh(1:endd); end cleanF buffer(clean, frameLen, frameLen - hop, nodelay); enhF buffer(enh, frameLen, frameLen - hop, nodelay); nFrames min(size(cleanF, 2), size(enhF, 2)); snr_list zeros(1, nFrames); for i 1:nFrames c cleanF(:, i); e enhF(:, i); Ec sum(c.^2) 1e-12; Ee sum((c - e).^2) 1e-12; snr_list(i) 10 * log10(Ec / Ee); end % 只统计干净语音中能量不低于全局均值 30dB 的帧 thr 10 * log10(mean(sum(cleanF.^2, 1))) - 30; valid 10 * log10(sum(cleanF.^2, 1)) thr; snr_out mean(snr_list(valid)); end注意finddelay返回的d为正时表示增强信号晚于干净信号此时丢弃增强信号的头部样本反之丢弃干净信号的头部样本。帧长取 30ms 是语音信号处理的常见折中既能覆盖一个音节的主要能量又不会让短时瞬态被平滑掉。静音帧剔除的 30dB 门限不是固定的如果测试集里本来就有大段低能量语音可以放宽到 20dB否则会误删有效帧。4.3 全局 SNR 与 VAD 门限的选择全局 SNR 计算简单但它对静音帧的长度非常敏感。一段 10 秒的音频如果前 5 秒是静音全局 SNR 会被拉低到让人误以为算法失败。所以在工程上我更常把全局 SNR 和 VAD语音活动检测结合使用只统计检测为语音的帧。VAD 门限的选择直接影响结果稳定性门限太高会漏掉轻声开头的音节门限太低则把噪声误判成语音。三种计算方式各有适用场景计算方式适用场景特点全局 SNR对比同一段音频增强前后简单直接但受静音帧影响大分段 SNR含 VAD 剔除算法调参与横向对比贴近主观听感能定位翻车帧仅语音帧全局 SNR语音识别前端评估排除静音干扰聚焦语音质量对语音增强落地来说单一 SNR 指标不足以支撑结论。它只能告诉你“信噪比有没有提升”不能告诉你“是不是以牺牲语音自然度为代价”。所以我在调参时会把 SNR 当作监控指标而不当作最终验收指标。5. 四个必踩的坑掩蔽增强从仿真到实际音频的调试记录5.1 掩蔽阈值在静音段抖动导致背景噪声忽大忽小现象安静段里残留噪声不是平稳的而是一阵一阵的“喘息感”。观察阈值曲线发现静音帧的掩蔽阈值在剧烈跳动。原因静音帧频谱平坦SFM 接近 0dBalpha 接近 0偏移量取小值掩蔽阈值整体偏高但静音帧能量很低阈值绝对值波动大增益也跟着大幅摆动。解决对掩蔽阈值做时间方向的递归平滑。常见做法是Tk_smooth 0.7 * Tk_smooth 0.3 * Tk_now平滑系数 0.7~0.9 都可以试。注意平滑要在 dB 域做线性域平滑会被语音帧的大能量彻底带偏。5.2 Bark 带映射出现空带几何平均直接变零现象某些采样率和 FFT 点数组合下compute_mask_threshold报错或者 SFM 算出来是 -Inf。原因accumarray 按 Bark 带分组时低频区域映射密集、高频区域稀疏。FFT 点数不够时某些高频 Bark 带可能一个 bin 都没分到Eb为 0几何平均为零log 计算崩溃。解决先检查nBands是否大于等于实际使用的 bin 数对空带用相邻带插值或者给Eb加 1e-12 地板值。更稳妥的做法是直接把 Bark 带数量限制在 FFT bin 数以内牺牲一点高频分辨率换取数值稳定。5.3 噪声估计不更新非平稳噪声下算法集体失效现象键盘敲击声、开关门声这类突发噪声出现时增强后的语音反而出现明显的“空洞感”。原因前 10 帧平均的噪声估计在平稳噪声场景下够用但非平稳噪声到来时噪声谱已经变了旧的噪声估计与当前帧噪声不匹配掩蔽阈值计算也失去参考。解决改用带 VAD 控制的递归噪声估计。检测到语音帧时冻结噪声更新检测到噪声帧时以较快时间常数更新。VAD 判断可以用简单的帧能量对比也可以用谱减法的中间结果做二次确认。这个改动能让算法对环境的适应能力上一个台阶。5.4 SNR 为负但听感正常先别急着否定算法现象增强后的音频听着很干净但分段 SNR 是负的或者只比增强前高了 0.5dB和你预期的“大幅提升”相差甚远。原因一种是前面说的延迟未对齐另一种是干净参考信号与增强信号之间存在非线性相移人耳对相位不敏感但 SNR 计算是逐点相减的相位差异全部算成了误差。解决先做互相关对齐再算 SNR。如果对齐后 SNR 仍然偏低检查增强信号是否被某种滤波引入了额外延迟或相位变化必要时在评估链路里加入与增强算法一致的滤波处理。SNR 不是万能的结合 PESQ、STOI 和语谱图一起看才能下结论。6. 一条进阶路径让掩蔽偏移量跟上输入信噪比变化固定偏移量 5.5~14.5 能覆盖大多数场景但我做的增强系统在输入 SNR 从 20dB 掉到 0dB 时暴露了问题低 SNR 下掩蔽阈值主要由噪声谱主导语音帧和噪声帧的阈值差异变小增益整形几乎退化成固定谱减。这时候我引入了一个简单的自适应策略——让偏移量随帧级输入 SNR 联动。具体做法是给compute_mask_threshold增加一个帧级 SNR 参数。估算方式可以很粗暴当前帧功率谱总能量和噪声谱估计总能量的比值转成 dB。SNR 低时增大偏移量让掩蔽阈值下降加强抑制力度SNR 高时减小偏移量避免过度抑制语音细节。映射关系用线性插值offset_adapt offset_fixed beta * (snr_frame - snr_ref)beta 取 0.1~0.2snr_ref 取 10dB。这样做的效果是低 SNR 段抑制更狠高 SNR 段保留更多自然度。配合时间平滑系数联动语音帧平滑系数调低0.3 左右保持瞬态噪声帧平滑系数调高0.9 左右压低方差整套算法就不再是“一个参数打天下”。验证这套调整时我的习惯是固定三组音频一组干净语音加白噪声、一组加 babble 噪声、一组加非平稳噪声分别跑增强前与增强后的分段 SNR、PESQ 和语谱图。语谱图主要观察两个点一是有没有残留的音乐噪声表现为离散亮斑二是高频清辅音有没有被削掉。只有 SNR 提升而语谱图上语音结构完整的方案才算值得上线。这套掩蔽效应增强最让我感慨的一点是它给了“最小可听残留”一个物理依据而不是靠耳朵反复试增益。如果你正在谱减法的音乐噪声里挣扎不妨从掩蔽阈值计算入手把固定下限换成动态下限哪怕是先跑通上面的最小实现再慢慢调偏移量上下界也能感受到明显差异。希望帮到你。本文还有配套的精品资源点击获取