FEATURED · 精选文章

信号频率分析实战:从傅里叶变换到频谱图,Python代码全解析

发布时间 / 2026/8/6 10:51:02
来源 / 创域科博编辑部
栏目 / 资讯中心
信号频率分析实战:从傅里叶变换到频谱图,Python代码全解析 1. 从“听”到“看”为什么我们需要分析信号的频率信息我们生活在一个充满信号的世界里。你听到的音乐、看到的图像、手机接收的无线网络本质上都是不同形式的信号。这些信号在时间轴上变化告诉我们“发生了什么”。但很多时候仅仅知道“发生了什么”是不够的我们更需要知道“它是由什么组成的”。这就好比听一首交响乐你不仅能感受到旋律的起伏时域信息更想知道里面有多少把小提琴、几支长号以及它们各自在演奏什么音符频域信息。分析信号的频率信息就是把一个复杂的、随时间变化的信号拆解成一系列不同频率、不同强度的简单正弦波的叠加过程。这个需求在工程和科研领域无处不在。音频工程师需要分离人声和背景音乐机械工程师通过分析设备振动信号的频率成分来诊断轴承故障通信工程师需要确保信号在特定的频带内传输以避免干扰生物医学工程师通过分析脑电波EEG的特定频率段如α波、β波来研究大脑状态。可以说从声音处理到图像识别从故障诊断到金融预测频率分析都是一项基础且核心的技能。它让我们能够透过现象看本质从混杂的“一团乱麻”中提取出有意义的、结构化的信息。2. 核心工具傅里叶变换及其家族要分析频率我们离不开一个数学工具傅里叶变换。它的核心思想非常直观任何周期性的、满足一定条件的复杂波形都可以看作是无数个不同频率、不同振幅、不同相位的正弦波和余弦波的叠加。傅里叶变换就是完成这个“拆解”过程的数学公式。2.1 离散傅里叶变换数字世界的基石在实际的计算机和数字系统中我们处理的都是离散时间点采样的信号而不是连续的模拟信号。这时离散傅里叶变换就登场了。DFT是连接时域离散信号和频域离散表示的桥梁。给定一个长度为N的离散信号序列DFT会计算出N个复数这些复数对应着从0频率直流分量到最高频率奈奎斯特频率的N个均匀分布的频率点上的“强度”和“相位”信息。DFT的计算公式虽然看起来复杂但其输出结果非常直观。每个输出值复数的模值幅度代表了该频率成分的强度而其辐角角度则代表了该频率成分的初始相位。通过绘制幅度随频率变化的图幅度谱我们就能一目了然地看到信号中哪些频率成分占主导地位。2.2 快速傅里叶变换让计算变得可行直接计算DFT的复杂度是O(N²)当数据点N很大时比如音频信号一秒就有44100个点计算量会变得无法承受。这就是快速傅里叶变换的意义所在。FFT不是一种新的变换而是计算DFT的一种极其高效的算法它将计算复杂度降低到了O(N log N)。可以说没有FFT现代实时信号处理如你的手机通话降噪、音乐均衡器几乎不可能实现。现在当我们说“做个FFT分析”通常指的就是利用FFT算法来计算信号的频谱。2.3 短时傅里叶变换捕捉频率的“演变”标准的FFT有一个重要的前提假设信号是平稳的即其频率成分不随时间变化。但现实中的很多信号比如语音、音乐、股票价格其频率成分是随时间变化的。一句话里元音和辅音的频率特性截然不同。为了解决这个问题短时傅里叶变换被引入。STFT的基本思想很巧妙它用一个滑动的“时间窗”截取信号的一小段然后对这一小段信号做FFT。接着窗口向前滑动一点再对新的数据段做FFT。如此重复最终我们得到的是一个二维矩阵一个维度是频率另一个维度是时间。将这个矩阵可视化就得到了我们常说的“频谱图”——一种用颜色深浅表示强度、横轴为时间、纵轴为频率的图像。频谱图让我们能够直观地看到频率成分是如何随时间演变的是分析非平稳信号的利器。注意STFT中“时间分辨率”和“频率分辨率”是一对矛盾体。窗口越长频率分辨率越高能区分开两个靠得很近的频率但时间分辨率越差无法精确定位频率变化发生的时刻窗口越短则相反。这被称为“海森堡不确定性原理”在信号处理中的体现选择窗口长度时需要根据具体需求权衡。3. 实战演练从理论到代码的完整流程理论懂了我们上手操作一遍。这里以分析一段包含多个频率成分的合成信号为例使用Python的numpy和matplotlib库这是信号处理最常用的工具组合。3.1 步骤一构造一个测试信号我们首先创建一个已知频率成分的信号这样便于验证分析结果是否正确。import numpy as np import matplotlib.pyplot as plt # 设置基本参数 fs 1000 # 采样频率每秒1000个点 T 1.0 # 信号总时长1秒 t np.linspace(0, T, int(fs * T), endpointFalse) # 时间轴从0到1秒共1000个点 # 构造信号包含10Hz50Hz120Hz三个正弦波并加入一些随机噪声 freq1, amp1 10, 0.8 freq2, amp2 50, 0.5 freq3, amp3 120, 1.0 signal (amp1 * np.sin(2 * np.pi * freq1 * t) amp2 * np.sin(2 * np.pi * freq2 * t) amp3 * np.sin(2 * np.pi * freq3 * t)) # 加入高斯白噪声模拟真实环境 noise 0.2 * np.random.randn(len(t)) signal_noisy signal noise # 绘制时域波形 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(t, signal, b-, alpha0.7, label纯净信号) plt.xlabel(时间 [秒]) plt.ylabel(幅度) plt.title(纯净信号时域图) plt.grid(True) plt.legend() plt.subplot(1, 2, 2) plt.plot(t, signal_noisy, r-, alpha0.7, label含噪信号) plt.xlabel(时间 [秒]) plt.ylabel(幅度) plt.title(含噪信号时域图) plt.grid(True) plt.legend() plt.tight_layout() plt.show()运行这段代码你会看到左右两幅图。左图是三个光滑正弦波的叠加右图是加入了噪声后的信号看起来毛刺很多。在时域上我们已经很难直接分辨出里面到底有几个频率成分了。3.2 步骤二执行FFT并绘制幅度谱接下来我们对含噪信号进行FFT分析看看能否“揪出”隐藏的频率。from scipy.fft import fft, fftfreq # 使用SciPy的高效FFT实现 # 执行FFT N len(signal_noisy) # 信号长度 yf fft(signal_noisy) # FFT结果是复数数组 xf fftfreq(N, 1/fs)[:N//2] # 获取对应的频率轴只取正频率部分 # 计算幅度谱 (取模并归一化) amplitude_spectrum 2.0/N * np.abs(yf[:N//2]) # 绘制幅度谱 plt.figure(figsize(10, 4)) plt.plot(xf, amplitude_spectrum, g-) plt.xlabel(频率 [Hz]) plt.ylabel(幅度) plt.title(信号的单边幅度谱 (FFT结果)) plt.grid(True) plt.xlim([0, 200]) # 聚焦在我们关心的频率范围 plt.show()现在奇迹发生了。在频域图上时域里杂乱无章的波形变成了三个清晰的尖峰它们分别位于10Hz、50Hz和120Hz附近并且尖峰的高度大致对应我们之前设置的振幅0.8 0.5 1.0。周围的“基底”就是加入的宽带噪声在频域上的表现。通过这个简单的例子你就能直观感受到频率分析的威力它从一团混沌中清晰地分离出了我们“预设”的真相。3.3 步骤三使用STFT生成频谱图最后我们看看如果信号的频率是随时间变化的STFT如何展现它。from scipy.signal import stft # 构造一个频率变化的信号线性调频信号 f0, f1 5, 50 # 频率从5Hz线性增加到50Hz chirp_signal np.sin(2 * np.pi * (f0 * t (f1 - f0) * t**2 / (2 * T))) # 执行STFT f_stft, t_stft, Zxx stft(chirp_signal, fs, nperseg256) # nperseg是窗口长度 # 绘制频谱图 plt.figure(figsize(10, 5)) plt.pcolormesh(t_stft, f_stft, np.abs(Zxx), shadinggouraud) plt.colorbar(label强度) plt.title(线性调频信号的短时傅里叶变换频谱图) plt.ylabel(频率 [Hz]) plt.xlabel(时间 [秒]) plt.ylim([0, 100]) plt.tight_layout() plt.show()在这张频谱图上你会看到一条从5Hz斜向上延伸到50Hz的亮色条纹。它完美地展示了信号频率随时间线性增加的过程。这就是STFT的价值所在——它为我们提供了信号频率内容随时间演变的“地图”。4. 关键参数与常见陷阱让分析结果更可靠掌握了基本操作只是第一步。在实际应用中参数设置不当会导致分析结果完全失真。以下是几个必须理解的要点。4.1 采样频率与奈奎斯特采样定理这是数字信号处理的“第一定律”。采样频率必须大于信号最高频率成分的两倍。换句话说信号中你希望分析的最高频率必须小于采样频率的一半。这个“采样频率的一半”被称为奈奎斯特频率。如果违反会怎样会发生“混叠”。高于奈奎斯特频率的信号成分会被错误地“折叠”到低频区域污染你的频谱分析结果而且这个过程是不可逆的。实战建议在数据采集阶段务必在传感器或ADC模数转换器前端使用抗混叠滤波器一个低通滤波器将高于奈奎斯特频率的成分滤除。在分析已知数据时首先要确认采样频率是否足够高。4.2 频谱泄露与窗函数的选择FFT在数学上假设我们分析的信号片段是无限长周期信号的一个周期。但现实中我们截取的是有限长的一段。如果这段信号的起点和终点幅度不相等就相当于用一个矩形窗粗暴地截断信号这会在频谱中引入虚假的频率成分即“频谱泄露”——本该是单一频率的尖峰会“泄露”能量到旁边的频率上形成旁瓣。解决方案是使用窗函数。窗函数如汉宁窗、汉明窗、布莱克曼窗在信号两端平滑地过渡到零从而减少因截断产生的突变。使用窗函数是专业分析的标配。# 使用汉宁窗的FFT示例 window np.hanning(N) # 生成汉宁窗 signal_windowed signal_noisy * window # 给信号加窗 yf_windowed fft(signal_windowed) amplitude_spectrum_windowed 2.0/N * np.abs(yf_windowed[:N//2]) plt.figure(figsize(10,4)) plt.subplot(1,2,1) plt.plot(xf, amplitude_spectrum, b-, label无窗矩形窗) plt.plot(xf, amplitude_spectrum_windowed, r-, label汉宁窗) plt.xlabel(频率 [Hz]); plt.ylabel(幅度); plt.title(加窗前后对比); plt.grid(True); plt.legend() plt.xlim([0, 200]) # 局部放大看主瓣和旁瓣 plt.subplot(1,2,2) plt.plot(xf, amplitude_spectrum, b-, label无窗) plt.plot(xf, amplitude_spectrum_windowed, r-, label汉宁窗) plt.xlabel(频率 [Hz]); plt.ylabel(幅度); plt.title(50Hz峰值的局部放大); plt.grid(True); plt.legend() plt.xlim([40, 60]); plt.ylim([0, 0.6]) plt.tight_layout() plt.show()运行对比后你会发现加窗后红色曲线的主峰更“干净”旁边的毛刺旁瓣被显著抑制了。但代价是主峰略微变宽频率分辨率轻微下降。汉宁窗在抑制旁瓣和保持主瓣宽度之间取得了很好的平衡是最常用的窗函数之一。4.3 频率分辨率与补零操作频率分辨率是指频谱图上能够区分两个相邻频率分量的最小间隔。它由Δf 采样频率 / 数据点数决定。数据点越多频率分辨率越高。有时我们的数据长度有限但又希望频谱图看起来更光滑、更精细这时可以使用“补零”操作即在原始数据后面添加若干个0然后再做FFT。补零能提高分辨率吗不能。补零不能增加任何新的信息因此无法提高真实的频率分辨率即区分两个紧邻频率的能力。它只是对已有的频谱进行了“插值”让曲线看起来更光滑并且使频率刻度更细便于读数。何时需要补零当你需要精确读取峰值频率例如通过寻找最大值索引来定位频率或者希望频谱图更美观时。5. 超越基础高级技术与应用场景掌握了FFT和STFT你已经能解决80%的问题。但对于更复杂的场景可能需要以下工具。5.1 功率谱密度衡量信号功率的分布幅度谱告诉我们各频率成分的振幅而功率谱密度则告诉我们信号功率在不同频率上的分布密度单位通常是V²/Hz或dB/Hz。这对于分析随机信号如噪声特别有用因为随机信号的频谱是连续分布的用PSD更能反映其统计特性。计算PSD的经典方法是韦尔奇法它对信号进行分段、加窗、求周期图后再平均能有效降低估计的方差得到更平滑的谱估计。from scipy.signal import welch # 使用韦尔奇方法估计功率谱密度 f_psd, psd welch(signal_noisy, fs, nperseg256) plt.figure(figsize(10,4)) plt.semilogy(f_psd, psd) # 纵坐标用对数坐标便于观察 plt.xlabel(频率 [Hz]); plt.ylabel(功率谱密度 [V**2/Hz]) plt.title(信号的功率谱密度估计 (韦尔奇方法)) plt.grid(True); plt.xlim([0, 200]) plt.show()5.2 小波变换多分辨率分析的利器STFT的窗口大小是固定的这决定了它在整个时频平面上具有固定的分辨率。小波变换则使用一个可伸缩、可平移的母小波函数能在高频部分提供好的时间分辨率在低频部分提供好的频率分辨率。这非常适合于分析瞬时突变信号如机械冲击、心电图中的异常波或具有分形特性的信号。5.3 实际应用案例简易音频频谱可视化我们可以将上述知识整合做一个简单的实时音频频谱可视化程序的核心部分这里仅展示分析部分不涉及实时采集。import numpy as np import matplotlib.pyplot as plt from scipy.io import wavfile # 1. 读取一个音频文件 sample_rate, audio_data wavfile.read(your_audio_file.wav) # 通常音频是双声道我们取单声道分析 if len(audio_data.shape) 1: audio_data audio_data[:, 0] # 归一化 audio_data audio_data / np.max(np.abs(audio_data)) # 2. 截取一段进行分析例如第1秒到第2秒 start_idx 1 * sample_rate end_idx 2 * sample_rate segment audio_data[start_idx:end_idx] time_segment np.arange(len(segment)) / sample_rate # 3. 加窗并计算FFT N len(segment) window np.hanning(N) segment_windowed segment * window yf fft(segment_windowed) xf fftfreq(N, 1/sample_rate)[:N//2] amplitude_spectrum 2.0/N * np.abs(yf[:N//2]) # 4. 绘制时域波形和频谱 fig, (ax1, ax2) plt.subplots(2, 1, figsize(12, 8)) ax1.plot(time_segment, segment) ax1.set_xlabel(时间 [秒]); ax1.set_ylabel(幅度); ax1.set_title(音频片段 (时域)); ax1.grid(True) ax2.plot(xf, amplitude_spectrum) ax2.set_xlabel(频率 [Hz]); ax2.set_ylabel(幅度); ax2.set_title(音频频谱 (频域)); ax2.grid(True) ax2.set_xlim([0, 5000]) # 人耳可听范围大致在20Hz-20kHz这里显示到5kHz ax2.set_yscale(log) # 对数坐标更符合人耳对响度的感知 plt.tight_layout() plt.show()通过这个案例你可以看到一段音乐在频域上可能包含从低频的鼓点到高频的镲片等丰富的频率成分。均衡器调节的本质就是提升或衰减这些特定频段的幅度。频率分析是一个从抽象数学到具体工程实践的桥梁。理解其原理避开常见的坑再结合强大的编程工具你就能让数据“开口说话”揭示出隐藏在时间序列背后的周期性规律和结构信息。无论是为了调试电路、优化产品还是进行科学研究这项技能都将是你工具箱里的一把利器。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻