如何用Python为录音添加低通滤波并利用FFT去除白噪声?
嘿,我来帮你搞定录音白噪声降噪和低通滤波的问题!先从你提到的FFT频域降噪说起,再给你演示更专业的低通滤波实现,顺便把你的代码调整得更完善些~
一、利用FFT频域数据实现白噪声降噪
白噪声在频域通常是均匀分布的,而语音信号的能量大多集中在200-3000Hz这个区间。所以我们的核心思路是:把频域中属于噪声的分量(比如高频或者能量极低的部分)置0,再通过逆FFT转回时域信号。
先给你修正并补充后的代码,里面加了详细的注释:
from scipy.io import wavfile import matplotlib.pyplot as plt import numpy as np # 你之前漏了这个关键导入哦 from numpy.fft import fft, fftfreq, ifft # 读取wav文件:注意第二个参数"wb"是写文件用的,读文件不需要,直接传路径就行 fs, data = wavfile.read("a.wav") n = len(data) # 如果是立体声音频,先转成单声道(取左右声道的平均值) if len(data.shape) > 1: data = data.mean(axis=1) # 计算实际频率值(之前的freqs是归一化频率,加上1/fs才是Hz单位) freqs = fftfreq(n, 1/fs) mask = freqs > 0 # 只取正频率部分用于绘图 # 计算原信号的FFT fft_vals = fft(data) # --- 核心:频域降噪处理 --- # 假设我们观察频谱后,确定3000Hz以上是主要噪声频段,创建掩码保留低频 cutoff_freq = 3000 noise_mask = np.abs(freqs) <= cutoff_freq # 保留<=3000Hz的频率分量 filtered_fft_vals = fft_vals * noise_mask # 把噪声频段的FFT值置0 # 逆FFT转回时域,取实部(计算过程中会产生微小虚部,需要去掉) filtered_data = np.real(ifft(filtered_fft_vals)) # 转成原音频的整数类型(比如int16,避免保存时出错) filtered_data = filtered_data.astype(data.dtype) # 可视化原信号和滤波后的频谱对比 plt.figure(figsize=(12, 8)) plt.subplot(2, 1, 1) plt.plot(freqs[mask], 2 * np.abs(fft_vals/n)[mask]) plt.title("Original Signal Spectrum") plt.xlabel("Frequency (Hz)") plt.ylabel("Amplitude") plt.subplot(2, 1, 2) plt.plot(freqs[mask], 2 * np.abs(filtered_fft_vals/n)[mask]) plt.title("Filtered Signal Spectrum") plt.xlabel("Frequency (Hz)") plt.ylabel("Amplitude") plt.tight_layout() plt.show() # 可选:播放滤波后的音频 # import simpleaudio as sa # play_obj = sa.play_buffer(filtered_data, 1, 2, fs) # play_obj.wait_done() # 保存降噪后的音频文件 wavfile.write("denoised_a.wav", fs, filtered_data)
如果你的噪声不是简单的高频噪声,还可以用噪声样本减法:先录一段没有语音的纯噪声,计算它的平均频谱,然后从原信号频谱中减去这个噪声频谱(要注意避免出现负幅值,取最大值0),这样降噪效果会更精准。
二、用Python实现专业的低通滤波
刚才用FFT手动置0是一种快速方法,但专业场景下更推荐用scipy.signal模块设计平滑的低通滤波器(比如巴特沃斯滤波器),这样能避免频谱泄漏和信号失真。
给你完整的实现代码:
from scipy.io import wavfile import matplotlib.pyplot as plt import numpy as np from scipy.signal import butter, lfilter, freqz # 定义巴特沃斯低通滤波器的系数生成函数 def butter_lowpass(cutoff, fs, order=5): nyq = 0.5 * fs # 奈奎斯特频率,是采样率的一半 normal_cutoff = cutoff / nyq # 归一化截止频率 # 生成滤波器的分子(b)和分母(a)系数 b, a = butter(order, normal_cutoff, btype='low', analog=False) return b, a # 定义滤波函数,应用滤波器到信号上 def butter_lowpass_filter(data, cutoff, fs, order=5): b, a = butter_lowpass(cutoff, fs, order=order) filtered_data = lfilter(b, a, data) return filtered_data # 读取音频文件 fs, data = wavfile.read("a.wav") if len(data.shape) > 1: data = data.mean(axis=1) # 设置滤波参数 cutoff_freq = 3000 # 低通截止频率,单位Hz order = 4 # 滤波器阶数:阶数越高,过渡带越陡,但可能引入更多相位失真 # 应用低通滤波 filtered_data = butter_lowpass_filter(data, cutoff_freq, fs, order) # 转成原音频的数据类型 filtered_data = filtered_data.astype(data.dtype) # 可视化滤波器的频率响应(可选,帮你理解滤波器效果) b, a = butter_lowpass(cutoff_freq, fs, order) w, h = freqz(b, a, worN=8000) plt.figure(figsize=(12, 8)) plt.subplot(2, 1, 1) plt.plot(0.5 * fs * w / np.pi, np.abs(h), 'b') plt.plot(cutoff_freq, 0.5 * np.sqrt(2), 'ko') # 巴特沃斯滤波器的-3dB点 plt.axvline(cutoff_freq, color='k', linestyle='--') plt.xlim(0, 0.5 * fs) plt.title("Lowpass Filter Frequency Response") plt.xlabel("Frequency (Hz)") plt.grid() # 对比原信号和滤波后信号的波形(取前1000个样本) plt.subplot(2, 1, 2) plt.plot(data[:1000], label='Original Signal') plt.plot(filtered_data[:1000], label='Filtered Signal') plt.legend() plt.title("Signal Waveform (First 1000 Samples)") plt.tight_layout() plt.show() # 保存滤波后的音频 wavfile.write("lowpass_filtered_a.wav", fs, filtered_data)
这个方法的优势是滤波器的过渡带更平滑,不会像手动FFT置0那样产生明显的频谱突变,能更好地保留语音的自然感。
内容的提问来源于stack exchange,提问作者umuterc
相关产品推荐
相关产品推荐

