news 2026/9/15 6:33:52

用Python脚本点亮信号与系统课堂:卷积、傅里叶与滤波演示

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python脚本点亮信号与系统课堂:卷积、傅里叶与滤波演示

简介:面向中山大学信号与系统课程教学的一套Python脚本设计源码集合,主要服务于电子信息、电气工程等专业本科生及任课教师。资源紧扣卷积、傅里叶变换、离散余弦变换等核心知识点,以代码与可视化结合的方式降低理论理解门槛。包内共104个文件,约16.91MB,既有68幅PNG信号波形与频谱图、18个Python算法脚本,还包含GIF动态演示、Jupyter Notebook交互文档及XML/Markdown配置说明,能覆盖课堂教学、课后实验与自主拓展多种场景。目前已有308人学习下载。通过这套资源,学习者可以对照Python实现逐段理解卷积、滤波、频谱分析等操作的代码逻辑,借助可视化图像与动态动画观察信号变化过程;教师也可直接引用Notebook开展随堂演示,或利用脚本快速生成教学用图,有效提升信号与系统课程的实践性与互动性。

1. 面向信号与系统课程教学的 Python 脚本源码,先从课堂断层说起

老师在讲台上推导傅里叶变换,公式学生抄下来了,可课件一翻页,频谱长什么样全靠想象。信号与系统课程的课堂断层就落在这里:时域波形直观可见,频域看不见摸不着;卷积的翻转平移在教材上是三张交替的箭头图,到了编程环节却不知道从哪一行代码开始写。这套 Python 脚本源码要解决的,是把课程里十几个抽象概念变成可运行、可调参、可输出图片的演示工具。它面向中山大学信号与系统课程教学,也适用于其他高校同类课程,服务正在修课的学生、备课的助教,以及想快速搭建信号处理原型的工程师。脚本不追求算法性能上的极致,只求把概念讲清楚、把参数调明白。

2. 从课程知识点到 Python 脚本源码的模块划分与参数约定

2.1 教学脚本源码按信号生成、运算、可视化三层划分

写这类教学源码,第一原则是别把逻辑写成一坨。很多初学者习惯一个文件从头写到底,信号生成、FFT、画图全纠缠在一起,改一个采样率要滚动半个编辑器窗口。我一般会按课程内容把脚本拆成三层,层与层之间单向依赖。

信号生成层只做一件事:给定时间轴,返回信号取值数组。单位阶跃、矩形脉冲、指数衰减、正弦调幅,全部写成统一签名的函数,输入参数风格一致,返回值都是numpy.ndarray。这一层不关心信号拿去卷积还是画图,职责单一。

运算层负责卷积、傅里叶变换、离散序列变换这些核心计算。这一层不import matplotlib,不读任何配置文件,输入数组、输出数组。保持纯粹的好处是方便单独做数值验证,也能在命令行环境里快速跑算法而不用初始化图形界面。

可视化层统一封装 matplotlib 绘图逻辑。时域波形子图、幅度谱子图、卷积过程三联动图,都收敛成几个绘图函数。助教调整配色、字体、图注时不需要碰前两层代码;学生修改参数时也不会误删核心算法。三层之间的调用方向是固定的:可视化层调用运算层和生成层,运算层只依赖生成层的数据结构,生成层不依赖任何上层模块。

2.2 连续信号与离散序列在脚本里的统一存储方式

信号与系统课程里同时存在连续时间信号和离散时间序列两类对象,但落到 Python 脚本里,它们都只能是有限长度的数组。常见做法是全部用numpy.ndarray存储,并始终配套维护一个时间轴或序号轴,避免只用裸数据。

连续信号用t = np.arange(N) / fs生成时间轴,fs是采样率,单位 Hz,脚本里默认取 1000。这个默认值不是随手写的:课程演示常用的 1 Hz 到 100 Hz 信号,用 1000 Hz 采样画出来波形足够平滑,而且时间轴每格换算成毫秒非常直观,学生读图时容易建立量级感。

离散序列则直接用n = np.arange(N)作为横轴,n是整数序号。两套轴不能在脚本里混用。我在源码命名上做了强制区分,连续信号函数统一带ct_前缀,离散序列带dt_前缀,靠命名约束提醒调用者当前对象属于哪一类。最常见的错误是拿连续信号的时间轴去画离散序列的 stem 图,横轴小数点多得离谱,封装层通过参数类型校验能提前挡住这一类低级轴错位。

2.3 公共参数约定:采样率 fs、时长 T、点数 N 的关系

教学脚本能不能在课堂上直接演示,取决于参数约定是否一致。源码头部固定一组公共参数,并写成表格注释放在配置区,助教上课前只改配置区的数字,不深入函数体。

参数含义默认值脚本内约定
fs采样率 (Hz)1000所有连续信号共用,不可单独覆盖
T演示时长 (s)1.0配合 fs 决定总点数
N采样点数int(fs * T)不手动设置,避免与 fs/T 矛盾
t0跳变时刻 (s)0.0阶跃、脉冲边沿位置

N不设成手动参数是这个约定的关键。一旦学生手动设N = 500又另设fs = 1000T = 1.0,时间轴长度和采样率就对不上,画出来的波形横轴与实际物理时间完全脱节。源码在make_time_axis内部强制用int(fs * T)计算N,并在函数入口检查传入数组长度与时间轴长度是否一致,不一致直接抛异常,从根上排除这类自相矛盾的参数组合。

3. 波形生成与卷积运算的 Python 脚本落地实现

3.1 生成阶跃、矩形脉冲和指数衰减信号的脚本模板

信号生成层的代码模板如下,这套函数在每个演示脚本里都会直接 import,属于最基础的一块地基。

import numpy as np def make_time_axis(fs=1000, T=1.0): """生成时间轴:0 到 T 秒,共 fs*T 个采样点""" N = int(fs * T) return np.arange(N) / fs def unit_step(t, t0=0.0): """单位阶跃信号:t >= t0 时值为 1,否则为 0""" return (t >= t0).astype(float) def rect_pulse(t, start=-0.1, stop=0.1): """矩形脉冲:区间 [start, stop) 内为 1,其余为 0""" return ((t >= start) & (t < stop)).astype(float) def exp_decay(t, alpha=5.0): """单边指数衰减信号:exp(-alpha*t) * u(t)""" return np.exp(-alpha * t) * (t >= 0)

逻辑不复杂,但三个细节值得展开说明。astype(float)把布尔比较结果转成 0/1 浮点数组,后续和np.exp这类浮点运算混合时不会出现类型断裂。矩形脉冲右边界用左闭右开,即start包含、stop不包含,这样两个脉冲首尾相接时不会在接缝处重复计数,卷积边界也更干净。指数衰减信号里(t >= 0)是硬截断,数学上等价于乘单位阶跃,写成这样比np.where更贴近教材公式,学生对照起来不费劲。

参数设计也经过权衡。t0startstop全部带默认值,课堂上演示时只需要调用exp_decay(t, alpha=8.0)就能改变衰减速度,不用把时间轴重复传一遍。每个函数的 docstring 第一句就是一句话功能说明,学生用help()查询时能秒懂用途,这对课程脚本的传播很重要。

3.2 卷积和的三种实现:教材定义、numpy 卷积与 FFT 方法

卷积和的课程定义是y[n] = Σ x[k] h[n-k],教材上用箭头图解释翻转平移。落到代码里有三种不同实现,我全部保留在源码里,分别对应不同教学阶段。

第一种完全按定义实现,三层循环,可读性最高:

def conv_direct(x, h): """按卷积和定义直接实现,三重循环,仅供理解原理""" nx, nh = len(x), len(h) y = np.zeros(nx + nh - 1) for n in range(nx + nh - 1): for k in range(nx): if 0 <= n - k < nh: y[n] += x[k] * h[n - k] return y

外层循环遍历输出序列的每个索引n,内层遍历x的索引k,条件判断n-k是否落在h的有效区间内。这段代码的时间复杂度是 O(N²),但和教材公式逐项对应,讲完这段再切换到 numpy 版本,学生才真正理解后者内部在算什么。

第二种用np.convolve(x, h)一行出结果,内部是编译好的 C 实现。第三种用 FFT 在频域相乘再逆变换,时间 O(N log N),但必须补零到N+M-1以上,否则循环卷积会把尾部混叠到头部。三种方式的结果差异和适用场景固定为一张表放在源码注释里:

实现方式复杂度结果长度适用场景
conv_direct 循环实现O(N²)N+M-1课堂讲原理
np.convolveO(NM)N+M-1一般数值计算
FFT 频域相乘O(N log N)需补零到 2 的幂大数据量

课堂上我会把 5000 点信号与 500 点冲激响应分别用三种方式跑一遍,直接循环实现有明显卡顿,FFT 几乎瞬间出结果。这时候再引出"时域卷积等于频域相乘",学生就有了计算复杂度的直观体感,而不仅是背一个结论。

3.3 卷积结果绘图时横轴错位与边界效应的处理

画卷积图是脚本里最容易翻车的地方。np.convolve返回数组长度是N+M-1,但默认横轴从 0 开始,如果输入信号的时间起点不是 0,画出来的卷积在时间轴上整体错位,图形形状是正确的,位置却是错的。

我封装了专用绘图函数解决这个问题:

def plot_conv(x, t_x, h, t_h, fs): """绘制 x 与 h 的卷积结果,横轴按物理时间对齐 t_x, t_h: 两个信号各自的时间轴 """ y = np.convolve(x, h) t_start = t_x[0] + t_h[0] t_step = 1.0 / fs t_y = t_start + np.arange(len(y)) * t_step plt.plot(t_x, x, label='x(t)') plt.plot(t_h, h, label='h(t)') plt.plot(t_y, y, label='y(t) = x(t) * h(t)') plt.legend()

关键在t_start = t_x[0] + t_h[0]这一行。卷积结果的起始时刻是两信号起始时刻之和,步长取公共采样间隔。这一行必须配注释写清楚,否则学生改了信号起点后,卷积波形和输入波形怎么都对不齐。

边界效应的处理同样重要。np.convolve默认mode='full'输出完整卷积段,首尾各有一段信号未完全重叠的过渡区;mode='same'则裁剪到与x等长,方便叠加对比。教学脚本默认展示full,因为它完整呈现了信号进入、重叠、退出的全过程,这正是课程要讲的东西。源码里通过mode参数开放切换,课堂上可以现场演示两种模式的区别。

4. 傅里叶变换与滤波仿真的脚本参数调优

4.1 用 numpy.fft 做频谱分析时频率轴与幅度归一化

给信号做完 FFT,第一步是构造频率轴。np.fft.fftfreq(N, d=1/fs)返回对称的双边频率数组,单位 Hz,d是采样间隔。教学脚本里我把频谱绘制封装成下面的函数,参数说明直接写在 docstring 里:

def plot_single_spectrum(t, x, fs, fmax=None): """绘制单边幅度谱 t: 时间轴; x: 信号数组; fs: 采样率 fmax: 只显示 0~fmax Hz 范围 """ N = len(x) X = np.fft.fft(x) freqs = np.fft.fftfreq(N, d=1/fs) half = N // 2 mag = 2.0 / N * np.abs(X[:half]) plt.plot(freqs[:half], mag) plt.xlabel('Frequency (Hz)') plt.ylabel('Magnitude')

调整参数时有三个高频出错点。第一是幅度系数,np.fft.fft的结果是原信号幅值的 N 倍,直接画出来峰值高得离谱,必须除以 N。第二是单边谱的 2 倍系数,负频率的能量合并到正频率一侧,除直流分量外都要乘 2。第三是halfN // 2,当 N 是奇数时多出的那个点归入负频侧,单边谱只保留前一半。

用这个脚本画 5 Hz 正弦波,峰值应精确落在 5 Hz 且幅值接近 1。如果峰值明显偏移,说明频率分辨率fs/N不够小。想区分频率差 1 Hz 的两个分量,点数 N 必须大于 fs。fmax参数则把频率轴裁剪到指定范围,避免高频段的毛刺把低频主峰的幅度视觉上压小。

4.2 理想低通与巴特沃斯滤波脚本的截止频率归一化

滤波仿真脚本里最常出错的参数是scipy.signal.butter的截止频率。butterWn参数要求归一化到奈奎斯特频率fs/2,漏掉这一步,截止频率的实际位置会比预期偏大fs/2倍,低通直接变成几乎不起作用。对比代码写成下面这样:

from scipy import signal def lowpass_butter(x, fs, cutoff_hz, order=1): """巴特沃斯低通滤波,cutoff_hz 为以 Hz 为单位的截止频率""" wn = cutoff_hz / (fs / 2) # 归一化到奈奎斯特频率 b, a = signal.butter(order, wn, btype='low') return signal.lfilter(b, a, x) def lowpass_ideal(x, fs, cutoff_hz): """理想低通:频域直接截断再逆变换,用于演示吉布斯现象""" X = np.fft.fft(x) freqs = np.fft.fftfreq(len(x), d=1/fs) X[np.abs(freqs) > cutoff_hz] = 0 return np.fft.ifft(X).real

巴特沃斯版本把cutoff_hz / (fs / 2)这一步单独拎出来做变量,理由是想让学生一眼看到归一化过程。理想低通直接在频域把高于截止频率的分量置零,逆变换后波形在跳变处会出现振铃,幅度约为原信号幅度的 9%,这就是吉布斯现象。

课堂上把两种滤波结果画在同一张图里。理想低通边缘锐利但有振铃,巴特沃斯平滑无振铃但过渡带倾斜,一阶滤波器在截止频率处已经衰减到 -3 dB 并伴随约 45 度相位滞后。把阶数从 1 调到 4,过渡带变陡,相位失真却更明显。这个"频率选择性与相位失真"的互相制约关系,靠讲理论很难建立直觉,脚本里调一次阶数就清楚了。

4.3 抽样定理演示脚本的混叠假峰与采样率选取

抽样定理的脚本演示是信号与系统课程里最能出效果的部分。演示脚本默认生成 20 Hz 正弦信号,采样率从 10 Hz 到 80 Hz 分档切换,每档输出一张频谱图,观察假峰如何出现、如何移动。

混叠的核心表现是欠采样时频谱上出现假峰,位置在fs - f处。20 Hz 信号用 15 Hz 采样时,假峰出现在 15 - 20 = -5 Hz,单边谱显示为 5 Hz 的正频率峰值。脚本里把假峰保留并在图上用箭头标注混叠来源,学生能直接看到假峰与真实频率之间的距离关系。

这里有一个值得专门讲的反直觉细节:当采样率刚好是信号频率的整数倍时,采样点可能恰好落在正弦波的零交叉处,频谱主峰反而消失。因此演示抽样定理时,采样率要刻意避开信号频率的整数倍。20 Hz 信号用 45 Hz 采样,混叠假峰出现在 45 - 20 = 25 Hz,和教材上的镜像频谱图完全对应,讲课解释起来非常顺畅。这个场景通过命令行参数直接启动,现场改采样率就能观察假峰位置的变化规律。

5. 用解析解断言与命令行参数提升教学脚本源码的可用性

5.1 在脚本里嵌入解析解断言,画图之前先验算

教学脚本最大的风险是数值算错了,但图看起来一切正常。我习惯在每个核心算法后、绘图调用前,用课程里已有的解析解做断言验证。

卷积示例中,两个幅度为 1、宽度分别为 a 和 b 的矩形脉冲卷积得到三角波,峰值等于 a*b。脚本里这样写:

# 两个矩形脉冲,宽度 0.2s 和 0.1s,幅度均为 1 y = np.convolve(rect_pulse(t, 0, 0.2), rect_pulse(t, 0, 0.1)) assert abs(np.max(y) - 0.2) < 1e-9, "卷积峰值与解析解不符"

断言写在画图之前,一旦算法被改动导致峰值偏差,脚本会在生成图片前直接报错中断,错误消息指向明确。傅里叶变换脚本里同样可以验证 5 Hz 正弦在 5 Hz 处的幅值应等于 1,容差取1e-9。这套断言相当于给教学脚本加了一层自动化单元测试,学生改完参数跑一遍不报错,至少说明数值计算没有偏离基本原理。

5.2 通过命令行参数切换演示场景,避免上课改代码

助教上课时最怕临时改源码。教学脚本库统一加了argparse命令行入口,演示场景通过参数切换:

python demos/conv_demo.py --demo conv --fs 1000 python demos/sampling_demo.py --demo sampling --fs 45 python demos/fourier_demo.py --demo spectrum --fs 200 --fmax 50

--fs覆盖默认采样率,--fmax限制频谱横轴显示范围。所有演示脚本默认输出 png 图片而不是弹出 matplotlib 窗口,课堂现场弹窗容易卡死,预渲染成图片插入课件更流畅,图片统一dpi=120,投影仪上坐标刻度依然清晰。这套设计的核心是把脚本的使用方式和内部实现分离,学生上课只需记住几条命令,深入理解时再打开源码阅读函数。

5.3 公共函数库与演示脚本分离的源码组织方式

教学脚本源码不建议一个文件塞几百行。我现在的组织方式是公共函数库加多个演示脚本,目录结构固定不变:

signal_lib/ # 公共函数库 waveform.py # 信号生成函数 operations.py # 卷积、FFT 封装 plotters.py # 绘图封装 demos/ # 每个知识点一个演示脚本 conv_demo.py # 卷积演示 fourier_demo.py # 频谱分析演示 sampling_demo.py # 抽样定理演示

公共函数库不依赖任何演示脚本,演示脚本只 import 自己需要的模块。依赖方面,每个脚本头部写明所需库版本,requirements.txt固定 numpy、matplotlib、scipy 的大版本号,避免 numpy 2.x 的接口变化把旧脚本跑挂。学生 clone 后创建虚拟环境安装依赖,直接运行命令即可。

源码用 git 管理,每个演示脚本对应一次独立提交,提交信息写清楚该脚本演示的知识点。前后两个脚本若要共享新工具函数,先加进signal_lib再做独立提交,学生看提交历史就能理解公共函数库是怎么逐步积累出来的。最后确认整套脚本在干净环境里能跑通,再交付给助教,这是教学脚本源码验收前最后一道工序。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 6:32:15

PCIe 3.0/4.0/5.0兼容性真相:M.2插槽、主板通道与NVMe性能全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 6:31:58

2026年AIGC技术演进与商业应用全景解析

1. 2026年AIGC行业全景扫描2026年的AIGC领域已经完成了从技术探索到商业落地的关键跨越。根据最新行业报告显示&#xff0c;全球AIGC市场规模较2023年增长了近8倍&#xff0c;渗透率在内容创作领域达到37.2%。这个曾经被视为"玩具"的技术&#xff0c;如今正在重构整个…

作者头像 李华
网站建设 2026/9/15 6:30:47

卖芯片的倒贴百亿给买家,这场史上最大上市案全靠左手倒右手

卖芯片的倒贴百亿给买家&#xff0c;这场史上最大上市案全靠左手倒右手 2026 年 9 月&#xff0c;全球资本市场迎来了一桩不可思议的交易传闻&#xff1a;制造大模型 Claude 的初创公司 Anthropic&#xff0c;正计划在公开市场上募资最多 1000 亿美元&#xff0c;冲刺约 2 万亿…

作者头像 李华
网站建设 2026/9/15 6:30:12

DPJ-96基于STM32单片机wifi远程监控智慧教室 温湿度光照粉尘人数考勤

1、前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉洪核学长自己做的项目系统达不到老师的要求。为了大家能够顺利以及最少的精力通过毕设&#xf…

作者头像 李华
网站建设 2026/9/15 6:29:37

AI前端实战:SSE流式处理与TypeScript类型安全设计

1. 这不是一份“复习计划”&#xff0c;而是一份9月8日启动的AI前端面试实战推演手册如果你准备在9月8号开始准备今年AI前端面试的话——这句话乍看像一句时间提醒&#xff0c;实则是一道隐含多重技术坐标的信号弹。它背后锚定的不是传统前端八股文&#xff0c;而是2024—2025年…

作者头像 李华
网站建设 2026/9/15 6:29:34

电影院订票网站开发多少钱?3个维度拆解真实成本

电影院订票网站开发多少钱?3个维度拆解真实成本 自己不会代码,却想把线上卖票这碗饭吃下,这焦虑我太懂了。很多人一上来就问“电影院订票网站开发多少钱”,好像只要报了个数字,剩下的就全是运气。别天真了,价格背后是技术栈的深浅、功能模块的复杂度,以及你未来三年的运营天花板。…

作者头像 李华