信号频谱分析实战:从1Hz余弦波到8Hz采样的完整解析(附Python代码)

张开发
2026/4/13 1:33:17 15 分钟阅读

分享文章

信号频谱分析实战:从1Hz余弦波到8Hz采样的完整解析(附Python代码)
信号频谱分析实战从1Hz余弦波到8Hz采样的完整解析附Python代码在数字信号处理领域频谱分析是理解信号特性的核心技能。想象一下当你面对一段未知的音频信号或传感器数据时如何快速判断其中包含哪些频率成分这就是频谱分析要解决的关键问题。本文将从一个简单的1Hz余弦波出发通过不同采样率的对比实验带你深入理解频谱分析的实际应用场景。我们将重点关注工程实践中最常见的两个问题采样率选择和频谱泄露现象。不同于教科书上的理论推导这里每步都配有可运行的Python代码和可视化结果适合已经了解傅里叶变换基本概念但想提升实战能力的中级学习者。通过本文你将掌握如何避免常见的频谱分析陷阱并学会解读实际工程中的频谱图。1. 实验环境准备与基础概念1.1 Python环境配置推荐使用Anaconda创建专用环境确保所有依赖库版本一致conda create -n signal_analysis python3.9 conda activate signal_analysis pip install numpy matplotlib scipy核心库的作用NumPy提供高效的数组运算和FFT实现Matplotlib用于信号和频谱的可视化SciPy包含更多高级信号处理函数1.2 余弦信号生成基础我们先从最简单的1Hz余弦信号开始import numpy as np import matplotlib.pyplot as plt t np.linspace(0, 2, 500) # 2秒时长500个采样点 cos_signal np.cos(2 * np.pi * 1 * t) # 1Hz余弦波 plt.figure(figsize(10,4)) plt.plot(t, cos_signal) plt.title(1Hz Continuous Cosine Wave) plt.xlabel(Time (s)) plt.ylabel(Amplitude) plt.grid(True) plt.show()这段代码生成的连续信号在理想情况下应该只有1Hz一个频率成分。但当我们进行数字化采样时情况会变得复杂起来。注意在数字信号处理中连续信号和离散采样信号的分析方法有本质区别这是理解频谱分析的第一步。2. 采样率对频谱的影响2Hz vs 8Hz案例2.1 临界采样2Hz案例根据奈奎斯特采样定理对1Hz信号至少需要2Hz采样率fs 2 # 采样率2Hz t_discrete np.arange(0, 2, 1/fs) # 2秒时长采样间隔0.5s discrete_signal np.cos(2 * np.pi * 1 * t_discrete) # 绘制时域信号 plt.figure(figsize(10,4)) plt.stem(t_discrete, discrete_signal, use_line_collectionTrue) plt.title(1Hz Cosine Sampled at 2Hz) plt.xlabel(Time (s)) plt.ylabel(Amplitude) plt.grid(True) plt.show() # 计算FFT fft_result np.fft.fft(discrete_signal) freqs np.fft.fftfreq(len(discrete_signal), 1/fs) # 绘制幅度谱 plt.figure(figsize(10,4)) plt.stem(freqs, np.abs(fft_result), use_line_collectionTrue) plt.title(Magnitude Spectrum (2Hz Sampling)) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.xlim(-1.5, 1.5) plt.grid(True) plt.show()关键观察点时域信号仅包含两个采样点[1, -1]频谱中除了1Hz成分外在-1Hz处也有对称分量幅度谱峰值出现在±1Hz处符合理论预期2.2 过采样案例8Hz采样将采样率提高到8Hz观察频谱变化fs 8 # 采样率8Hz t_discrete np.arange(0, 2, 1/fs) # 2秒时长采样间隔0.125s discrete_signal np.cos(2 * np.pi * 1 * t_discrete) # 绘制时域信号 plt.figure(figsize(10,4)) plt.stem(t_discrete, discrete_signal, use_line_collectionTrue) plt.title(1Hz Cosine Sampled at 8Hz) plt.xlabel(Time (s)) plt.ylabel(Amplitude) plt.grid(True) plt.show() # 计算FFT fft_result np.fft.fft(discrete_signal) freqs np.fft.fftfreq(len(discrete_signal), 1/fs) # 绘制幅度谱 plt.figure(figsize(10,4)) plt.stem(freqs, np.abs(fft_result), use_line_collectionTrue) plt.title(Magnitude Spectrum (8Hz Sampling)) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.xlim(-4, 4) plt.grid(True) plt.show()对比发现时域波形更接近原始连续信号频谱分辨率更高主瓣更窄频谱泄漏现象开始显现非理想冲激函数3. 频谱泄露现象深度解析3.1 泄露现象的产生机制当采样时长不是信号周期的整数倍时就会发生频谱泄露。让我们模拟1.5秒采样时长的案例fs 8 duration 1.5 # 非整数周期 t_discrete np.arange(0, duration, 1/fs) discrete_signal np.cos(2 * np.pi * 1 * t_discrete) # 计算FFT fft_result np.fft.fft(discrete_signal) freqs np.fft.fftfreq(len(discrete_signal), 1/fs) # 绘制幅度谱 plt.figure(figsize(10,4)) plt.stem(freqs, np.abs(fft_result), use_line_collectionTrue) plt.title(Magnitude Spectrum with Leakage (1.5s Duration)) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.xlim(-4, 4) plt.grid(True) plt.show()典型泄露特征能量从主频扩散到邻近频率出现多个非零频谱分量主瓣宽度明显增加3.2 加窗函数缓解泄露使用汉宁窗(Hanning Window)可以减轻泄露效应window np.hanning(len(discrete_signal)) windowed_signal discrete_signal * window # 计算加窗后的FFT fft_windowed np.fft.fft(windowed_signal) plt.figure(figsize(10,4)) plt.stem(freqs, np.abs(fft_windowed), use_line_collectionTrue) plt.title(Magnitude Spectrum with Hanning Window) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.xlim(-4, 4) plt.grid(True) plt.show()加窗后的改进旁瓣幅度显著降低频谱更集中但主瓣宽度进一步增加这是加窗的代价4. 频率分辨率与采样时长4.1 分辨1.0Hz和1.1Hz信号要区分两个相近频率需要足够长的采样时间duration 10 # 10秒采样时长 t np.linspace(0, duration, 1000) signal1 np.cos(2 * np.pi * 1.0 * t) signal2 np.cos(2 * np.pi * 1.1 * t) combined signal1 signal2 # 计算FFT fft_result np.fft.fft(combined) freqs np.fft.fftfreq(len(combined), t[1]-t[0]) plt.figure(figsize(10,4)) plt.plot(freqs[:len(freqs)//2], np.abs(fft_result[:len(fft_result)//2])) plt.title(Frequency Resolution Demonstration) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.xlim(0.5, 1.5) plt.grid(True) plt.show()关键参数关系频率分辨率 1/采样时长要分辨Δf的频率差至少需要1/Δf的采样时间上例中1.1-1.00.1Hz因此需要至少10秒采样4.2 零填充技术应用通过零填充可以提高频谱显示的平滑度# 原始信号 duration 1 fs 8 t np.arange(0, duration, 1/fs) signal np.cos(2 * np.pi * 1 * t) # 常规FFT fft_normal np.fft.fft(signal) freqs_normal np.fft.fftfreq(len(signal), 1/fs) # 零填充到64点 zero_padded np.zeros(64) zero_padded[:len(signal)] signal fft_padded np.fft.fft(zero_padded) freqs_padded np.fft.fftfreq(len(zero_padded), 1/fs) # 对比绘图 plt.figure(figsize(12,5)) plt.subplot(1,2,1) plt.stem(freqs_normal, np.abs(fft_normal), use_line_collectionTrue) plt.title(Original FFT) plt.xlim(-4,4) plt.subplot(1,2,2) plt.stem(freqs_padded, np.abs(fft_padded), use_line_collectionTrue) plt.title(Zero-padded FFT) plt.xlim(-4,4) plt.tight_layout() plt.show()零填充的效果不增加实际频率分辨率使频谱曲线更平滑便于观察频谱细节

更多文章