直接开怼。不正确的地方欢迎指出。
目的:多看多写多积累,记录下爬坑和学习的过程,共同进步、一起成长!
功能:基于傅里叶变换处理语音音频,将data文件夹下noised.wav音频文件里的噪声去除。音频文件时长5s,口述一下,大致是5s的高音“吱”里面持续夹杂着低音的“滋”。(注意前后鼻音)
环境:python库:科学计算库numpy和scipy,可视化库matplotlib。
什么是傅里叶变换?
法国科学家傅里叶提出,任何一条周期曲线,无论多么跳跃或不规则,都能表示成一组光滑正弦曲线叠加之和。傅里叶变换即是把一条不规则的曲线拆解成一组光滑正弦曲线的过程。
傅里叶变换的目的是可将时域(即时间域)上的信号转变为频域(即频率域)上的信号,随着域的不同,对同一个事物的了解角度也就随之改变,因此在时域中某些不好处理的地方,在频域就可以较为简单的处理。这就可以大量减少处理信号存储量。
example:弹钢琴
假设有一时间域函数:y = f(x),根据傅里叶的理论它可以被分解为一系列正弦函数的叠加,他们的振幅A,频率ω或初相位φ不同:
y = A 1 s i n ( ω 1 x + ϕ 1 ) + A 2 s i n ( ω 2 x + ϕ 2 ) + A 2 s i n ( ω 2 x + ϕ 2 ) + R y = A_1sin(\omega_1x+\phi_1) + A_2sin(\omega_2x+\phi_2) + A_2sin(\omega_2x+\phi_2) + R y=A1sin(ω1x+ϕ1)+A2sin(ω2x+ϕ2)+A2sin(ω2x+ϕ2)+R
所以傅里叶变换可以把一个比较复杂的函数转换为多个简单函数的叠加,看问题的角度也从时间域转到了频率域,有些的问题处理起来就会比较简单。
导入快速傅里叶变换所需模块
import numpy.fft as nf
通过采样数与采样周期求得傅里叶变换分解所得曲线的频率序列
freqs = nf.fftfreq(采样数量, 采样周期)
通过原函数值的序列j经过快速傅里叶变换得到一个复数数组,复数的模代表的是振幅,复数的辐角代表初相位
nf.fft(原函数值序列) -> 目标函数值序列(复数)
通过一个复数数组(复数的模代表的是振幅,复数的辐角代表初相位)经过逆向傅里叶变换得到合成的函数值数组
nf.ifft(目标函数值序列(复数))->原函数值序列
案例:针对合成波做快速傅里叶变换,得到一组复数序列;再针对该复数序列做逆向傅里叶变换得到新的合成波并绘制。
ffts = nf.fft(y)
sigs7 = nf.ifft(ffts).real
plt.plot(times, sigs7, label=r'$\omega$='+str(round(1 / (2 * np.pi),3)), alpha=0.5, linewidth=6)
案例:针对合成波做快速傅里叶变换,得到分解波数组的频率、振幅、初相位数组,并绘制频域图像。
# 得到分解波的频率序列
freqs = nf.fftfreq(times.size, times[1] - times[0])
# 复数的模为信号的振幅(能量大小)
ffts = nf.fft(sigs6)
pows = np.abs(ffts)
plt.subplot(122)
plt.title('Frequency Domain', fontsize=16)
plt.xlabel('Frequency', fontsize=12)
plt.ylabel('Power', fontsize=12)
plt.tick_params(labelsize=10)
plt.grid(linestyle=':')
plt.plot(freqs[freqs >= 0], pows[freqs >= 0], c='orangered', label='Frequency Spectrum')
plt.legend()
plt.tight_layout()
plt.show()
含噪信号是高能信号与低能噪声叠加的信号,可以通过傅里叶变换的频域滤波实现降噪。
通过FFT使含噪信号转换为含噪频谱,去除低能噪声,留下高能频谱后再通过IFFT留下高能信号。
案例:基于傅里叶变换的频域滤波为音频文件去除噪声。
import numpy as np
import numpy.fft as nf
import scipy.io.wavfile as wf
import matplotlib.pyplot as plt
# 读取音频文件
# sample_rate:采样率
# noised_sigs: 存储音频中每个采样点的采样位移
sample_rate, noised_sigs = wf.read('./data/noised.wav')
print(sample_rate, noised_sigs.shape)
times = np.arange(noised_sigs.size) / sample_rate
plt.figure('Filter', facecolor='lightgray')
plt.subplot(221)
plt.title('Time Domain', fontsize=16)
plt.ylabel('Signal', fontsize=12)
plt.tick_params(labelsize=10)
plt.grid(linestyle=':')
plt.plot(times[:178], noised_sigs[:178],c='orangered', label='Noised')
plt.legend()
# 傅里叶变换后,绘制频域图像
freqs = nf.fftfreq(times.size, times[1]-times[0])
complex_array = nf.fft(noised_sigs)
pows = np.abs(complex_array)
plt.subplot(222)
plt.title('Frequency Domain', fontsize=16)
plt.ylabel('Power', fontsize=12)
plt.tick_params(labelsize=10)
plt.grid(linestyle=':')
plt.semilogy(freqs[freqs>0], pows[freqs>0], c='limegreen',label='Noised')
plt.legend()
# 寻找能量最大的频率值
fund_freq = freqs[pows.argmax()]
# where函数寻找那些需要抹掉的复数的索引
noised_indices = np.where(freqs != fund_freq)
# 复制一个复数数组的副本,避免污染原始数据
filter_complex_array = complex_array.copy()
filter_complex_array[noised_indices] = 0
filter_pows = np.abs(filter_complex_array)
plt.subplot(224)
plt.xlabel('Frequency', fontsize=12)
plt.ylabel('Power', fontsize=12)
plt.tick_params(labelsize=10)
plt.grid(linestyle=':')
plt.plot(freqs[freqs >= 0], filter_pows[freqs >= 0],c='dodgerblue', label='Filter')
plt.legend()
filter_sigs = nf.ifft(filter_complex_array).real
plt.subplot(223)
plt.xlabel('Time', fontsize=12)
plt.ylabel('Signal', fontsize=12)
plt.tick_params(labelsize=10)
plt.grid(linestyle=':')
plt.plot(times[:178], filter_sigs[:178],c='hotpink', label='Filter')
plt.legend()
# 生成音频文件
wf.write('./data/filter.wav',sample_rate,filter_sigs)
plt.show()
代码还有待优化和改进