MIT RES.6.007《信号与系统》是麻省理工学院开放课程平台上一套经典视频课程,1987 年由 Alan V. Oppenheim 与 A. S. Willsky 主讲。虽然录制至今已经几十年,但这套课程依然是许多通信、控制、音频处理和算法工程师系统重学信号与系统基础的首选材料。真正去学的时候你会发现,视频里没有花哨的动画,没有炫酷的演示,只有板书、推导和大量直觉解释,但它把“信号与系统为什么这样设计”讲得非常清楚。这篇博客会围绕这套课程规划一条可复现的自学路径,同时用 Python 和 NumPy 把课程里的核心概念做成最小实验,帮助你从“看懂公式”走向“能写代码验证公式”,再进一步知道在实际工程项目里应该注意什么。
如果你正在准备通信、嵌入式、图像处理、深度学习等方向的面试,或者工作中遇到滤波器设计、采样率转换、频谱分析等问题,这套课程和本文的验证实验可以帮你把基础补扎实。文章的后续内容不需要依赖原视频也能阅读,但如果你能同时打开视频对照章节学习,效果会更好。
1. 为什么 1987 年的 MIT 信号与系统课程仍值得系统学习
1.1 课程背景与内容范围
RES.6.007 是 MIT OpenCourseWare 上公开的“Signals and Systems”课程录像,以教材《Signals and Systems》(Oppenheim 与 Willsky 著)为蓝本。1987 年的版本由 Oppenheim 本人主讲,课程内容覆盖从连续时间信号、离散时间信号、线性时不变系统、卷积、傅里叶级数与傅里叶变换,到采样、拉普拉斯变换、Z 变换、通信系统与反馈系统。
这套课程的核心主线可以概括成一句话:用一组统一的数学工具去描述“信号经过系统之后变成什么”。课程先讲信号的表示,再讲系统的性质,然后逐步引出卷积、傅里叶分析、采样和变换域分析。这种组织方式在今天看来仍然是信号处理知识体系的经典骨架。
与很多短视频教程不同,这套课程的特点是重推导、重直觉。Oppenheim 在讲解傅里叶变换时,不是直接给公式,而是先回答“为什么要用复指数信号去分解一个信号”“频率分量意味着什么”。如果你只看结论不看推导,后面遇到线性时不变系统的频率响应、采样定理的混叠条件时,很容易只知道式子,不知道适用边界。
1.2 这门课对现代开发者和工程师的实际价值
很多开发者第一次接触信号与系统是在大学课堂,当时可能因为数学基础薄弱或缺少应用场景,学完后只留下“傅里叶变换很神奇”的印象。工作后再回到这套课程,价值反而更大。原因是现代工程里到处是信号处理问题:
- 音频处理中的频谱分析、降噪、重采样。
- 通信中的调制解调、匹配滤波、信道均衡。
- 控制系统中的 PID 参数整定、系统稳定性分析。
- 图像处理中的卷积滤波、边缘检测、图像缩放。
- 深度学习中的卷积神经网络,底层本质也是离散卷积运算。
这套课程的价值在于,它把“连续时间”和“离散时间”放在同一个框架里讲。你既能理解模拟信号的物理含义,又能理解数字信号处理中的采样和 Z 变换。很多人在实际项目里分不清 DFT、DTFT 和 FFT 的关系,根源就是没有建立统一的频率分析框架。学完这套课程后,再去看现代数字信号处理库的接口文档,会更容易理解每个参数背后的物理含义。
2. 先建立信号与系统的知识骨架:从连续到离散
2.1 信号表示:连续时间、离散时间与数字信号
信号是信息的载体。按自变量类型,信号可以分为连续时间信号和离散时间信号。连续时间信号用x(t)表示,自变量t取实数;离散时间信号用x[n]表示,自变量n只取整数。数字信号是离散时间信号,并且幅值也被量化成有限字长。课程一开始就强调这个区分,因为后续所有变换的定义都依赖自变量类型。
在实际项目中,传感器输出的电压、麦克风采集到的声波、天线收到的电磁波,天然是连续时间信号。单片机通过 ADC 采样后变成离散时间信号,再经过量化变成数字信号。如果采样率不满足奈奎斯特条件,原始信号中的高频分量会折叠到低频位置,这就是混叠。课程会在采样章节仔细讨论这个问题,实际工程中这也是音频和通信系统最常见的坑之一。
信号还可以分成能量信号与功率信号、周期信号与非周期信号。判断一个信号能否使用傅里叶级数,关键看它是否满足狄利克雷条件。课程中会反复强调这些分类,因为不同类别的信号对应不同的分析工具。
2.2 系统与 LTI 系统的核心性质
系统是把一个信号映射成另一个信号的黑盒,记为y(t) = T{x(t)}或y[n] = T{x[n]}。系统可以按性质分类:线性还是非线性、时变还是时不变、因果还是非因果、稳定还是不稳定。
信号与系统课程的核心研究对象是线性时不变系统,简称 LTI 系统。线性意味着系统对输入的加权叠加等于对输出做同样的加权叠加;时不变意味着如果输入延迟一段时间,输出也会延迟相同时间,而形状不变。这两个性质合在一起,才能推导出系统可以由单位冲激响应完全描述。
LTI 系统的输出等于输入信号与单位冲激响应的卷积,这是课程最重要的结论之一。对于连续时间系统,输出是卷积积分;对于离散时间系统,输出是卷积和。这个结果的意义在于:只要知道系统对单位冲激的响应,就能预测系统对任意输入的响应。这也就是为什么很多信号处理算法库都提供impulse和convolve函数。
2.3 卷积、傅里叶变换与拉普拉斯/Z 变换的定位关系
初学者最容易搞混的问题是:有了卷积,为什么还要学傅里叶变换、拉普拉斯变换和 Z 变换?答案可以这样理解:卷积是时域中描述系统输入输出关系的工具,但它运算起来比较复杂;傅里叶变换把信号从时域换到频域,时域卷积对应频域相乘,这是极大的简化。
不过,傅里叶变换并不是万能的。它要求信号满足绝对可积条件,工程上很多信号(比如指数增长信号、阶跃信号)直接做傅里叶变换会碰到收敛性问题。于是连续时间域引入拉普拉斯变换,通过衰减因子让更多信号可以变换;离散时间域引入 Z 变换来分析更一般的序列。拉普拉斯变换中的s平面、Z 变换中的z平面上的零极点位置,直接决定系统的稳定性。
课程会明确区分这几种变换的关系:
- 连续时间傅里叶变换是拉普拉斯变换在
s = jω轴上的特殊情况。 - 离散时间傅里叶变换(DTFT)是 Z 变换在单位圆
z = e^(jω)上的特殊情况。 - 计算机只能处理有限的离散点,因此需要取 DTFT 的等间隔采样,得到离散傅里叶变换(DFT),快速算法是 FFT。
把这层关系想明白后,再看到不同教材里的傅里叶变换公式差异,就不会慌了。
3. 搭建自学环境:Python + NumPy/SciPy + Matplotlib
3.1 环境依赖与安装命令
学习这门课,纸上推导很重要,但用代码验证推导更容易建立直觉。推荐的实验环境是 Python,配合 NumPy、SciPy 和 Matplotlib。安装命令如下:
python -m venv signals_env source signals_env/bin/activate # Windows 下使用 signals_env\Scripts\activate pip install numpy scipy matplotlib jupyter如果你不想手动创建虚拟环境,也可以使用 Anaconda:
conda create -n signals python=3.11 conda activate signals conda install numpy scipy matplotlib jupyter这里选择 Python 而不是 MATLAB,是因为 Python 生态免费、易上手,而且后续学习深度学习时也能复用同一套环境。SciPy 里有scipy.signal模块,封装了大量滤波器设计和系统分析函数,适合做课程对应的验证实验。
3.2 项目目录结构和常用工具
建议为这门课单独建一个目录,按章节组织代码。一种可行的目录结构如下:
signals-and-systems/ ├── chapters/ │ ├── 01_signals/ # 信号表示与基本运算 │ ├── 02_lti_systems/ # 线性时不变系统与卷积 │ ├── 03_fourier/ # 傅里叶级数与变换 │ ├── 04_sampling/ # 采样与重建 │ ├── 05_laplace/ # 拉普拉斯变换 │ ├── 06_z_transform/ # Z 变换 │ └── 07_applications/ # 通信、滤波等应用 ├── common/ │ └── signal_utils.py # 常用绘图与信号生成函数 ├── notebooks/ │ └── experiments.ipynb └── README.md论文或作业中的公式推导建议用 Markdown 或 LaTeX 记录,代码单独放一个文件。这样做的目的是让你能随时回到某个实验,重新运行并确认当时的结论。很多人在 “看懂” 和 “跑通” 之间差了这一步:只看视频不动手,三个月后公式全忘光。
3.3 用 Python 验证冲激响应与卷积
课程会证明:LTI 系统的输出等于输入与冲激响应的卷积。用一段最简单的 Python 代码就能验证离散卷积的线性性质。
import numpy as np # 定义输入信号 x[n] x = np.array([1, 2, 3, 4]) # 定义单位冲激响应 h[n] h = np.array([1, -1, 2]) # 使用 numpy 计算卷积 y = np.convolve(x, h) print("输入信号 x:", x) print("冲激响应 h:", h) print("卷积结果 y:", y)运行后你会看到输出:
输入信号 x: [1 2 3 4] 冲激响应 h: [ 1 -1 2] 卷积结果 y: [ 1 1 3 5 7 8]np.convolve默认使用full模式,结果长度为len(x) + len(h) - 1。这个结果跟你手动按卷积和公式计算完全一致。为了进一步确认 LTI 性质,可以验证一下“输入延迟后,输出也会延迟”。例如:
x_delayed = np.concatenate([[0], x]) y_delayed = np.convolve(x_delayed, h) print("延迟输入后的输出:", y_delayed) print("原来输出延迟一位 :", np.concatenate([[0], y]))如果两个数组完全相等,就说明系统具有时不变性。这个实验虽然简单,却是理解 LTI 系统性质很好的起点。
3.4 用 Python 观察傅里叶变换与频谱
接下来用离散傅里叶变换观察一个正弦信号的频谱。这里我们先不关心数学定义,只看结果。
import numpy as np import matplotlib.pyplot as plt fs = 1000 # 采样率 1000 Hz t = np.arange(0, 1, 1/fs) # 时长 1 秒 f0 = 50 # 信号频率 50 Hz x = np.sin(2 * np.pi * f0 * t) # 计算 FFT X = np.fft.fft(x) freqs = np.fft.fftfreq(len(x), 1/fs) # 只显示正频率部分 half = len(x) // 2 plt.figure(figsize=(10, 4)) plt.plot(freqs[:half], np.abs(X[:half])) plt.xlabel('频率 (Hz)') plt.ylabel('幅度') plt.title('50Hz 正弦信号的幅度谱') plt.grid(True) plt.show()这个实验对应课程中“周期信号的傅里叶级数”和“非周期信号的傅里叶变换”两个概念。你会看到 50Hz 处出现一个峰值,这正是信号的频率分量。如果把正弦信号改成方波,频谱中会出现多个奇次谐波,这能帮助你理解傅里叶级数分解的含义。
4. 课程知识节点的“最小可复现实验”设计
4.1 连续时间傅里叶变换的数值验证
课程中连续时间傅里叶变换公式为:
X(jω) = ∫ x(t) e^(-jωt) dt
纯解析计算适合手推,但数值验证可以帮助你建立直观认识。例如对矩形脉冲x(t) = 1 (|t| ≤ T),它的傅里叶变换是sinc函数。用 Python 可以直接画出来:
import numpy as np import matplotlib.pyplot as plt T = 1.0 t = np.linspace(-5, 5, 10000) x = np.where(np.abs(t) <= T, 1.0, 0.0) # 数值积分近似傅里叶变换 def ctft_numerical(x, t, omega): dt = t[1] - t[0] return np.sum(x * np.exp(-1j * omega * t)) * dt omega = np.linspace(-20, 20, 1000) X = np.array([ctft_numerical(x, t, w) for w in omega]) plt.figure(figsize=(10, 4)) plt.plot(omega, np.abs(X)) plt.xlabel('角频率 ω (rad/s)') plt.ylabel('幅度 |X(jω)|') plt.title('矩形脉冲的连续时间傅里叶变换幅度') plt.grid(True) plt.show()这个实验帮助你理解时域宽度与频域宽度的反比关系:矩形脉冲越宽,频谱主瓣越窄;脉冲越窄,频谱越宽。这也是通信系统中“时域上快速变化的信号占据更宽频带”的直觉来源。
4.2 采样定理与混叠现象实验
采样定理是课程中工程价值最高的章节之一。为了观察混叠,可以构造一个高频正弦信号,用低于奈奎斯特频率的采样率进行采样,然后观察离散频率。
import numpy as np import matplotlib.pyplot as plt f_signal = 400 # 原始信号 400 Hz fs_low = 300 # 采样率 300 Hz,不满足奈奎斯特条件 fs_high = 1000 # 采样率 1000 Hz,满足奈奎斯特条件 t_cont = np.linspace(0, 0.01, 5000) x_cont = np.sin(2 * np.pi * f_signal * t_cont) n_low = np.arange(0, int(0.01 * fs_low)) x_low = np.sin(2 * np.pi * f_signal * n_low / fs_low) n_high = np.arange(0, int(0.01 * fs_high)) x_high = np.sin(2 * np.pi * f_signal * n_high / fs_high) plt.figure(figsize=(12, 6)) plt.subplot(3, 1, 1) plt.plot(t_cont, x_cont) plt.title('原始 400Hz 信号') plt.subplot(3, 1, 2) plt.stem(n_low / fs_low, x_low, basefmt=" ") plt.title('300Hz 采样(混叠)') plt.subplot(3, 1, 3) plt.stem(n_high / fs_high, x_high, basefmt=" ") plt.title('1000Hz 采样(正常)') plt.tight_layout() plt.show()在 300Hz 采样的结果中,你会看到一个低频正弦波,频率大约是 100Hz,这就是 400Hz 信号混叠后的结果。实际项目里如果 ADC 采样率不够,或者没有前置抗混叠滤波器,就会出现这种 “假低频” 信号,导致后续算法完全失真。
4.3 LTI 系统频率响应的可视化
系统的频率响应H(jω)描述的是系统对不同频率输入信号的增益和相位影响。用 SciPy 可以方便地计算一个模拟滤波器的频率响应。
from scipy import signal # 二阶低通滤波器,截止频率 100Hz b, a = signal.butter(2, 100, btype='low', fs=1000) w, h = signal.freqz(b, a, fs=1000) plt.figure(figsize=(10, 6)) plt.subplot(2, 1, 1) plt.semilogx(w, 20 * np.log10(np.abs(h))) plt.title('二阶巴特沃斯低通滤波器幅频响应') plt.xlabel('频率 (Hz)') plt.ylabel('幅度 (dB)') plt.grid(True) plt.subplot(2, 1, 2) plt.semilogx(w, np.angle(h)) plt.title('相频响应') plt.xlabel('频率 (Hz)') plt.ylabel('相位 (rad)') plt.grid(True) plt.tight_layout() plt.show()这个实验对应课程中“用拉普拉斯变换分析系统”“频率响应与滤波”的内容。你会看到低频信号被保留,高频信号被衰减。如果改变滤波器阶数,你会看到过渡带变陡,但相位失真也更严重。理解这个 trade-off,对实际滤波器设计非常重要。
4.4 离散傅里叶变换与快速傅里叶变换的关系
很多教材会区分 DTFT、DFT 和 FFT。用代码可以直观展示:DFT 相当于在单位圆上等间隔采样 DTFT。对有限长序列,可以直接用np.fft.fft计算 DFT。
x = np.array([1, 2, 3, 4]) X = np.fft.fft(x, n=8) # 补零到 8 点 print("X[0]:", X[0]) # 直流分量,等于所有样本之和 print("X[4]:", X[4]) # 奈奎斯特频率分量补零可以增加频率采样点的密度,但不能增加频率分辨率。真正的频率分辨率由原始数据长度决定,补零只是插值。这个坑在课程后的工程实践中很常见:有人以为在 FFT 之前随便补一堆零就能“看清”更细的频率成分,其实那只是让离散谱线更密集,并没有引入新的分辨能力。
5. 学习这门课最常见的几个坑和排查路径
5.1 术语混淆:傅里叶级数、傅里叶变换、离散时间傅里叶变换、DFT 的区别
很多人在学到后面会混淆这些概念。它们的区别可以这样记:
- 傅里叶级数:针对连续时间周期信号,分解成一系列谐波复指数之和。
- 连续时间傅里叶变换:针对连续时间非周期信号,频谱是连续的函数。
- 离散时间傅里叶变换(DTFT):针对离散时间非周期序列,频谱是周期连续的。
- 离散傅里叶变换(DFT):对有限长序列在频域进行等间隔采样得到的有限长序列,是计算机能直接处理的工具。
- 快速傅里叶变换(FFT):计算 DFT 的高效算法,不是新的变换。
如果连这些概念都分不清,调试频谱程序时会出现各种 “频率轴不对” 的问题。排查时先问自己:我的信号是周期的还是非周期的,连续的还是离散的,限长的还是无限长的。
5.2 符号和版本差异:连续与离散的时移、频率变量
连续时间用t和ω,离散时间用n和Ω。很多教材对离散时间频率用ω或Ω表示,容易造成混淆。实际编程中,频率轴单位经常面临 Hz 与 rad/s 的换算:
ω = 2πf- 数字角频率
Ω = ω T_s = 2π f / f_s
排查频率轴错位问题时,先确认采样率fs有没有用对。常见错误是直接用np.fft.fftfreq返回的值,但没有换算成实际物理频率。代码中最好统一封装一个频率轴函数,减少重复出错。
5.3 数值计算的采样点数和频率分辨率设置不当
做 FFT 实验时,一个经典错误是:采样点数太少,导致频谱“糊成一片”;采样点数太多,导致计算量大而且看不出细节。频率分辨率Δf与采样时长T的关系是:
Δf = 1 / T = f_s / N
其中N是 FFT 点数。如果两个信号频率差小于Δf,FFT 频谱里它们会合并成一个峰。想要分辨两个邻近频率,必须增加采样时长,而不是无脑补零。
排查频谱分辨率问题时,可以按以下顺序检查:
- 确认原始信号使用的时间数组长度。
- 计算理论频率分辨率是否为
1 / T。 - 确认是否误用补零后的长度去计算
Δf。 - 确认频谱图横轴是否已经转换为 Hz。
5.4 把数学推导和工程应用脱节
课程里的公式推导是为了建立统一框架,但工程实现时还要考虑很多现实条件:信号量化位数、有限字长效应、窗函数的频谱泄漏、滤波器相位失真、实时处理延迟。如果只背公式,不写实验,很难发现这些坑。
一个建议是,每学完一章,就找一个实际场景做一个小项目。比如学完傅里叶变换,就写一个简单的音调检测器;学完采样,就用两个不同采样率采集同一段音频,对比混叠现象;学完 Z 变换,就分析一个数字滤波器的零极点并画出幅频响应。这样做能让抽象概念变成可操作的经验。
6. 学习环境与工程生产环境的差异
6.1 课程作业 vs 真实项目中的信号处理
课程作业通常给出理想的解析信号,比如x(t) = cos(2πt) + cos(20πt)。你手推或者用代码画频谱,得到的是干净谱线。真实项目里,传感器数据带有噪声、直流偏置、幅度漂移、非平稳特性,频率分量会随时间变化。课程里的 LTI 系统和傅里叶变换假设信号在无穷长时间上性质不变,实际项目往往要使用短时傅里叶变换、小波变换或滤波器组。
因此,学完课程后不要直接套用连续傅里叶变换公式去处理一段很长的采样数据。应该先做预处理:去直流、加窗、分段、去趋势。这些工程细节教材往往不会展开,但生产环境里省不掉。
6.2 从公式到代码的转换注意事项
将数学公式转换成代码,最容易出错的是索引和边界。离散卷积公式里的求和范围、时移操作中的n-k,在数组里对应的是索引偏移。连续信号的时移x(t - t0)在离散化时要保证t0是采样间隔的整数倍,否则会产生相位误差。
另外,数值积分和解析积分不同。用数值方法近似傅里叶变换时,采样间隔dt必须足够小,否则高频部分会严重失真。建议写代码时先打印数组长度、采样间隔、频率范围,确认与理论推导一致后再继续分析。
6.3 生产环境需要的额外处理:浮点误差、窗函数、实时性
生产环境里的信号处理还要考虑:
- 浮点误差:级联滤波器运算会累积误差,必要时使用双精度或定点化处理。
- 频谱泄漏:非整周期截断会让频谱出现拖尾,加窗函数可以缓解,但会牺牲主瓣宽度。
- 实时性:FFT 虽然快,但如果数据流非常大,仍需考虑分帧、重叠、缓存和线程调度。
- 异常检测:实际信号可能丢点、饱和、越界,需要先做数据质量检查再做算法处理。
这些内容课程不会直接教,但理解了课程中的基本原理后,碰到这些问题你能更快定位到原因。例如频谱泄漏,你如果能想起傅里叶变换对周期信号的假设,就自然明白非整周期截断为什么会产生虚假频率分量。
7. 可复用的学习路线与自查清单
7.1 按周拆解的学习计划
学习这套课程需要投入的时间因基础而异。一个可行的计划是安排 8 到 12 周,每周学 1 个主题,配套完成一组代码实验。下表是一个参考节奏:
| 周次 | 学习主题 | 配套实验 | 核心输出 |
|---|---|---|---|
| 1 | 信号的分类与基本运算 | 生成正弦、指数、阶跃、冲激信号 | 能画出常见信号波形,理解时移、尺度变换 |
| 2 | 系统性质与 LTI 系统 | 验证线性与时不变性 | 能判断一个系统是否 LTI |
| 3 | 卷积积分与卷积和 | 手动计算卷积,并用 numpy 验证 | 掌握卷积运算的图形化过程 |
| 4 | 傅里叶级数 | 方波与三角波的谐波合成 | 理解吉布斯现象 |
| 5 | 连续时间傅里叶变换 | 矩形脉冲的 sinc 频谱 | 理解时频宽度反比关系 |
| 6 | 离散时间傅里叶变换 | 有限长序列的 DTFT 数值计算 | 理解频谱周期性 |
| 7 | 采样定理 | 混叠实验 | 能解释采样率不足的后果 |
| 8 | 拉普拉斯变换 | 画零极点图与系统稳定性分析 | 能判断系统是否稳定 |
| 9 | Z 变换 | 数字滤波器零极点分析 | 能分析离散系统的稳定性与频率响应 |
| 10 | 通信系统与调制 | 简单 AM 调制的频谱图 | 理解频谱搬移原理 |
| 11 | 反馈系统 | 一阶反馈系统响应 | 理解反馈对稳定性的影响 |
| 12 | 综合项目 | 设计一个低通滤波器处理音频 | 完成一个端到端信号处理流程 |
这个计划不是唯一答案,你可以根据自己的节奏调整。关键是每个主题都要落到一个能运行的实验上,而不是只看视频。
7.2 学完每一章后的自查清单
每学完一章,可以用下面的清单检查自己是否真的掌握了:
- [ ] 能否用自己的话解释这一章解决什么问题?
- [ ] 能否不看笔记推导出核心公式?
- [ ] 能否用代码生成一个例子,验证公式的输出?
- [ ] 能否说出这个理论在什么条件下成立,什么条件下不成立?
- [ ] 能否举出一个实际应用场景,说明这个知识有什么用?
- [ ] 是否知道这个知识点与前后章节的联系?
如果某个问题答不上来,就回去重新看视频或教材,不要急着往前赶。信号与系统是高度依赖前置知识的课程,前半部分的卷积和傅里叶变换理解不透,后面采样和 Z 变换会很难推进。
7.3 扩展方向:控制系统、通信、图像处理、深度学习
学完 RES.6.007 后,你可以根据工作方向继续扩展:
- 控制系统方向:学习状态空间方程、能控性与能观性、PID 与频域设计。
- 通信方向:学习随机信号、匹配滤波、调制解调、信道编码。
- 图像处理方向:学习二维傅里叶变换、图像滤波、小波变换。
- 深度学习方向:卷积神经网络中的卷积运算、时频特征提取、语音识别前端。
这些领域都会复用信号与系统的基础。例如卷积神经网络里的卷积核,本质就是一组有限的冲激响应;语音识别里的梅尔频谱,本质就是短时傅里叶变换后做频率映射。把基础打牢,学这些扩展方向会顺畅得多。
在学习的过程中,最值得坚持的一个习惯是:把每一个抽象公式都变成一个 10 行以内的 Python 实验。第一次跑通后,你的理解深度会明显高于只看推导的人。遇到问题不要先怀疑数学公式,要先问自己的代码是否实现了公式的假设条件。这种排查思路,也会在以后处理真实数据时帮上大忙。