基于FFT频谱分析的Matlab语音分离滤波器设计
简介面向语音分离技术学习与项目开发的Matlab代码包适合电子信息工程、计算机、数学等专业学生用于课程设计、期末大作业或毕业设计。包内包含完整可运行程序通过FFT分析音频频谱并借助合适滤波器实现不同语音信号的分离代码采用参数化编程可灵活调整参数注释详细便于二次开发。整个压缩包共7个文件其中包含2个M脚本、3个MAT数据文件和2张结果示意图资源大小19.55MB数据文件可直接载入运行图片可直观比对验证分离效果。已有100人学习下载适合希望快速掌握语音分离实现流程并开展实验验证的初学者。该资源不仅提供可直接运行的工程文件还展示了从数据导入、特征提取到FFT分析滤波的完整处理链条可帮助理解频域分析与滤波器设计的工程落地方法也便于在此基础上扩展更复杂的多源分离场景。1. 语音分离的核心前提FFT分析先于滤波器设计两个语音信号叠在一起时直接在时域里切分是切不开的。人耳能同时听清两个人的说话本质上是大脑把混叠声音实时分解成了不同频段。机器要做语音分离通常也是同样思路先把混合音频做FFT从频谱上找出语音主体和干扰各自占据的频率范围再按分析结果设计合适的滤波器把目标语音的频段保留下来。滤波器承担的不只是“切频段”这个动作它的中心频率、边界和阶数都要由FFT分析结果来定这正是“先分析、后分离”的原因。这套流程在Matlab里可以完整走通读取wav文件、加窗做短时傅里叶变换、从平均频谱估计目标频段、用designfilt生成滤波器、filtfilt完成零相位滤波最后把分离前后的频谱放在一起验证。按这个顺序写出来的代码规模不大但每一步的参数都会直接影响分离音质。适合做音频预处理、语音增强和说话人分离前置工作的工程师参考也方便在实验环境里快速定位滤波器的设计依据。2. 用FFT分析混合语音频谱从整段到短时变换2.1 直接对整段音频做FFT的结果能说明什么语音是典型的非平稳信号。一句话里停顿、清音、浊音交替出现基频和共振峰时刻都在变化。如果对整段wav直接做一次FFT得到的是整段时间内的平均频谱两个说话者各自的时变特征会被平均掉。所以整段FFT并不适合直接定位声音重叠的时间点但在估计稳定噪声、确认频段覆盖范围时仍然有用。下面的代码把读入的音频转成频谱图[x, fs] audioread(mixed.wav); if size(x, 2) 1 x mean(x, 2); % 双声道取平均避免声道相位差压低频谱幅度 end N length(x); X fft(x); % 单次FFT长度等于采样点数 f (0:N-1) * fs / N; % 序号换算成实际频率 mag abs(X); % 复数谱取幅度相位在这里不参与判断 figure; plot(f(1:N/2), 20*log10(mag(1:N/2) eps)); xlabel(Frequency (Hz)); ylabel(Magnitude (dB)); grid on;这里的N是整段音频的采样点数频率轴f从0排到fs取前一半是因为实信号频谱关于奈奎斯特频率fs/2对称。20*log10把幅度转到dB刻度小能量的干扰峰也能看清加eps是为了防止静音时刻log10(0)输出NaN。整段FFT的频率分辨率等于fs/N一个10秒的语音文件算下来谱线间距只有0.1Hz频率方向看似很精细但时间方向上没有任何信息。用它的平均谱来设计滤波器带宽会被平均得过宽或过窄最终边界还是要回到短时频谱上取。2.2 短时FFT语音分离的分帧频谱更适合设计滤波器要用FFT分析驱动滤波器设计常见做法是分帧加窗也就是短时傅里叶变换STFT。Matlab里spectrogram调用返回时频矩阵frameLen 2048; % 帧长2048个采样点 noverlap 1024; % 帧叠一半保留时间连续性 nfft 4096; % FFT点数补零提高频谱插值密度 win hann(frameLen, periodic); % 周期性汉宁窗减少频谱泄漏 [S, f, t] spectrogram(x, win, noverlap, nfft, fs); figure; imagesc(t, f, 20*log10(abs(S) eps)); axis xy; % 把纵轴调成频率向上 xlabel(Time (s)); ylabel(Frequency (Hz)); colorbar;frameLen决定频率分辨率公式是fs / frameLen。采样率fs16000Hz时frameLen2048对应约7.8Hz的分辨率这对语音基频和窄带干扰都够用。noverlap取frameLen的一半时时间窗移动量约为frameLen/2个采样点短时能量变化不会出现台阶感。nfft可以大于frameLen因此实现时用了补零但补零只把频谱曲线插值得更平滑并不能提高真实分辨率。hann窗的主瓣比矩形窗宽旁瓣低能抑制频谱泄漏。短时FFT图上重点看两个维度纵向是频率位置横向是持续时间。窄带干扰如果从第3秒持续到第8秒图上体现为一条水平亮带语音则是断续的横向条纹。水平亮带对应的频率就是滤波器需要陷波的位置横向条纹覆盖的频率范围就是目标语音的通带范围。2.3 从FFT结果里读取四个关键参数语音分离滤波器需要依据的频域数据可以从下面几张表里的特征直接提取FFT谱上看到的特征读取的参数对滤波器设计的意义单一窄带干扰亮线中心频率fc、3dB带宽B带阻滤波器或陷波器的中心频率与阻带宽度目标语音浊音段的横条纹基频f0、谐波间隔Δf带通滤波器通带中心位置梳状滤波的间隔估计高频随机白噪声底噪噪声平台电平通带上限和滤波器阶数的上限两个语音的频段重叠情况重叠区宽度判断能否线性分离重叠过大就改用时频掩码读取方式可以先画出声谱图再用findpeaks找峰读取中心频率的代码如下avgSpectrum mean(abs(S), 2); % 时间维平均压平语音短时波动 [peakVal, peakIdx] findpeaks(avgSpectrum, ... MinPeakHeight, max(avgSpectrum)*0.4); fc f(peakIdx(1)); % 取最强峰的频率作为窄带干扰中心 halfMask avgSpectrum peakVal/2; % 3dB带宽对应的掩码 fL f(find(halfMask, 1, first)); fH f(find(halfMask, 1, last));findpeaks限制了候选峰高度必须超过最大幅度的40%避免把琐碎毛刺当成真实共振峰适合窄带干扰的情况。halfMask用“幅度大于peakVal的一半”来圈定3dB带宽第一次分离时可以直接把fL和fH当作通带边界。如果目标语音与干扰的频谱重叠区很明显3dB边界会被拉宽到分不开的程度这种场景线性滤波器已经失效下一章给出判断边界和替代方案。3. 根据FFT频谱特征选择语音分离滤波器并设定参数3.1 频谱形态与滤波器类型的对应关系上一章拿到目标语音与干扰的频谱位置后接下来判断用什么滤波器。语音分离中常见的四类对应关系如下表FFT分析结论滤波器方案Matlab设计入口语音集中在低频带外是宽带噪声低通designfilt(lowpassiir)语音在特定频段两个频段外都是噪声带通designfilt(bandpassiir)语音分布宽中间有一个固定窄带干扰带阻或陷波designfilt(bandstopiir)干扰峰较多且间隔固定多陷波并联或梳状多个bandstopiir串联后filtfilt为什么表格里的方案能直接用关键在于语音分离的目标不是“还原全部声音”而是“保留目标频段、压制其它频段”。带通滤波器会把目标语音频段之外的成分压制掉保留的频段正好覆盖语音主要能量窄带干扰位于语音频段内部时用带通切不掉它必须先用带阻或陷波在该频率上抠出一个缺口。干扰不多时用多陷波串联干扰密集时再考虑梳状滤波器。3.2 截止频率怎么定以FFT测量值为准并留出余量实际工作中直接按照FFT得到的3dB边界设计滤波器往往把人声处理“闷”或者在没有语音的段落里留下可闻噪声。原因在于滤波器过渡带的存在如果把通带边界恰好定在fL和fH上语音的边界频率落入过渡带后会被部分衰减听感发闷如果阻带离干扰峰太近干扰无法完全压下去。需要留出余量expL max(1, fL * 0.9); % 下边界外扩10%保住语音低频成分 expH fH * 1.1; % 上边界外扩10%保住共振峰高频尾 bpFilt designfilt(bandpassiir, ... StopbandFrequency1, max(1, expL*0.8), ... PassbandFrequency1, expL, ... PassbandFrequency2, expH, ... StopbandFrequency2, expH*1.2, ... PassbandRipple, 1, ... StopbandAttenuation1, 30, ... StopbandAttenuation2, 30, ... SampleRate, fs);这里用“阻带到通带再到阻带”的四频点参数模式需要同时指定四个频率。StopbandFrequency1 和 StopbandFrequency2 决定阻带边界PassbandFrequency1 和 PassbandFrequency2 决定通带边界两组频率之间的区域就是过渡带。阻带衰减30dB对语音分离是一个居中值压宽带噪声足够又不至于让高阶滤波器带来明显群延迟失真。通带纹波1dB对可懂度影响很小能保留语音细节。3.3 滤波器阶数选型不是越高越好阶数越大过渡带越陡但相位失真和计算量都会上升。语音分离常见的选型如下表场景推荐结构阶数参考离线处理不关心实时延迟FIR least-squares128~512阶线性相位实时性要求高IIR Butterworth4~8阶配合filtfilt单个窄带干扰二阶陷波级联每个频点约2阶FIR的优点是相位线性离线分离时不会产生明显相位畸变IIR的优点是计算量小结合filtfilt做双向滤波可以部分抵消相位问题分离后的频谱形状与原始频谱的一致性更好。离线语音分离用IIR配合filtfilt既省资源又好控制。提示不要用filter()直接做语音分离。filter()把滤波器相位响应完整作用在信号上IIR带来的群延迟在听感上表现为“拖尾”filtfilt()先正向再反向滤波相位偏移互相抵消语音分离建议使用filtfilt()。代码里已经把filtfilt用到第4章的示例脚本中这里单独提出来是因为它对听感的影响较大。若分离出来的语音听起来发虚、发闷优先检查是不是把filtfilt写成了filter。4. 用Matlab把FFT分析和语音分离滤波器串成完整流程4.1 一个可直接保存运行的语音分离脚本把前面的分析串起来得到一个完整脚本。这个脚本从wav读入开始到输出分离后的wav结束全程自动%% 语音分离主流程FFT分析 → 滤波器设计 → 滤波输出 clear; close all; clc; [x, fs] audioread(mixed_voice.wav); if size(x, 2) 1 x mean(x, 2); % 双声道先平均 end x x / max(abs(x)); % 幅度归一化避免后续dB计算溢出 %% 1. 短时FFT分析 frameLen 2048; % 帧长fs16000时频率分辨率约7.8Hz noverlap 1024; % 帧叠一半 nfft 4096; win hann(frameLen, periodic); [S, f, t] spectrogram(x, win, noverlap, nfft, fs); avgSpectrum mean(abs(S), 2); % 时间平均频谱用于稳定估计边界 %% 2. 自动估计通带边界 [peakVal, ~] max(avgSpectrum); aboveHalf avgSpectrum peakVal / 2; if sum(aboveHalf) 5 error(频谱峰值不明显无法自动估计频段请检查信号); end fL f(find(aboveHalf, 1, first)); fH f(find(aboveHalf, 1, last)); % 两段外扩降低过渡带影响 expL max(1, fL * 0.9); expH fH * 1.1; %% 3. 根据FFT结果生成带通滤波器 bpFilt designfilt(bandpassiir, ... StopbandFrequency1, max(1, expL*0.8), ... PassbandFrequency1, expL, ... PassbandFrequency2, expH, ... StopbandFrequency2, expH*1.2, ... PassbandRipple, 1, ... StopbandAttenuation1, 30, ... StopbandAttenuation2, 30, ... SampleRate, fs); %% 4. 零相位滤波并写回文件 y filtfilt(bpFilt, x); audiowrite(separated_voice.wav, y, fs); %% 5. 分离前后频谱对比 [Sx, ~, ~] spectrogram(x, win, noverlap, nfft, fs); [Sy, ~, ~] spectrogram(y, win, noverlap, nfft, fs); figure; subplot(2,1,1); imagesc(t, f, 20*log10(abs(Sx)eps)); axis xy; title(Before separation); subplot(2,1,2); imagesc(t, f, 20*log10(abs(Sy)eps)); axis xy; title(After separation);这段代码的重点在第二步和第三步。第二步里aboveHalf用最大幅度的一半做阈值把频谱中能量较强的范围整体圈出来find取第一个和最后一个True索引得到的就是目标的3dB频带。sum(aboveHalf) 5这个判断是为了防止信号里只有单频干扰、缺少语音主体时把带通滤波器退化成窄带滤波器。第三步里带通滤波器的四个频率参数全部由第二步的测量值推导没有手工经验值。4.2 参数调整哪些值影响分离质量实际使用中可以按下面的表调整参数参数默认值调大后的效果调小后的效果frameLen2048频率分辨率更高时间分辨率变差瞬态语音拖影时间细节更好频谱变粗noverlap1024即50%帧间更平滑计算量增大可能出现频谱块状感expL/expH系数0.9 / 1.1通带更宽语音更自然抗噪变弱语音发闷高频毛刺减少StopbandAttenuation30dB干扰压制更好阶数提高带外残留明显重点是外扩系数不要太过头。把下边界外扩到0.5倍fL低频轰鸣会全部进入通带外扩到0.98倍过渡带起始点正好压在语音频带上滤波后基频和第一共振峰被削掉男性声音听感会明显变闷。fL和fH本身来自3dB边界外扩10%到20%是常见做法。4.3 干扰在目标频段内部怎么处理带通滤波适用于目标语音频段外有干扰的场景。如果FFT谱显示一个锐利的窄带峰出现在目标频段内部带通无能为力。此时一般做法是先带阻后带通先用bandstopiir把窄带干扰压掉再进带通分离。窄带滤波器如下notchFilt designfilt(bandstopiir, ... StopbandFrequency1, fc - 20, ... StopbandFrequency2, fc 20, ... PassbandFrequency1, fc - 80, ... PassbandFrequency2, fc 80, ... PassbandRipple, 1, ... StopbandAttenuation, 20, ... SampleRate, fs); x filtfilt(notchFilt, x);这里的fc来自findpeaks检测出的中心频率。20Hz和80Hz不是固定经验值而是按频谱上峰宽度定的如果干扰峰在FFT谱上的宽度已经超过几十Hz需要先量出3dB带宽再把Stopband宽度设成3dB带宽的2倍左右。这样处理不会把语音共振峰一并挖掉因为真正的窄带干扰比语音共振峰窄得多。5. 用分离前后的频谱验证语音分离效果5.1 残差谱对比法分离是否成功不要只靠“听起来还行”的主观判断。常见做法是生成分离前后的频谱残差[Sx, f, t] spectrogram(x, win, noverlap, nfft, fs); [Sy, ~, ~] spectrogram(y, win, noverlap, nfft, fs); residual mean(abs(Sx) - abs(Sy), 2); plot(f, 20*log10(residual eps));理想情况下通带内残差应接近0通带外残差接近原始信号频谱的幅度。因为带外部分被滤波器压掉带内部分几乎原样通过。分开来看通带内残差大说明通带增益不足或边界收得太紧通带外残差小则说明滤波器阻带衰减够高。5.2 两个比值指标通带保留率和带外残留率进一步用数值判断假设freqMask已经把通带范围圈成0/1向量freqMask (f fL) (f fH); pEnergy sum(mean(abs(Sy), 2).^2 .* freqMask) / ... (sum(mean(abs(Sx), 2).^2 .* freqMask) eps); sEnergy sum(mean(abs(Sy), 2).^2 .* ~freqMask) / ... (sum(mean(abs(Sx), 2).^2 .* ~freqMask) eps);pEnergy是通带保留率sEnergy是带外残留率。语音分离时pEnergy在0.85以上听感基本自然sEnergy低于0.1时带外干扰明显被压制。如果sEnergy高于0.2先看阻带衰减是否调低再看通带边界外扩是否过宽如果pEnergy低于0.7就降低一部分带外抑制要求把expL、expH重新外扩10%再跑一次。这比在滤波器设计界面里反复试探要高效。5.3 一个实用技巧先把干扰峰列表提取出来陷入“滤波器参数来回调”的时候我一般先做一步预处理把FFT谱上的所有谱峰提取成列表再根据列表决定哪些频点需要动态调整。[pks, locs] findpeaks(avgSpectrum, SortStr, descend); candidateFreq f(locs(1:min(6, length(locs))));倒序排列后取前6个峰可以得到语音分离目标频段内和频段外的主要干扰位置。设计滤波器时直接把candidateFreq作为初始参数列表分别做成带阻或带通。这样做的好处是当FFT分析结果明显多于两个频段成分时不需要手动从spectrogram图上逐个读坐标而是让脚本给出候选再根据分离效果决定保留哪几个频率。滤波器参数不再是一组定死的通带数值而是随FFT结果更新的动态列表这就是语音分离里“分析信号”和“选择滤波器”结合得最紧的一步。本文还有配套的精品资源点击获取