简介:一份面向数字信号处理课程学习的Matlab版线性相位FIR数字滤波器实验文档,适合高校电子信息类学生及需要完成相关实验报告的研究者。内容围绕线性相位FIR滤波器展开,系统阐述了实验目的、基本原理、四种类型(Ⅰ~Ⅳ)的对称条件、冲激响应与符幅特性、零极点分布特点,并附有amplres.m通用程序及典型算例的Matlab调试代码,可直接对照运行。资源包内共1个doc文件,压缩包大小仅82KB,文档结构完整,实验步骤与结果分析清晰,便于复制修改并应用到自己的实验报告中。目前已有711人学习浏览。通过本实验可深入理解FIR滤波器相对IIR易于实现严格线性相位的特点,掌握利用Matlab编写程序分析滤波器幅度特性与零点分布的方法,为后续音频、图像等信号处理应用打下基础。
1. 线性相位 FIR 数字滤波器:实验代码为什么值得跑一遍
数字信号处理实验里,线性相位 FIR 滤波器是绕不开的关口:课程要求理解四种类型的对称条件、画出带正负号的幅度特性(符幅特性),再通过零极点分布图验证零点成组规律。直接看教材公式容易云里雾里,这份实验报告的好处是把 code 和结论一一对上了——amplres.m 一行行改出来的 type 结果,直接告诉你属于类型Ⅰ还是类型Ⅳ,图也全。适合正在做数字信号处理实验、需要现成 MATLAB 脚本和结果分析的同学,也适合准备期末、想把零点分组规律彻底弄明白的人。照着跑一遍,四类滤波器特性比背十遍定义都牢靠。
2. 核心脚本 amplres.m:类型判定与符幅特性的计算逻辑
2.1 线性相位条件的数学本质
设 FIR 滤波器单位脉冲响应为 h(n),长度为 N,系统函数写作 H(z) = Σ h(n)z⁻ⁿ。当系数满足对称或反对称条件时,滤波器就具有严格线性相位。四种类型的分法很简单:
- 类型Ⅰ:h(n) = h(N-1-n),N 为奇数
- 类型Ⅱ:h(n) = h(N-1-n),N 为偶数
- 类型Ⅲ:h(n) = -h(N-1-n),N 为奇数
- 类型Ⅳ:h(n) = -h(N-1-n),N 为偶数
但要真正研究幅度特性,直接对 H(e^jω) 取模会丢掉正负号信息。因为线性相位滤波器在部分频点上幅值会变号(比如类型Ⅱ在 ω=π 处过零),取模后看的是 |A|,而符幅特性 A 保留符号,能更清楚看到零点位置和滤波器能做什么。amplres.m 干的就是这件事。
2.2 amplres.m 逐段拆解
先看这份实验自带的通用程序:
function [A, w, type, tao] = amplres(h) N = length(h); tao = (N-1)/2; % 群延迟 L = floor((N-1)/2); % 对称轴左侧点数 n = 1:L+1; w = [0:500]*2*pi/500; % 频率轴 0~2pi,共501个采样点 if all(abs(h(n) - h(N-n+1)) < 1e-10) A = 2 * h(n) * cos(((N+1)/2 - n)' * w) - mod(N,2) * h(L+1); type = 2 - mod(N, 2); elseif all(abs(h(n) + h(N-n+1)) < 1e-10) & (h(L+1) * mod(N,2) == 0) A = 2 * h(n) * sin(((N+1)/2 - n)' * w); type = 4 - mod(N, 2); else error('错误:这不是线性相位滤波器!') end先说判定逻辑。第一层 if 判断对称条件,用 abs(h(n) - h(N-n+1)) < 1e-10 而不是直接比较相等,是为了容忍浮点误差。第二层判断反对称条件,注意它还多了一个条件 h(L+1) * mod(N,2) == 0,这是为了处理类型Ⅲ:当 N 为奇数时,中间点系数必须为 0,否则反对称不成立。
再看 type 的取值。对称条件下,N 为奇数时 mod(N,2)=1,type = 2-1 = 1(类型Ⅰ);N 为偶数时 mod(N,2)=0,type = 2(类型Ⅱ)。反对称条件下,N 为奇数时 type = 4-1 = 3(类型Ⅲ);N 为偶数时 type = 4(类型Ⅳ)。这个映射非常优雅,一行代码把奇偶性和对称性同时编码进去了。
最后看 A 的计算。第一部分 2*h(n)*cos(((N+1)/2 - n)'*w) 是对称部分叠加的余弦项,第二项 -mod(N,2)*h(L+1) 是修正项:当 N 为奇数时,中间点 h(L+1) 在叠加时只应该计入一次(因为它对应 n=(N+1)/2 的对称中心),不修正就会多算一次。这个细节很多人第一次copy代码时不理解,建议对照 N=9 的例子手推一遍,符号就清楚了。
2.3 频率轴与绘图参数说明
w = [0:500]2pi/500 生成 0 到 2π 的 501 个均匀采样点。后面绘图时用 plot(w/pi, A),横轴是归一化频率(单位为 π),这样看阻带、通带位置更直观。注意 w 覆盖到 2π,因为 FIR 频率响应是周期性的,画满一个周期才能看到完整特性,尤其在分析类型Ⅱ和类型Ⅳ在 ω=π 处是否强制过零时,这个范围是必要的。
运行这段脚本,命令行窗口会回显 type 值,图里显示符幅特性曲线。想看群延迟 tao 的话,直接 disp(tao) 即可,它总是 (N-1)/2,与滤波器类型无关。
3. 四种类型逐一仿真:冲激响应与符幅特性对照
3.1 类型Ⅰ:N=9,对称系数
h = [3, -1, -5, 4, 6, 4, -5, -1, 3]; M = length(h); n = 0:M-1; [A, w, type, tao] = amplres(h); type subplot(2,1,1), stem(n, h); ylabel('h(n)'); xlabel('n'); subplot(2,1,2), plot(w/pi, A); ylabel('A'); xlabel('\pi');这段代码先用 stem 画单位脉冲响应序列,展示系数的对称结构;随后用 amplres 求符幅特性并画在第二张子图。type 回显为 1,对应类型Ⅰ。注意这个例子 N=9 是奇数且系数对称,符幅特性在 ω=0 和 ω=π 处都没有强制零点,因此可以用于低通、高通、带通、带阻任意一种滤波器设计,灵活性最高。在这里我建议你把 h 改成任意奇数长度对称序列试试,type 输出应该始终是 1。
3.2 类型Ⅱ:N=8,偶数长度对称系数
h = [3, -1, -5, 4, 4, -5, -1, 3]; M = length(h); n = 0:M-1; [A, w, type, tao] = amplres(h); type subplot(2,1,1), stem(n, h); ylabel('h(n)'); xlabel('n'); subplot(2,1,2), plot(w/pi, A); ylabel('A'); xlabel('\pi');type 回显为 2。类型Ⅱ的关键特征是 N 为偶数,幅度特性关于 ω=π 反对称,也就是说 A(π)=0 是结构性的。这意味着类型Ⅱ的滤波器在高频处天然衰减到零,做不了高通和带阻。如果你用 fir1 设计一个高通 FIR 滤波器,注意它自动改用类型Ⅳ实现,正是因为类型Ⅱ在 π 处有强制零点。仿真时观察第二个子图,曲线在 ω/π=1 处必然过零,与理论完全吻合。
3.3 类型Ⅲ:N=9,反对称系数
h = [3, -1, -5, 4, 0, -4, 5, 1, -3]; M = length(h); n = 0:M-1; [A, w, type, tao] = amplres(h); type subplot(2,1,1), stem(n, h); ylabel('h(n)'); xlabel('n'); subplot(2,1,2), plot(w/pi, A); ylabel('A'); xlabel('\pi');type 回显为 3。类型Ⅲ是反对称且 N 为奇数,注意 h 正中间那个元素是 0,这是 N 为奇数时反对称序列的硬性要求——中心元素只能等于它自己的相反数,唯一的解就是 0。如果把这个元素改成非零值,amplres.m 第二层判断里 h(L+1)*mod(N,2)==0 就会触发,直接报错"这不是线性相位滤波器"。类型Ⅲ在 ω=0 和 ω=π 处都是强制零点,因此只能实现带通滤波器,这是它在工程上用得少的主要原因。观察第二个子图,曲线在两个端点都过零,中间段才有通带。
3.4 类型Ⅳ:N=8,反对称系数
h = [3, -1, -5, 4, -4, 5, 1, -3]; M = length(h); n = 0:M-1; [A, w, type, tao] = amplres(h); type subplot(2,1,1), stem(n, h); ylabel('h(n)'); xlabel('n'); subplot(2,1,2), plot(w/pi, A); ylabel('A'); xlabel('\pi');type 回显为 4。类型Ⅳ是反对称且 N 为偶数,幅度特性在 ω=0 处强制为零,在 ω=π 处可以取任意值。因此它可以做高通、带通,不能做低通和带阻。这正好与类型Ⅱ形成互补关系——一个是"零在高端",一个是"零在低端"。实际设计微分器和希尔伯特变换器时,类型Ⅲ和类型Ⅳ是首选,因为它们天然在低频段抑制直流分量。
3.5 四类滤波器合并对比绘图
实验过程中要求把四个滤波器的冲激响应和符幅特性画在同一张图,方便对比。核心代码如下:
h1 = [3, -1, 2, -3, 5, -3, 2, -1, 3]; h2 = [3, -1, 2, -3, -3, 2, -1, 3]; h3 = [3, -1, 2, -3, 0, 3, -2, 1, -3]; h4 = [3, -1, 2, -3, 3, -2, 1, -3]; for k = 1:4 eval(['[A,w,type,tao] = amplres(h' num2str(k) ');']); type end这里我用了 eval 循环批量生成变量名,省去四段重复代码。不过 eval 在 MATLAB 新版里有性能损耗和代码检查警告,更推荐的写法是直接把这些 h 放在元胞数组里:
hset = {h1; h2; h3; h4}; for k = 1:4 [A, w, type, tao] = amplres(hset{k}); fprintf('h%d type=%d\n', k, type); end四个 type 依次输出 1、2、3、4,证明这组由 h0=[3,-1,2,-3] 扩展出来的系数,恰好完整覆盖了四种类型。这也是一种构造技巧:把一段长度为 4 的序列折叠成对称/反对称,再补奇偶长度,就得到四个不同滤波器。
4. 零极点分布:零点分组规律与 zplane 验证
4.1 roots 求解与倒序根的物理含义
线性相位 FIR 滤波器的系统函数 H(z) 是 z⁻¹ 的 N-1 次多项式,在 z 平面有 N-1 个零点,原点 z=0 是 N-1 阶重极点。研究零点分布的意义在于:零点的位置直接决定幅频响应在某些频点是否为零。
h = [3, -1, -5, 4, 6, 4, -5, -1, 3]; rz = roots(h) % 求多项式的根,即系统零点 for i = 1:8 r(i) = 1/rz(i); % 对每个零点取其倒数 end r' zplane(h, 1) % 绘制零极点图roots 函数按 h 系数构造多项式求根。倒数运算对应 z 变换中 z 与 z⁻¹ 的关系:FIR 系数对称时,H(z) 与 z⁻^(N-1)H(z⁻¹) 相等,因此若 z_i 是零点,1/z_i 也必然是零点。这段代码用循环逐一计算倒根,目的就是验证这一成对关系。注意 r(i) = 1/rz(i) 的计算顺序没有排序保证,roots 返回的根顺序不固定,所以你在命令窗口看到的 r' 顺序可能与 rz 不完全一一对应,属正常现象。
按实验报告的运行结果,8 个零点中:第 1、2 项是共轭复数对,第 7、8 项是对应的倒数共轭对,四者构成一组;第 3、6 项是实数零点,互为倒数,成一组;第 4、5 项在单位圆上,互为共轭且倒数等于自身,成一组。这就是线性相位 FIR 零点分布的三种情况。为了看得更直观,可以再用 abs(rz) 检查零点是否在单位圆上:
abs(rz)' % 值为1说明在单位圆上,值≠1说明在圆内或圆外4.2 零点分布三类规律的判别标准
线性相位 FIR 零点的分布可以归纳为三条规律:
- 若零点在 z=1 或 z=-1,它单独存在,不需要成对。这是因为 1 和 -1 的倒数都是自身,对称性自然满足。类型Ⅱ 在 z=-1 有零点、类型Ⅲ 在 z=±1 都有零点、类型Ⅳ 在 z=1 有零点,这些强制零点正是前面分析频率特性时看到的过零现象。
- 实数零点或单位圆上的复数零点,按 z_i 与 1/z_i 成对出现。实数的倒数是实数,单位圆上点的倒数等于共轭,所以对子内部关系简单。
- 不在单位圆上的复数零点,按 z_i、z_i*、1/z_i、1/z_i* 四个点同时出现。因为系数是实数,复数根必然共轭成对;又因为系数对称,每个根都配一个倒数根,共轭操作和倒数操作交替作用就形成了四点一组。
判断一个零点属于哪种情况,直接看它的虚部和模值:虚部为 0 是实数零点,模为 1 是单位圆零点,两者都不满足则是圆外共轭组。你可以修改 h 里的一个系数再看极点图如何变化,这比任何讲解都直观。
4.3 三种类型的零极点图差异(例 23-6)
h2 = [3, -1, -5, 4, 4, -5, -1, 3]; % 类型Ⅱ h3 = [3, -1, -5, 4, 0, -4, 5, 1, -3]; % 类型Ⅲ h4 = [3, -1, -5, 4, -4, 5, 1, -3]; % 类型Ⅳ for k = 2:4 eval(['rz = roots(h' num2str(k) ');']); figure; zplane(h{k}, 1); end类型Ⅱ 因为是偶数长度对称,z=-1 处必有零点(对应 ω=π 幅度为零);类型Ⅲ 在 z=±1 都有零点;类型Ⅳ 在 z=1 有零点。zplane 绘图时,零点用圆圈标出,极点用叉号标出,原点处的重极点会以多个叉号重叠显示。说实话,zplane 在原点处画多个极点时图形会叠在一起,看起来像只有一两个叉号,但不影响判断零点的分组情况。若想验证某个具体零点是否落在单位圆上,用 abs(root) 精确计算比看图更可靠。
5. 避坑指南:类型判断出错与绘图异常的五个典型案例
5.1 浮点误差导致类型判断翻车
现象:直接用了相等判断 if h(n) == h(N-n+1),结果一组明明对称的系数被判为"不是线性相位滤波器"。
原因:MATLAB 里小数的二进制表示会产生浮点误差,比如 0.1+0.2 不等于 0.3。系数经过运算后再比较,误差会累积到 1e-16 量级,== 判断就会失败。
解决:一律采用容差比较。amplres.m 里用 abs(h(n)-h(N-n+1)) < 1e-10 就是这个道理。自己写判断时把容差设为 1e-8 到 1e-12 都行,但别设太小——有些从文件中读取的系数误差会达到 1e-9,设成 1e-12 就误判了。我一般取 1e-10,和原程序保持一致。
5.2 类型Ⅲ中间系数不为零导致报错
现象:构造类型Ⅲ滤波器时用了 h=[3,-1,-5,4,1,-4,5,1,-3](中间元素是 1 而不是 0),运行 amplres 直接报错。
原因:N 为奇数时,反对称性要求中心元素满足 h(L+1) = -h(L+1),唯一可行解是 0。amplres.m 里专门写了 (h(L+1)*mod(N,2)==0) 来拦截这种情况。
解决:写类型Ⅲ 系数时,先确认长度是奇数,再把正中间那个元素设成 0。事实上,任意奇数长度反对称序列的中心元素都必须是 0,这是硬约束,不是可选条件。
5.3 xlabel 中 \pi 写成全角字符导致绘图异常
现象:复制网络上的代码到 MATLAB 里运行,xlabel('\pi') 显示出来是一串乱码,横轴标签不对。注意原文里这个"\"是全角反斜杠 U+FF3C,不是 ASCII 反斜杠。
原因:部分实验报告文档在排版时把反斜杠自动替换成了全角字符,导致 MATLAB 无法识别转义序列。
解决:检查代码里的 xlabel 和 ylabel 字符串,确保用的是半角反斜杠 \pi 而不是全角 \pi。辨别方法是看字符串中斜杠和后面字母 p 之间是否紧凑,全角字符通常占据一个中文字符宽度。这类问题在从 PDF 或网页直接复制代码时非常容易踩到。
5.4 新版 MATLAB 中 eval 与旧版脚本的兼容性
现象:实验环境是 MATLAB 7.0,脚本里大量使用了 eval 和直接命令行语句。放到 R2022b 及以上版本运行时,代码能跑但警告刷屏,一些绘图函数(如 zplane)的样式也变了。
原因:新版 MATLAB 对 eval 的优化使得它在循环中变得非常慢,且编辑器会用橙色波浪线提示潜在问题;zplane 的句柄体系在 R2014b 之后全面切换到了新的对象系统,旧代码画出来的图形虽然坐标轴对,但线条属性设置方式不同。
解决:把 eval(['...' num2str(k) '...']) 改成元胞数组循环,代码可读性和运行效率都更好。zplane 画完图后,用 findobj 或 gca 去设置线条样式时要基于新对象语法(如 set(findobj(gca,'Type','Line'),'LineWidth',1.5) 仍兼容),不要照抄旧教材里的 set(handles,...) 写法。运行环境建议直接用新版,没必要为了兼容 MATLAB 7.0 去装旧版,因为 amplres.m 的所有函数在新旧版本里行为一致,只有语法风格差异。
5.5 roots 返回的根顺序与倒序结果对不上
现象:运行例 23-5 的代码,发现 rz 的第一个根和 r' 的第一个元素不是倒数关系,以为代码写错了。
原因:roots 返回的多项式根顺序不固定,按降序排列的是系数,不是根。r 向量是按 rz 的原始顺序计算的倒数,所以对应关系没问题,只是显示时 rz(i) 和 r(i) 的位置对不上。
解决:如果想按位置一一对比,先对 rz 排序再求倒数,或者直接用 [sort_complex(rz), sort_complex(r')] 对比。也可以调用 zplane 图上的数据光标手动查看。这个"对不上"不是错误,是顺序问题,理解后就没事了。
6. 从验证到设计:用 amplres.m 反向检验自建 FIR 滤波器
实验做完,amplres.m 的价值其实不止于验证教材例题。实际工作中我用它来反向检查设计好的 FIR 滤波器是否符合线性相位预期、类型判断是否正确。做法很简单:先用设计函数生成滤波器系数,再用 amplres 判断类型。
% 用窗函数法设计一个低通滤波器,19阶,截止频率 0.4*pi h_design = fir1(18, 0.4, 'low', hamming(19)'); [A, w, type, tao] = amplres(h_design); fprintf('设计出的滤波器属于类型%d,群延迟为%g\n', type, tao);fir1 生成的系数天然满足对称条件,N=19 是奇数,所以 type 应该回显为 1。你也可以试着把阶数改成 20(偶数长度),此时 type 会变成 2,但要注意类型Ⅱ 存在 ω=π 处的结构零点,所以如果设计的滤波器要求高频段有增益,就必须改用类型Ⅳ(也就是让 fir1 自动采用反对称模式)。进阶一点,用 firpm 或 firls 设计等波纹滤波器时,设定的频点如果包含 0 或 π 且增益不为 0,MATLAB 会自动切换滤波器类型,此时 amplres 的输出就是你的验证依据。
零极点验证也一样用。设计完滤波器后用 zplane 画出零极点图,再把光标移到零点上,观察它是否按前面说的三类规律成组。曾经有一次我设计的带通滤波器零点分布特别乱,本来以为是优化算法问题,用 amplres 一查才发现系数在浮点处理时被截断了对称性,type 返回报错。从那以后我每次用 firpm 设计完滤波器,都会先强制走一遍 amplres 和 zplane 两个验证脚本,确认极点全在原点、零点成组规律正确,再拿去给下一步仿真用。这个习惯帮我省了不少排查时间。希望帮到你。
本文还有配套的精品资源,点击获取