在octave中设计滤波器,会提示软件包属于Octave Forge。
百度Octave Forge,进入。在Files->Octave Forge Packages,选择最新Individual Package Releases。里面有大量软件包。这里的部分软件包已经包含在ubuntu源中,其中的滤波器设计在octave-signal中,可以sudo apt-get install octave-signal安装,在ubuntu14.04中,signal版本是1.2.2,比这里要旧。可以下载signal-1.3.2.tar.gz,然后在octave中用命令pkg install 安装(一般安装到home),然后load,就可以用了。使用pkg install -auto 安装则自动load,更方便。如果 sudo octave 启动,则pkg安装到/usr/local/share/octave/packages中。
软件包中实际主要是众多的.m文件组成。例如fir1.m是用来设计滤波器的。从ubuntu源安装的话,位于/usr/share/octave/packages/signal-1.2.2中,浏览fir1.m可以看到用法说明及例子,或在octave中用help fir1查看使用说明。
1, FIR 滤波器设计
## usage: b = fir1(n, w [, type] [, window] [, noscale]) ......
## Examples:
## freqz(fir1(40,0.3));
## freqz(fir1(15,[0.2, 0.5], 'stop')); # note the zero-crossing at 0.1
## freqz(fir1(15,[0.2, 0.5], 'stop', 'noscale'));
可以直观看到滤波器响应。输入参数是归一化频率。
设计好后,使用Hn=fir1(40,0.3);得到滤波器系数。
firls可以用来设计最优化线性相位FIR滤波器,滤波器几乎可以是任意响应的。一个设计多带通滤波器的例子:
freqz(firls(100,[0 .1,.15 .25,.3 .4,.45 .55,.6 1],[0 0 1 1 0 0 1 1 0 0],[20 1 20 1 20]))
设计了两个通带的带通滤波器。
2, IIR 滤波器设计
IIR 滤波器包括了 巴特沃思、切比雪夫I, II、椭圆等等。
例如 设计切比雪夫II型低通滤波器
(1) 确定参数 [n,wc]=cheb2ord(0.0002,0.001,1,80)
n = 5
wc = 0.0010000
(2)设计滤波器 [b,a]=cheby2(5,80,0.001)
b =
7.8431e-07 -2.3529e-06 1.5686e-06 1.5686e-06 -2.3529e-06 7.8431e-07
a =
1.00000 -4.99721 9.98886 -9.98330 4.98887 -0.99722
(3)查看响应 freqz(b,a)。
这是一个通带很窄的低通滤波器,只有频带的万分之二。freqz缺省显示512频点,可以指定点数如freqz(b,a,32768),显示32768频点。然后放大看,幅度特性在低频有很大波动。
增加滤波器阶数n不能消除通带波动。减少滤波器阶数n并降低衰减指标来设计。
[b,a]=cheby2(4,78.5,0.001)
用freqz(b,a,32768*8)来观察幅频特性,其通带波动明显减弱
查看-6db附近曲线,滤波器精确的阻带衰减应该在78到78.5之间。
其阻带衰减曲线为
(4)稳定性
如果冲激响应h(n)绝对可和,则系统就是稳定的。使用impz来得到所设计滤波器的冲激响应
[x,t]=impz(b,a)
plot(x)
可以看出,该滤波器是稳定的。
设计得到的滤波器系数是双精度浮点值,如果转换为单精度浮点或定点,也需要重新检查幅频特性图和冲激响应图,来确定转换的可靠性。
下面使用cast函数将b,a转换为单精度数
octave:115> bc=cast(b,"single")
bc =
Columns 1 through 3:
1.18767202366143e-04 -4.75059438031167e-04 7.12584471330047e-04
Columns 4 and 5:
-4.75059438031167e-04 1.18767202366143e-04
octave:116> b
b =
Columns 1 through 3:
1.18767204282999e-04 -4.75059439772821e-04 7.12584471072194e-04
Columns 4 and 5:
-4.75059439772821e-04 1.18767204282999e-04
octave:117> ac=cast(a,"single")
ac =
Columns 1 through 3:
1.00000000000000e+00 -3.99856305122375e+00 5.99568986892700e+00
Columns 4 and 5:
-3.99569106101990e+00 9.98564064502716e-01
octave:118> a
a =
Columns 1 through 3:
1.00000000000000e+00 -3.99856300710054e+00 5.99569005368819e+00
Columns 4 and 5:
-3.99569108563957e+00 9.98564039052005e-01
然后观察幅频特性
freqz(bc,ac,32768)
可以看到,滤波器响应变得很坏。
察看冲激响应
[x,t]=impz(bc,ac)
plot(x)
冲激响应不收敛,系统不稳定了。
可见,b,a参数对系数极其敏感。
(5)滤波器转换为sos级联形式
octave:155> [sos,g]=tf2sos(b,a)
sos =
Columns 1 through 3:
1.00000000000000e+00 -1.99993260713065e+00 1.00000000001269e+00
1.00000000000000e+00 -1.99998843707316e+00 9.99999999987311e-01
Columns 4 through 6:
1.00000000000000e+00 -1.99898365433624e+00 9.98983955750905e-01
1.00000000000000e+00 -1.99957935276430e+00 9.99579656213214e-01
g = 1.18767204282999e-04
然后将sos转为单精度浮点数
octave:159> sosc=cast(sos,"single")
sosc =
Columns 1 through 3:
1.00000000000000e+00 -1.99993264675140e+00 1.00000000000000e+00
1.00000000000000e+00 -1.99998843669891e+00 1.00000000000000e+00
Columns 4 through 6:
1.00000000000000e+00 -1.99898362159729e+00 9.98983979225159e-01
1.00000000000000e+00 -1.99957931041718e+00 9.99579668045044e-01
定义输入为单位冲激序列x(n)=[1 0 0 0 0 0 ... ],使用100000点序列。
x=linspace(0,0,100000)
x(1)=1
使用sosc对x滤波来得到冲激响应
y=sosfilt(sosc,x)
乘以增益g
yg=y*g
plot(yg)
对比前面的冲激响应曲线,响应幅度有所下降。直观来看冲激响应曲线是一致的。该系统也是稳定的。
对此冲激响应做FFT变换来获得幅频特性。这里简单认为该冲激响应为有限时宽的,即认为是FIR(有限冲激响应)系统,从而使用freqz(yg)即可得到其幅频特性。如图
通带特性。可以看出,通带衰减了约3db,因此其冲激响应幅度下降。如果使用该参数,需要据此对输出幅度作补偿。
阻带特性
可见,sos参数的系数敏感度较低,实现的滤波器更稳定可靠。
另外,数据长度要大于滤波器趋于稳态响应时间,其时间从单位阶跃响应曲线可以看出。
对于级联形式,增益g是一个很小的值,可以想象各级级联子滤波器会有很大的增益,这对于双精度浮点运算没有问题,但是使用单精度浮点或定点运算可能会比较容易溢出或者损失精度。较好的办法是把增益g分散到各级子滤波器中,使得滤波器具有下面的形式。
为了确定Ak的值,考虑存在一频点w,使得|H(z)|=1,显然这是滤波器通带中的一点(这里不考虑该点w是否存在,仅说明其含义),则Ak取各级联函数在w处的频率响应的倒数认为是合理的,该Ak取值使得各级联函数在频率w处的响应为1,从而使得各级子滤波器的幅度响应在w附近趋于合理。这里不认为这样的取值是最优的,但认为是可以接受的,可以较好解决溢出或精度损失问题。
这里并不打算求解这个w的值,实际上由[h,f]=freqz(b,a),观察abs(h)的值很容易知道w的位置,或者使用[h,f]=freqz(b,a,32768*8)增加频点密度来观察,结合幅频特性曲线更容易找到该点。当然得到的只是近似位置,实际上也不需要精确值。使用同样频点密度的freqz来得到各级子滤波器在该频点的响应值,其绝对值的倒数即为各级的Ak值,注意最后要使用g除以各级的Ak得到末级输出增益(或者将末级输出增益合并到最后一级的Ak中)。
上面阐述了在工程中确定Ak的办法,并没有做严格数学证明,而且通过讨论来说明尽可能用得正确。
滤波器在通带可能包含多个|H(z)|=1的w频点,因此得到的Ak值可能各不相同,可以根据子滤波器的具体要求来决定用哪一个。如果没有特殊要求,可以认为遇到的第一个符合|H(z)|=1的w频点即为所求。
下面结合上述设计的IIR滤波器,给出例子说明具体方法。
(1) 求sos
上述由[sos,g]=tf2sos(b,a) 已求得sos。
(2) 取得子滤波器S1,S2
S1_b(1)=sos(1,1);
S1_b(2)=sos(1,2);
S1_b(3)=sos(1,3);
S1_a(1)=sos(1,4);
S1_a(2)=sos(1,5);
S1_a(3)=sos(1,6);
S2_b(1)=sos(2,1);
S2_b(2)=sos(2,2);
S2_b(3)=sos(2,3);
S2_a(1)=sos(2,4);
S2_a(2)=sos(2,5);
S2_a(3)=sos(2,6);
(3) 计算频率响应及取模,使用32768频点
[H,F]=freqz(b,a,32768);
[Hs1,Fs1]=freqz(S1_b,S1_a,32768);
[Hs2,Fs2]=freqz(S2_b,S2_a,32768);
Hm=abs(H);
Hms1=abs(Hs1);
Hms2=abs(Hs2);
(4) 观察Hm的值,并求出Ak(k=1,2)
octave:27> Hm
Hm =
9.99545383224670e-01
9.96803033882977e-01
9.94312183059719e-01
9.98480051931816e-01
9.70153278642685e-01
8.65366392734935e-01
6.29142895139925e-01
3.99511684648812e-01
2.43693130492970e-01
1.52834995284539e-01
9.90403010191981e-02
可以看到Hm(1)最接近1,就取该频点为所求w。(不必求出具体的w值,因为Hms1(1)和Hms2(1)位于同一频点)
A1=1/Hms1(1);
A2=1/Hms2(1);
octave:30> A1
A1 = 4.47250001934901e-03
octave:31> A2
A2 = 2.62432901186802e-02
(5) 计算末级输出增益
由于所求频点w的响应不精确为1,所以A1*A2的值与g有偏差,这里使用末级增益Ae来修正此偏差。由 A1*A2*Ae=g,得出Ae=g/(A1*A2);
octave:32> Ae=g/(A1*A2)
Ae = 1.01187741087664e+00
一般地,可以将Ae合并到末级子滤波器中 令A2=A2*Ae;
这样最后得到的Ak为
A1 = 4.47250001934901e-03 A2 = 2.65549924581747e-02
将A1、A2乘到各子滤波器的分子项,则全部的子滤波器在w频点幅度响应都接近1。
实际使用中,延迟单元还是要使用双精度来保存才行。目前还没有找到解决办法。
下面是滤波器结构和C语言算法示例,xm为延迟单元。
//
// -1 -2
// b0+ b1*Z + b2*Z
// H(z)=----------------------
// -1 -2
// 1 - a1*Z - a2*Z
//
// xm(n)
// x(n) ------->------O------>------O------>------O------>------ y(n)
// | | -1 b0 |
// ^ v Z ^
// | xm(n-1)| |
// O------<------O------>------O
// | a1 | -1 b1 |
// ^ v Z ^
// | xm(n-2)| |
// O------<------O------>------O
// a2 b2
//
//
// xm(n)= x(n)+a1*xm(n-1)+a2*xm(n-2)
// y(n) =b0*xm(n)+b1*xm(n-1)+b2*xm(n-2)
// xm(n-2)=xm(n-1)
// xm(n-1)=xm(n)
#define SOSNUM 12 // number of IIR sos
float sos[SOSNUM];
#define S1b0 sos[0]
#define S1b1 sos[2]
#define S1b2 sos[4]
#define S1a0 sos[6]
#define S1a1 sos[8]
#define S1a2 sos[10]
#define S2b0 sos[1]
#define S2b1 sos[3]
#define S2b2 sos[5]
#define S2a0 sos[7]
#define S2a1 sos[9]
#define S2a2 sos[11]
#define DATNUM 30000L // number of IIR data
short sampleX[DATNUM];
float resultX[DATNUM];
double S1xm[3]={0.0,0.0,0.0};
double S2xm[3]={0.0,0.0,0.0};
float S1y;
float S2y;
int n;
for(n=0;n<DATNUM;n++)
{
S1xm[0]=sampleX[n] -S1a1*S1xm[1]-S1a2*S1xm[2];
S1y =S1b0*S1xm[0]+S1b1*S1xm[1]+S1b2*S1xm[2];
S1xm[2]=S1xm[1];
S1xm[1]=S1xm[0];
S2xm[0]=S1y -S2a1*S2xm[1]-S2a2*S2xm[2];
S2y =S2b0*S2xm[0]+S2b1*S2xm[1]+S2b2*S2xm[2];
S2xm[2]=S2xm[1];
S2xm[1]=S2xm[0];
resultX[n]=S2y;
}
输入为sampleX,输出为resultX,数据长度DATNUM。sos及数据从octave输出的文件中读取。
网上一个卡尔曼滤波的例子:速度为1的物体的运动轨迹测量,测量结果加入方差为1的高斯噪声。
Z=(1:100); %观测值
noise=randn(1,100); %方差为1的高斯噪声
Z=Z+noise;
X=[0; 0]; %状态
P=[1 0; 0 1]; %状态协方差矩阵
F=[1 1; 0 1]; %状态转移矩阵
Q=[0.0001, 0; 0 0.0001]; %状态转移协方差矩阵
H=[1 0]; %观测矩阵
R=1; %观测噪声方差
figure;
hold on;
for i=1:100
X_ = F*X;
P_ = F*P*F'+Q;
K = P_*H'/(H*P_*H'+R);
X = X_+K*(Z(i)-H*X_);
P = (eye(2)-K*H)*P_;
plot(X(1), X(2)); %画点,横轴表示位置,纵轴表示速度
end