ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

信号分析与处理实验全流程:从波形生成到滤波器设计的四个台阶

信号分析与处理实验全流程:从波形生成到滤波器设计的四个台阶

简介:这份资源是南京邮电大学「信号分析与处理实验」课程的完整实验报告,面向正在修读数字信号处理、信号与系统相关课程的高校学生,以及需要借助 MATLAB 完成实验与课程设计的自学者。报告覆盖信号的产生和运算、连续时间信号的频域分析、信号的时域采样和重建、离散傅里叶变换与应用等核心实验,并延伸至连续时间系统的频域与复频域分析、数字滤波器设计及信号处理综合应用。压缩包内共 1 个 docx 文档,约 1.02MB,按实验逐项整理实验目的、内容、过程与结果分析,配有可直接运行的 MATLAB 程序示例和波形图,便于对照理解单位阶跃、复指数、正弦等序列的时域与频域特性,掌握卷积、采样定理、DFT 及滤波器设计等关键操作。目前已有 952 人学习,适合作为实验预习、报告撰写与期末复习的参考,帮助读者在动手编程中夯实 DSP 基础。

1. 信号分析与处理实验:从“波形画出来”到“结果说得清”的四个台阶

很多同学做完信号分析与处理实验,波形图截了一堆,数据也跑了,但写报告时还是不知道从哪下笔——不是不会 MATLAB,而是没搞明白每个实验到底在训练什么能力。实验1到实验4这条线,本质上是从“认识信号”到“分析系统”的递进:先看清时域波形,再拆开频域成分,然后让信号通过系统,最后用滤波器把想要的成分留下来。每一步都在回答一个工程问题:这个信号长什么样?它由哪些频率组成?经过系统后变成了什么?怎么把干扰去掉?如果你正在做这四个实验,或者带低年级学生做信号处理入门,这篇笔记会把每个实验的核心操作、参数设置和报告写法拆开讲,让你既能跑出结果,也能把结果讲清楚。

2. 实验1:信号生成与时域分析——先把“信号”这个概念变成看得见的东西

2.1 为什么第一个实验总是从正弦和方波开始

信号分析与处理的第一课,几乎都是从连续正弦信号和方波信号的生成与采样开始的。原因很直接:正弦信号是频域分析的“原子”,任何复杂信号都可以分解成正弦叠加;方波则是最容易观察“ Gibbs 现象”和“谐波叠加”的入门波形。实验1的目标不是让你背公式,而是让你亲手看到:一个连续信号被采样后,离散序列到底长什么样,采样率变了波形会怎么变。

我一般会让学生先做三组对比:同一频率的正弦,分别用 10 倍、5 倍、2.5 倍信号频率的采样率去采,然后画在同一张图上。结果很直观——采样率越低,离散点越稀疏,恢复出来的波形越“歪”。这就是采样定理的感性认识,比直接背“ fs ≥ 2fmax ”要深刻得多。

2.2 用 MATLAB 生成正弦与方波的最小代码框架

下面这段代码可以直接跑,覆盖了实验1最核心的几步:定义时间轴、生成信号、采样、画图。

% 实验1:正弦信号生成与采样对比 f0 = 10; % 信号频率 10 Hz T = 0.5; % 观察时长 0.5 s fs_list = [200, 100, 50]; % 三种采样率:20倍、10倍、5倍信号频率 t_fine = 0:0.0001:T; % 高密度时间轴,用于画“连续”参考波形 x_fine = sin(2*pi*f0*t_fine); figure; for k = 1:length(fs_list) fs = fs_list(k); n = 0:1/fs:T; % 离散时间轴 x_n = sin(2*pi*f0*n); subplot(3,1,k); plot(t_fine, x_fine, 'b', 'LineWidth', 1.2); hold on; stem(n, x_n, 'r', 'filled', 'MarkerSize', 3); title(['fs = ' num2str(fs) ' Hz']); xlabel('时间 (s)'); ylabel('幅值'); legend('连续参考', '采样点'); grid on; end

这段代码的逻辑很清晰:t_fine用来画一条光滑的参考曲线,n是实际采样点的时间轴,stem画出离散样本。参数方面,f0决定信号频率,fs_list里的三个值分别对应过采样、临界采样和欠采样边缘。跑完之后你会看到,当fs = 50 Hz时,每个周期只有 5 个采样点,波形已经明显“折线化”了。

注意:采样率低于 2 倍信号频率时会出现混叠,但实验1通常只做到“临界”附近,混叠的定量分析留到实验2的频域部分再展开。

2.3 方波生成与谐波叠加的观察方法

方波可以用square函数直接生成,但更有教学价值的是用傅里叶级数前 N 项叠加来逼近方波。这样做的好处是:你能亲眼看到随着谐波项数增加,方波边缘的过冲(Gibbs 现象)如何变化。

% 方波的谐波叠加逼近 t = 0:0.0001:0.04; % 两个周期,基频 50 Hz f_base = 50; N_list = [1, 3, 7, 15]; % 叠加的谐波项数 figure; for k = 1:length(N_list) N = N_list(k); x_approx = zeros(size(t)); for n = 1:2:N % 只取奇次谐波 x_approx = x_approx + (4/(pi*n)) * sin(2*pi*n*f_base*t); end subplot(2,2,k); plot(t, x_approx, 'LineWidth', 1.2); title(['叠加 ' num2str(N) ' 次谐波']); xlabel('时间 (s)'); ylabel('幅值'); ylim([-1.5 1.5]); grid on; end

这里n只取奇数,因为方波的偶次谐波系数为零。4/(pi*n)是方波傅里叶级数的标准系数。跑完四张子图,你会看到 N=1 时只是个正弦,N=15 时已经很像方波,但边缘仍有明显的过冲——这就是 Gibbs 现象,也是实验报告里可以重点讨论的现象。

2.4 实验报告里时域部分该写什么

很多同学的报告只贴图,不写分析。时域部分至少要有三块内容:第一,参数说明,包括信号频率、采样率、采样点数;第二,现象描述,比如“当 fs 从 200 Hz 降到 50 Hz 时,离散点从每周期 20 个减少到 5 个,波形重建误差明显增大”;第三,结论,比如“验证了采样定理中 fs ≥ 2fmax 的必要性,但实际工程中通常取 5 到 10 倍以保证重建质量”。这三块写清楚,实验1的报告就不会空洞。

3. 实验2:频域分析与频谱计算——把信号从“时间轴”搬到“频率轴”

3.1 为什么 FFT 之后要取模和移位

实验2的核心是频域分析,而频域分析绕不开 FFT。但很多同学第一次用fft函数时会懵:为什么结果是一堆复数?为什么横轴要重新定义?为什么幅值要除以 N?这里把最常见的三个坑一次说清。

FFT 输出的是复数序列,每个点包含幅值和相位信息。要画频谱,通常只关心幅值,所以取abs。但直接取模之后,你会发现横轴是 0 到 N-1,对应的频率是 0 到 fs,而实际上频谱是关于 fs/2 对称的。所以需要用fftshift把零频移到中心,横轴改成 -fs/2 到 fs/2。最后,幅值要除以 N 才能得到真实的物理幅值——如果是单边谱,还要把正频率部分乘以 2。

3.2 完整频谱分析代码与参数逐行说明

% 实验2:正弦信号的频谱分析 fs = 1000; % 采样率 1000 Hz T = 1; % 信号时长 1 s N = fs * T; % 采样点数 1000 t = (0:N-1)/fs; % 时间轴 f0 = 50; % 信号频率 50 Hz x = sin(2*pi*f0*t); X = fft(x); % 快速傅里叶变换 X_shifted = fftshift(X); % 零频移到中心 f_axis = (-N/2:N/2-1) * (fs/N); % 频率轴 mag = abs(X_shifted) / N; % 幅值归一化 mag = [mag(N/2+2:end)*2, mag(N/2+1), mag(2:N/2)*2]; % 单边谱修正 f_single = (0:N/2) * (fs/N); figure; subplot(2,1,1); plot(f_axis, abs(X_shifted)/N, 'LineWidth', 1.2); title('双边频谱'); xlabel('频率 (Hz)'); ylabel('幅值'); grid on; subplot(2,1,2); plot(f_single, mag, 'LineWidth', 1.2); title('单边频谱'); xlabel('频率 (Hz)'); ylabel('幅值'); grid on;

关键参数说明:fs/N是频率分辨率,这里为 1 Hz,意味着频谱上相邻两个点间隔 1 Hz。N越大,分辨率越高,但计算量也越大。单边谱修正那行代码稍微复杂:先把正频率部分乘以 2(因为能量被对称分到了负频率),零频和奈奎斯特频率不乘。跑完之后,单边谱会在 50 Hz 处出现一个峰值,幅值接近 1。

3.3 频谱泄漏与窗函数的实际影响

如果信号频率不是频率分辨率的整数倍,比如 f0 = 50.5 Hz,你会看到频谱峰值“漏”到了相邻的频点上,这就是频谱泄漏。解决办法是加窗,比如汉宁窗或汉明窗。加窗的代价是主瓣变宽,频率分辨率下降,但旁瓣泄漏会明显减小。

% 频谱泄漏对比:矩形窗 vs 汉宁窗 f0 = 50.5; % 非整数倍频率 x = sin(2*pi*f0*t); w_rect = ones(N,1); % 矩形窗 w_hann = hann(N); % 汉宁窗 X_rect = fftshift(fft(x .* w_rect')); X_hann = fftshift(fft(x .* w_hann')); figure; plot(f_axis, abs(X_rect)/N, 'b', 'LineWidth', 1.2); hold on; plot(f_axis, abs(X_hann)/N, 'r', 'LineWidth', 1.2); legend('矩形窗', '汉宁窗'); title('频谱泄漏对比'); xlabel('频率 (Hz)'); ylabel('幅值'); grid on;

跑完这张图,你会看到矩形窗的峰值虽然尖锐,但旁边有明显的“裙边”;汉宁窗的峰值变宽了,但裙边低了很多。实验报告里可以写:“加窗以牺牲频率分辨率为代价,换取了更低的旁瓣泄漏,适用于非整周期采样场景。”

3.4 实验报告里频域部分的分析模板

频域部分的报告建议按“参数—现象—结论”三段写。参数包括采样率、采样点数、频率分辨率;现象包括峰值位置、幅值大小、泄漏情况;结论要回答“FFT 能否准确识别信号频率成分”以及“窗函数对频谱的影响是什么”。如果做了泄漏对比,还可以加一句“在非整周期采样时,加汉宁窗后旁瓣幅值从 -13 dB 降到 -31 dB 左右”——具体数值以你实际跑出来的为准。

4. 实验3:线性系统与卷积——信号通过系统后到底变了什么

4.1 卷积的物理意义:不是数学运算,是“记忆效应”

实验3通常讲线性时不变系统,核心运算是卷积。很多同学把卷积当成一个公式来背,但其实它的物理意义很直观:系统对每一个输入样本的“响应”会持续一段时间,输出就是所有这些响应叠加的结果。如果系统是因果的,响应只向后延伸;如果是 FIR 系统,响应长度有限,卷积就是有限项的加权和。

我一般会让学生先做一个最简单的实验:输入一个单位脉冲,看系统的冲激响应;再输入一个方波,看输出怎么被“拖尾”。这样就能直观理解卷积的作用。

4.2 用 conv 和 filter 实现卷积的两种方式

% 实验3:卷积与系统响应 n = 0:30; h = [1, 0.8, 0.6, 0.4, 0.2]; % FIR 系统冲激响应 x = [1, zeros(1, 25)]; % 单位脉冲输入 y_conv = conv(x, h); % 方式一:conv 函数 y_filter = filter(h, 1, x); % 方式二:filter 函数 figure; subplot(3,1,1); stem(n, x(1:31), 'filled'); title('输入:单位脉冲'); grid on; subplot(3,1,2); stem(0:length(h)-1, h, 'filled'); title('系统冲激响应'); grid on; subplot(3,1,3); stem(0:length(y_conv)-1, y_conv, 'filled'); title('输出:卷积结果'); grid on;

conv和filter的区别在于:conv输出长度是length(x) + length(h) - 1,而filter输出长度与输入相同。对于 FIR 系统,两者在稳态部分一致,但filter更适合实时处理场景。参数方面,h的长度决定了系统的“记忆长度”,这里为 5 个采样点。

4.3 系统频率响应的验证方法

除了时域卷积,实验3通常还会要求验证频域特性。方法很简单:给系统输入一个正弦信号,看输出幅值和相位的变化,然后和freqz算出来的频率响应对比。

% 频率响应验证 fs = 1000; f_test = [50, 100, 200]; % 测试频率 t = 0:1/fs:0.1; figure; for k = 1:length(f_test) x = sin(2*pi*f_test(k)*t); y = filter(h, 1, x); % 计算幅值比 amp_in = max(abs(x)); amp_out = max(abs(y(end-100:end))); % 取稳态部分 amp_ratio = amp_out / amp_in; subplot(3,1,k); plot(t, x, 'b', t, y, 'r', 'LineWidth', 1.2); title(['f = ' num2str(f_test(k)) ' Hz, 幅值比 = ' num2str(amp_ratio, '%.3f')]); legend('输入', '输出'); grid on; end

跑完之后,你可以把实测幅值比和freqz(h,1,[],fs)算出来的理论值对比。如果差距在 5% 以内,说明系统是线性的,卷积模型成立。实验报告里可以写:“在 50 Hz 处幅值比约为 0.98,在 200 Hz 处降至 0.72,说明该系统具有低通特性。”

4.4 实验报告里系统分析部分的写法

系统部分的报告要突出“输入—系统—输出”这条链。先写系统的冲激响应和差分方程,再写输入信号的类型和参数,然后写输出信号的时域特征和频域特征,最后写结论:系统对哪些频率成分放大了,对哪些抑制了。如果做了频率响应验证,把实测值和理论值列成表格,对比一目了然。

5. 实验4:数字滤波器设计与应用——把想要的留下,把不想要的去掉

5.1 FIR 与 IIR 的选型理由

实验4通常是滤波器设计。第一个决策是选 FIR 还是 IIR。FIR 的优点是线性相位、总是稳定,缺点是阶数高、计算量大;IIR 的优点是阶数低、计算效率高,缺点是非线性相位、稳定性需要检查。对于实验场景,如果要求波形不失真,选 FIR;如果只关心幅频特性,选 IIR 更省事。

我一般会让学生先用fdatool或designfilt设计一个低通滤波器,然后分别用 FIR 和 IIR 实现,对比滤波效果和相位变化。

5.2 用 designfilt 设计低通滤波器的完整流程

% 实验4:FIR 低通滤波器设计 fs = 1000; % 采样率 fc = 100; % 截止频率 order = 64; % 滤波器阶数 % 设计 FIR 低通滤波器 fir_filt = designfilt('lowpassfir', ... 'PassbandFrequency', fc, ... 'StopbandFrequency', fc + 50, ... 'PassbandRipple', 1, ... 'StopbandAttenuation', 60, ... 'SampleRate', fs); % 查看频率响应 freqz(fir_filt); % 生成含噪信号并滤波 t = 0:1/fs:0.5; x_clean = sin(2*pi*30*t); % 30 Hz 有用信号 x_noise = 0.5 * sin(2*pi*300*t); % 300 Hz 干扰 x = x_clean + x_noise; y = filter(fir_filt, x); figure; subplot(3,1,1); plot(t, x); title('含噪输入'); grid on; subplot(3,1,2); plot(t, x_clean); title('干净参考'); grid on; subplot(3,1,3); plot(t, y); title('滤波输出'); grid on;

designfilt的参数含义:PassbandFrequency是通带边缘,StopbandFrequency是阻带边缘,两者之间的过渡带宽度决定了滤波器阶数。PassbandRipple是通带波动,单位 dB;StopbandAttenuation是阻带最小衰减。阶数order越高,过渡带越窄,但计算量越大。跑完freqz你会看到幅频响应和相频响应,FIR 的相位是线性的,这是它的核心优势。

5.3 滤波前后频谱对比与性能评估

滤波效果不能只看时域波形,还要看频谱。把滤波前后的信号分别做 FFT,对比 300 Hz 处的幅值变化。

% 滤波前后频谱对比 N = length(x); f_axis = (0:N/2) * (fs/N); X_before = abs(fft(x))/N; X_after = abs(fft(y))/N; figure; plot(f_axis, X_before(1:N/2+1), 'b', 'LineWidth', 1.2); hold on; plot(f_axis, X_after(1:N/2+1), 'r', 'LineWidth', 1.2); legend('滤波前', '滤波后'); title('频谱对比'); xlabel('频率 (Hz)'); ylabel('幅值'); grid on;

跑完之后,你会看到 300 Hz 处的峰值被明显压制,而 30 Hz 处的峰值基本不变。实验报告里可以写:“滤波后 300 Hz 分量幅值从 0.5 降至 0.02 以下,衰减超过 25 dB,30 Hz 分量幅值变化小于 5%,满足设计指标。”

5.4 实验报告里滤波器部分的评估指标

滤波器部分的报告要围绕四个指标写:通带截止频率、阻带截止频率、通带波动、阻带衰减。如果做了 FIR 和 IIR 对比,还要加一个相位线性度的讨论。建议用表格列出设计指标和实测指标,差值在 10% 以内就算合格。最后写一句“该滤波器可用于去除高频干扰,但会引入约 N/2 个采样点的群延迟”,这是 FIR 线性相位的代价。

6. 避坑与排查:四个实验里最容易翻车的五个地方

6.1 采样率设错导致波形完全不对

现象:画出来的正弦波形像随机噪声,或者频率明显不对。原因:采样率低于 2 倍信号频率,发生混叠;或者时间轴步长和采样率不匹配。解决:先确认fs和信号频率f0的关系,保证fs >= 10*f0用于观察波形;检查时间轴是0:1/fs:T而不是0:0.001:T这种固定步长。

6.2 FFT 幅值不归一化导致数值对不上

现象:频谱峰值是 500 而不是 1。原因:没有除以 N,或者没有做单边谱修正。解决:双边谱幅值除以 N,单边谱正频率部分乘以 2。如果信号是实数,负频率部分对称,能量要算到正频率上。

6.3 滤波器阶数过高导致数值不稳定

现象:IIR 滤波器输出发散或剧烈振荡。原因:阶数过高,极点接近单位圆,量化误差被放大。解决:降低阶数,或者改用二阶节级联结构sos。MATLAB 的designfilt默认输出sos矩阵,用filter(sos, x)比用filter(b, a, x)更稳定。

6.4 卷积结果长度对不上导致画图报错

现象:plot报错“向量长度不一致”。原因:conv输出长度是N + M - 1,而时间轴还是按N画的。解决:卷积后重新生成时间轴t_conv = (0:length(y)-1)/fs,或者用filter代替conv保持长度一致。

6.5 窗函数加错位置导致频谱更差

现象:加窗后频谱峰值反而变宽、幅值下降。原因:窗函数没有和信号逐点相乘,或者窗长和信号长度不匹配。解决:确保w = hann(N)生成列向量,然后x .* w'逐点相乘;如果信号长度不是整数周期,加窗是必要的,但要接受主瓣变宽的代价。

7. 从实验报告到工程习惯:三个让结果更可信的进阶技巧

7.1 用脚本参数化,别硬编码

我见过太多同学的报告代码里全是fs = 1000; f0 = 50;这样的硬编码,换个题目就要改十几处。更好的习惯是把所有参数放在脚本开头,用结构体或变量集中管理。比如:

% 参数集中管理 params.fs = 1000; params.f0 = 50; params.T = 1; params.N = params.fs * params.T;

这样改一个地方,全脚本生效。实验报告里的代码也更整洁,老师一看就知道你是有工程思维的。

7.2 每次画图都加标题、轴标签和网格

这不是为了好看,是为了可复现。一张没有轴标签的图,过两周你自己都看不懂横轴是时间还是采样点。我一般要求每张图必须有title、xlabel、ylabel、grid on,如果是多曲线还要legend。实验报告里的图是要给别人看的,信息完整比“好看”重要。

7.3 用理论值验证实测值,别只贴图

实验报告最怕的就是“只有现象,没有验证”。比如你设计了一个截止频率 100 Hz 的滤波器,就要用freqz算出理论幅频响应,再和实测的滤波前后幅值比对比。如果实测值偏离理论值超过 10%,就要找原因:是阶数不够?是过渡带太窄?还是信号频率落在了过渡带里?这种“理论—实测—偏差分析”的闭环,才是信号处理实验真正要训练的能力。

我自己的习惯是:每做完一个实验,先不急着写报告,而是把关键参数和理论值列一张表,跑一遍代码,把实测值填进去,差值超过阈值的就回去查代码。这个习惯帮我省了很多“后悔药”——很多错误在写报告之前就发现了,不用等到交上去被打回来。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进