python 滤波器设计
FIR和IIR滤波器
在介绍滤波器之前,先介绍一下几个采样相关的概念:
- 采样频率:每秒钟采样多少个点,需大于奈奎斯特频率,符号 $f_s$
- 采样宽度:量化目标的位数,音频处理中一般为16位(pyaudio中用几个byte表示)
- 频率分辨率:DFT转换时每个频点间的频率差值,等于采样频率除以参数DFT运算的个数N,即 $f_s / N$
DFT特性:
- 输出相位是奇对称的,输出幅度是偶对称的(对于实数序列来说)
- 输入序列移位,对应频率相移
- 输入是偶函数,则输出相位为0
- 输入是奇函数,则输出振幅为0
- DFT频率相位是相对与该频率点余弦波的相位
- 当输入含有的频率不是DFT频率分辨率倍数时,将发生频谱泄漏
- 对输入序列补零以增加DFT点数,则频域数据插值(没有提高频率分辨率)
- 采样率不变时,增加输入序列的DFT点数,则提高频率分辨率
- 矩形窗的基频等于采样率除以矩形窗的点数(即 $f_s / N_w$),画图时主瓣和旁瓣个数之和为 $N_w - 1$
DFT频率幅度响应公式(sinc函数):
其中 $A_0$ 时振幅,N 是DFT点数, k 是实际频率对应频率分辨率的倍数,为整数时泄漏为0,非整数时有泄漏。
FIR滤波器公式为:
IIR 滤波器公式为:
增益(dB)与振幅(A)公式:
经验分享:
- FIR 低通滤波器设计方法:用理想低通滤波器系数 sin(x)/x 与所选窗函数相乘得到最终 h(k)
- FIR 带通滤波器设计方法:低通滤波器系数乘以正弦信号,以平移低通滤波器达到带通滤波器对目的
- FIR 高通滤波器设计方法:低通滤波器系数乘以 fs/2 对正弦信号(1,-1 交替对序列)
- 时域相乘频域卷积(时域乘以正弦信号后相当于频域移动 — 调频)
- 增加FIR滤波器系数,可以减小过渡带宽
- 对FIR系数加窗,可以减小通带抖动,代价是过渡带宽有所增加
- 频谱响应功率下降一半时对频点称之为截止频率
- $0.7 = \sqrt{0.5}$ 由于功率与振幅对平方成正比,功率下降一半时,振幅下降 0.7
- -3dB = 20 * log10(0.7) 也就是说振幅下降至0.7时,增益下降3dB到达截止频率
- -6dB = 20 * log10(0.5) 也就是说振幅下降一半时,增益下降6dB
最优滤波器设计法
最优滤波器设计法又称为 Parks-McClellan FIR 方法,是当前现实应用中最广为接受对滤波器设计法。 python 中使用 scipy.signal.remez 函数利用最优滤波器设计方法生滤波器系数。
Python中的滤波器函数为:signal.lfilter。
低通滤波器
滤波器系统可以通过手动计算或者使用现成的函数来生成:
- signal.firwin : 生成低通滤波器的 FIR 系数
- signal.remez : 生成带通滤波器的 FIR 系数
滤波器采样点跟频率的关系:
%matplotlib notebook
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
def lowpass_filter(n, fc):
return 2 * fc * np.sinc(2 * fc * np.arange(-n, n, 1.0))
# 截止频率 0.1 * fs
b1 = lowpass_filter(30, 0.1)
b2 = signal.firwin(len(b1), 0.2)
# 频率响应
w1, h1 = signal.freqz(b1)
w2, h2 = signal.freqz(b2)
# 画图
plt.figure(figsize=(12,9))
plt.subplot(2,1,1)
plt.plot(w1/2/np.pi, 20*np.log10(np.abs(h1)+0.01), label = 'ideal')
plt.plot(w2/2/np.pi, 20*np.log10(np.abs(h2)+0.01), label = 'firwin')
plt.legend()
plt.ylabel(u"幅值(dB)")
plt.title('低通滤波器频响')
plt.subplot(2,1,2)
plt.title('滤波器系数')
plt.plot(b1, label='ideal coef')
plt.plot(b2, label='firwin coef')
plt.legend()
plt.show()
均值低通滤波器
均值低通滤波器在现实生活中还是比较常用的,我们一般仅仅是根据常识来使用均值滤波器,却并不清楚其频响特性。 这里先总结一下关于均值滤波器的几个结论:
- 均值滤波器是低通滤波器,与矩形窗类似,其旁瓣增益很高
- 均值滤波器的个数越多,其截止频率越低
下面我们用python代码画出均值滤波器的频响曲线及其滤波效果。
%matplotlib notebook
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
sample_rate = 8000
time = 0.5
# 均值滤波器系统 FIR
b = np.array([0.25, 0.25, 0.25, 0.25])
a = np.array([1.0])
# 生成 0.5s 的音频数据,采样率 8k,频率从 50 ~ 4000
t = np.arange(0, time, 1.0/sample_rate)
x = signal.chirp(t, 50, time, 4000)
# 滤波器处理
y = signal.lfilter(b, a, x, zi=None)
# 计算滤波器的脉冲响应
impulse = np.zeros(7)
impulse[0] = 1
imp_y = signal.lfilter(b, a, impulse)
# 计算频率响应
w, h = signal.freqz(b,a)
# 画图
plt.figure(figsize=(12,6))
plt.subplot(2,2,1)
plt.title('脉冲响应')
plt.plot(imp_y)
plt.subplot(2,2,2)
plt.title('50~4000Hz数据')
plt.plot(t, x)
plt.subplot(2,2,3)
plt.title('频率响应')
plt.plot(w/2/np.pi, 20*np.log10(np.abs(h)+0.01))
plt.subplot(2,2,4)
plt.title('滤波后数据')
plt.plot(t, y)
plt.show()
半带滤波器
半带滤波器因为其稀疏的非零系数使其成为重采样因子为2的整数次幂(2,4,8等)的采样率转换应用对理想滤波器。 半带 FIR 滤波器是频率响应关于点 $f_s/4$ 对称的一种滤波器, 它有以下特点:
- $f_{pass} + f_{stop} = f_s / 2$
- $\Delta f = f_s / 4 - f_{pass} = f_{stop} - f_s / 4$
- 通带和组带抖动相同
- 其 h(k) 的系数交替为零
- 抽头系统 N + 1 必须是4对整数倍
- 所需的乘法器数量大概为 N / 4,因为其中接近一般为0的系数以及系数的对称性
如果使用最优滤波器设计法(Parks-McClellan FIR),则 $\Delta f = f_s /(N + 1)$
下面我们设计一个 11 个抽头的半带滤波器,因为 $\Delta f = \frac{1}{11 + 1} = 0.08$, 所以归一化的半带滤波器的两个频带:
$f_{pass} = 0.25 - 0.08 = 0.17$
$f_{stop} = 0.25 + 0.08 = 0.33$
%matplotlib notebook
import matplotlib.pyplot as plt
import scipy.signal as signal
import numpy as np
def amp2db(amp):
return 20 * np.log10(np.clip(np.abs(amp/abs(amp).max()), 1e-5, 1e100))
## 使用最有滤波器设计法得到半带滤波器系数
N = 11
taps_origin = signal.remez(N, [0.0, 0.17, 0.33, 0.5], [1,0])
taps = taps_origin.copy()
taps[abs(taps) <= 1e-4] = 0.0
## 打印半带滤波器系数
print(' origin taps ')
print('------------------------------------')
for i in range(N):
print(' index %2d %10.6f %10.6f' % (i+1, taps_origin[i], taps[i]))
## 生成频率响应
W, H = signal.freqz(taps, whole=True)
plt.figure(figsize=(12,9))
plt.subplot(211)
plt.title('11抽头半带滤波器系数')
plt.stem(np.arange(len(taps)), taps)
plt.grid()
plt.subplot(212)
plt.title('11抽头半带滤波器频响')
plt.plot(W/2/np.pi, amp2db(H))
plt.axvspan(0.17, 0.33, facecolor='0.5',alpha=0.7)
plt.axvline(0.25, color='g', linestyle='--')
plt.grid('on')
plt.show()
origin taps
------------------------------------
index 1 0.027289 0.027289
index 2 0.000065 0.000000
index 3 -0.078655 -0.078655
index 4 -0.000009 0.000000
index 5 0.308248 0.308248
index 6 0.500045 0.500045
index 7 0.308248 0.308248
index 8 -0.000009 0.000000
index 9 -0.078655 -0.078655
index 10 0.000065 0.000000
index 11 0.027289 0.027289
从上图可以看出,11个系数的半带滤波器有4个为0,剩余的7个系数关于第6个系数(0.500045 约等于 0.5)对称,又由于 0.5 可以使用移位实现,因此只需3个乘法器就可以实现11抽头的半带滤波器。实践证明半带滤波器在降2和升2采样率转换应用中比较好用。