
振动信号分析这个领域很多人是从FFT入门的。把一段加速度传感器采集的数据丢进FFT看到频谱上几根尖峰觉得分析也就这么回事。但真正做过设备故障诊断或者语音信号处理的人都知道FFT有个致命的局限它把整个时间轴上的信息压缩成一张静态的频谱图时间维度被彻底抹掉了。你只知道信号里有哪些频率成分却不知道这些成分是什么时候出现的。对于旋转机械的启停过程、轴承的冲击性故障、语音的音素过渡段这种时间盲区会让关键信息全部丢失。短时傅里叶变换STFT就是为解决这个问题而生的。它的思路很朴素既然FFT看不了时间那我就把长信号切成很多短段每段分别做FFT再把结果按时间顺序拼起来。这样得到的时频图横轴是时间纵轴是频率颜色深浅代表该时刻该频率的能量强度。这篇文章会从工程实操的角度把STFT在振动数据处理中的完整落地路径讲清楚——包括窗函数怎么选、参数怎么定、代码怎么写、结果怎么读以及我在实际项目中踩过的那些坑。1. 从FFT到STFT为什么非要做这个升级1.1 FFT在振动分析中的真实短板先看一个具体的场景。假设你在一台电机壳体上装了加速度传感器采样率设为5120 Hz采集了10秒钟的振动数据。这段数据里包含两种成分一种是电机稳定运行时的基频振动大概在50 Hz附近另一种是启动瞬间的冲击振动频率成分很丰富但只持续了不到0.5秒。如果你直接对这10秒数据做FFT得到的频谱上会同时出现50 Hz的尖峰和一片高频区域的能量抬升。问题来了你没法判断那片高频能量是启动瞬间产生的还是整个10秒里一直存在的。如果高频能量只在启动瞬间出现那说明是启动冲击导致的如果一直存在那可能是某个轴承或齿轮的持续故障。这两种情况的处理方案完全不同但FFT给不了你答案。这就是傅里叶变换的全局性带来的问题。它假设信号是平稳的也就是说信号的频率成分不随时间变化。但实际工程中的振动信号尤其是涉及启停、变速、冲击、调制等工况的几乎都是非平稳的。用FFT分析非平稳信号就像用一张全程曝光的照片去分析一场足球比赛的战术——你只能看到球场上所有人的平均位置看不到传球路线和跑位时机。1.2 STFT的核心思路分而治之STFT的做法很直接既然整段信号不平稳那我就假设它在很短的时间窗口内是平稳的。具体操作是用一个固定长度的窗函数比如汉宁窗在时间轴上滑动每滑动一个步长就把窗内的信号截取出来做一次FFT然后把每次FFT的结果按时间顺序排列。这里有一个关键的权衡窗长决定了时间分辨率和频率分辨率的平衡。窗越长截取的信号越长频率分辨率越高能区分更接近的频率成分但时间分辨率越差无法精确定位频率变化发生的时刻。窗越短时间分辨率越好但频率分辨率越差。这个矛盾本质上来源于海森堡不确定性原理在信号处理中的体现你不可能同时获得无限高的时间分辨率和频率分辨率。用一个生活化的类比假设你要描述一首歌的旋律变化。如果你以整首歌为单位来描述你能准确说出用了哪些音符频率分辨率高但说不清每个音符具体在哪个时间点出现时间分辨率差。如果你以每0.1秒为单位来描述你能精确追踪每个瞬间的音高变化时间分辨率高但可能分不清两个很接近的音符频率分辨率差。STFT就是让你在这两者之间选一个平衡点。1.3 STFT在振动诊断中的典型应用场景在实际的旋转机械故障诊断中STFT的价值主要体现在几个场景。第一是变速工况分析比如风力发电机在风速变化时的齿轮箱振动转速在变啮合频率也在变STFT能清晰展示频率随时间的变化轨迹。第二是冲击性故障检测比如轴承外圈剥落产生的周期性冲击每次冲击在时频图上表现为一条垂直的宽带能量线冲击的间隔对应故障特征频率。第三是启停过程监测电机启动时电流频率从0逐渐升到工频对应的振动频率成分也会有一个爬升过程STFT能完整记录这个过渡。还有一个容易被忽略的场景是调制现象分析。齿轮箱中齿轮的啮合频率会被轴的转频调制形成边带。如果只看FFT边带和主频的关系不够直观但在时频图上你能看到能量在啮合频率和边带之间周期性地跳动调制的周期和深度一目了然。2. 窗函数与参数选择STFT效果好坏的分水岭2.1 窗函数到底在做什么很多人做STFT时直接调用scipy.signal.stft窗函数用默认值参数凭感觉设结果出来的时频图模糊一片然后得出结论说STFT效果不好。问题往往出在窗函数和参数选择上。窗函数的作用是减少频谱泄漏。所谓频谱泄漏是因为我们对信号做了截断只取窗内的一段截断处的突变会在频域产生额外的频率成分就像你在一段平滑的曲线上硬生生切一刀切口处会产生高频振荡。窗函数通过在两端逐渐衰减信号来平滑这个截断减少泄漏。但窗函数也有代价它会让主瓣变宽频率分辨率下降同时旁瓣虽然被压低但不会完全消失。不同窗函数的区别就在于主瓣宽度和旁瓣高度的权衡。矩形窗主瓣最窄但旁瓣最高汉宁窗主瓣较宽但旁瓣衰减快汉明窗介于两者之间布莱克曼窗旁瓣最低但主瓣最宽。2.2 振动信号该选什么窗对于振动信号我的经验是优先用汉宁窗Hann。原因有几个。第一汉宁窗的旁瓣衰减速率快对于振动信号中常见的强主频加弱边带的结构汉宁窗能有效抑制强主频的泄漏对弱边带的掩盖。第二汉宁窗的等效噪声带宽适中不会让频率分辨率损失太多。第三汉宁窗在工程界接受度高结果的可比性好。但有两种情况需要换窗。一种是需要精确测量幅值的场合比如做振动烈度评价时汉宁窗的幅值恢复系数不是1需要做补偿。这时候可以用平顶窗Flat Top它的主瓣很宽但幅值精度极高适合校准和计量。另一种是信号中包含瞬态冲击需要精确定位冲击时刻这时候可以用矩形窗或指数窗牺牲频率分辨率换取时间定位精度。下面这张表是我在实际项目中总结的窗函数选择参考窗函数主瓣宽度相对旁瓣峰值dB幅值精度适用场景矩形窗最窄-13差瞬态冲击定位汉宁窗中等-31中等通用振动分析汉明窗中等-41中等语音、声学布莱克曼窗宽-57中等强干扰下的弱信号平顶窗最宽-90优幅值精确测量2.3 窗长和重叠率怎么定窗长的选择取决于你要分析的频率成分。一个实用的经验公式是窗长至少包含你要分辨的最低频率的3到5个周期。比如你要分析50 Hz的基频采样率5120 Hz那么一个周期是102.4个采样点窗长至少取307到512点。如果取512点频率分辨率是5120/51210 Hz意味着频率间隔小于10 Hz的两个成分无法区分。但窗长也不是越长越好。如果信号中有快速变化的成分比如持续0.1秒的冲击窗长取512点对应0.1秒512/51200.1秒刚好能覆盖但如果取1024点就是0.2秒冲击会被稀释在窗内时频图上的能量会被摊薄定位精度下降。重叠率的选择相对灵活。重叠的目的是让时频图在时间轴上更平滑避免因为窗的跳跃导致某些时刻的信号被遗漏。常用的重叠率是50%到75%。重叠率越高时频图越平滑但计算量也越大。对于大多数振动分析场景75%重叠即每次移动窗长的1/4是一个不错的平衡点。这里有一个容易踩的坑重叠率不是越高越好。当重叠率超过90%时相邻窗之间的相关性太强时频图会出现拖尾效应反而让时间定位变得模糊。而且计算量会急剧增加对于长时间数据可能无法接受。3. 用Python实现STFT从原始数据到可读时频图3.1 数据准备与预处理在动手写STFT代码之前有几个预处理步骤不能省。第一步是去均值振动信号中往往包含直流偏置或趋势项不去掉的话会在时频图的0 Hz附近形成一条很强的能量带掩盖低频信息。用scipy.signal.detrend或者直接减去均值都可以。第二步是检查采样率。STFT的结果中频率轴的范围是0到采样率的一半奈奎斯特频率。如果采样率设置错误整个频率轴都是错的。我遇到过有人把采样率写成1000 Hz实际采集是5120 Hz结果时频图上的频率全部压缩了5倍多故障特征频率完全对不上。第三步是数据长度的考虑。STFT的输出是一个二维矩阵行数对应频率点数列数对应时间帧数。如果数据太长比如几小时的连续采集直接做STFT会得到一个巨大的矩阵内存可能扛不住。这时候需要分段处理或者先做降采样。import numpy as np from scipy import signal import matplotlib.pyplot as plt # 模拟一段振动信号50Hz基频 200Hz冲击成分仅在0.5-1.0秒出现 fs 5120 # 采样率 t np.arange(0, 2, 1/fs) # 2秒数据 f_base 50 # 基频 f_impact 200 # 冲击频率 # 基频信号 x 1.0 * np.sin(2 * np.pi * f_base * t) # 冲击成分在0.5-1.0秒之间加入200Hz成分 impact_mask (t 0.5) (t 1.0) x[impact_mask] 0.5 * np.sin(2 * np.pi * f_impact * t[impact_mask]) # 加入少量噪声 x 0.1 * np.random.randn(len(t)) # 去均值 x x - np.mean(x)3.2 调用scipy.signal.stft的正确姿势scipy.signal.stft是SciPy 1.0之后引入的STFT函数比旧版的spectrogram接口更清晰。它的核心参数包括window窗函数、nperseg每段长度即窗长、noverlap重叠点数、nfftFFT点数、fs采样率。这里有一个关键点nperseg和nfft的关系。nperseg是实际截取的信号长度nfft是FFT的点数。如果nfft大于nperseg信号会在末尾补零相当于对频谱做插值频率轴上的点会更密但不会提高实际频率分辨率。如果nfft小于nperseg信号会被截断信息会丢失。通常设nfft等于nperseg或者略大比如取2的整数次幂方便计算。# STFT参数 nperseg 512 # 窗长 noverlap 384 # 重叠点数75%重叠 nfft 512 # FFT点数 f, t_stft, Zxx signal.stft( x, fsfs, windowhann, npersegnperseg, noverlapnoverlap, nfftnfft, boundaryNone, # 不补边界避免时间轴偏移 paddedFalse # 不额外补零 ) # Zxx是复数矩阵取模得到幅值 magnitude np.abs(Zxx)3.3 时频图的可视化技巧拿到magnitude矩阵后直接plt.pcolormesh出来的图往往不好看因为振动信号的动态范围很大强成分和弱成分可能差几个数量级。这时候需要对幅值做对数变换用分贝dB表示。# 转换为dB参考值为最大值 magnitude_db 20 * np.log10(magnitude / np.max(magnitude) 1e-12) # 绘制时频图 plt.figure(figsize(12, 6)) plt.pcolormesh(t_stft, f, magnitude_db, shadinggouraud, cmapjet) plt.ylabel(频率 (Hz)) plt.xlabel(时间 (s)) plt.title(STFT时频图) plt.colorbar(label幅值 (dB)) plt.ylim(0, 500) # 只显示0-500Hz范围 plt.tight_layout() plt.show()这里有几个可视化上的经验。第一cmap的选择很重要jet虽然被很多人批评但在时频图上它的对比度确实好能清晰看出能量分布。如果要做正式报告可以用viridis或inferno更符合视觉感知均匀性。第二shadinggouraud能让图像更平滑但计算量稍大如果数据量大用shadingauto或nearest。第三ylim一定要设因为振动信号的高频部分往往能量很低全频段显示会让低频区域的细节被压缩。还有一个技巧如果信号中有很强的单一频率成分比如50 Hz基频它会在时频图上形成一条贯穿始终的亮线把其他弱成分掩盖。这时候可以在做STFT之前先做一次带阻滤波把强主频滤掉再看时频图弱成分会清晰很多。这个操作在轴承故障诊断中特别有用因为轴承故障特征频率往往在几千赫兹能量比基频低几十个dB。4. 时频图读图实战从图案反推设备状态4.1 稳态信号的时频图特征先看最简单的情况设备稳定运行振动信号由几个固定的频率成分组成。这时候时频图上的表现是几条水平的亮线每条线对应一个频率成分线的粗细反映该成分的频率稳定度。如果线很细很直说明频率非常稳定如果线有轻微的抖动可能是转速有波动。这种时频图看起来平淡但信息量不小。水平线的数量告诉你有多少个主要频率成分线的位置告诉你频率值线的亮度告诉你幅值大小。如果某条线的亮度随时间有周期性变化那说明这个频率成分被调制了调制的周期可以从亮度变化的周期读出来。4.2 冲击成分的识别冲击在时频图上的表现非常有辨识度一条垂直的宽带能量线。因为冲击在时域上是一个短促的脉冲在频域上覆盖很宽的频率范围所以在时频图上表现为某个时刻、从低频到高频都有能量。轴承故障的冲击是周期性的所以时频图上会看到一系列等间隔的垂直亮线。间隔时间就是冲击的周期它的倒数就是冲击的重复频率。对于轴承外圈故障这个重复频率就是外圈故障特征频率BPFO内圈故障对应BPFI滚动体故障对应BSF。这些特征频率可以通过轴承几何参数和转速计算出来和时频图上测到的间隔对比就能判断是哪个部件出了问题。这里有一个实操中的坑冲击的间隔在时频图上不一定能直接测准。因为窗长有限如果冲击间隔小于窗长相邻冲击会在同一个窗内混叠时频图上分不开。这时候需要减小窗长提高时间分辨率。但窗长减小又会导致频率分辨率下降可能看不清冲击的宽带特征。这个矛盾需要根据具体信号来平衡。4.3 变速过程的时频图解读变速工况是STFT最擅长的场景。电机启动时转速从0逐渐升高振动信号中的基频对应转频也会从0逐渐升高。在时频图上这表现为一条从低频向高频倾斜的亮线。如果设备中有齿轮齿轮啮合频率是转频的齿数倍那么时频图上会看到多条平行的倾斜亮线它们的斜率相同但频率值成比例。变速过程中还有一个重要现象频率模糊。当转速变化很快时在窗长内频率已经发生了显著变化FFT的结果会把这段频率变化平均掉导致时频图上的亮线变粗甚至分裂。解决方法是减小窗长让窗内频率变化足够小。但窗长减小又会影响频率分辨率所以需要在变速速率和频率分辨率之间做权衡。一个实用的判断标准是窗长内频率变化量应小于频率分辨率。假设频率分辨率是Δf频率变化率是df/dt那么窗长T应满足T Δf / (df/dt)。比如Δf10 Hzdf/dt100 Hz/s那么T 0.1秒对应512个采样点采样率5120 Hz。5. 那些让我熬夜调试的STFT参数坑5.1 边界效应导致的时间轴偏移scipy.signal.stft默认的boundaryzeros会在信号两端补零这会导致时频图的时间轴比原始信号长。如果你用t_stft去对齐其他传感器数据会发现对不上。我第一次遇到这个问题时排查了半天以为是采样率设置错了后来才发现是边界补零导致的。解决方法很简单设boundaryNone这样STFT只对信号实际存在的部分做分析时间轴和原始信号一致。但代价是信号两端的窗可能不完整时频图两端的数据会少一些。如果信号很长这点损失可以忽略如果信号很短就需要考虑是否接受这个损失。5.2 幅值标定的陷阱STFT输出的幅值和原始信号的幅值之间有一个比例关系这个比例取决于窗函数和窗长。如果你直接用np.abs(Zxx)作为幅值得到的数值和实际振动幅值比如加速度的m/s²是对不上的。要做定量分析必须做幅值标定。标定的方法是用一个已知幅值的正弦信号做STFT看输出的幅值是多少然后计算比例系数。对于汉宁窗这个系数大约是窗长的一半因为汉宁窗的相干增益是0.5。但更可靠的做法是实际标定因为不同实现可能有细微差异。我在做振动烈度评价时踩过这个坑直接用STFT的幅值去算RMS结果比实际值小了将近一半。后来发现是窗函数的相干增益没有补偿。补偿之后STFT算出来的RMS和直接对时域信号算的RMS就对上了。5.3 频率轴的范围与混叠STFT的频率轴范围是0到fs/2。如果信号中有高于fs/2的频率成分它们会混叠到低频区域在时频图上表现为虚假的低频成分。这个问题在振动信号中很常见因为加速度传感器的高频响应很好而很多故障特征频率都在几千赫兹。避免混叠的方法是在采集时加抗混叠滤波器把高于fs/2的频率成分滤掉。如果数据已经采集了发现时频图上有不明低频成分可以检查一下是不是高频混叠。一个简单的判断方法是改变采样率重新采集如果那个低频成分的位置随采样率变化那基本可以确定是混叠。5.4 大数据量下的内存与速度优化处理长时间振动数据时STFT的计算量和内存占用是必须考虑的问题。假设采样率5120 Hz采集1小时数据量是1843万个点。如果窗长512重叠384那么时间帧数大约是1843万/(512-384)≈14.4万帧频率点数256STFT矩阵的大小是256×14.4万≈3700万个复数每个复数16字节总共约590 MB。这还只是一小时的数据如果是多通道或者多天内存很快就爆了。优化方法有几个。第一分段处理每次处理几分钟的数据分析完保存结果再处理下一段。第二降采样如果只关心低频成分可以先降采样再STFT数据量能减少一个数量级。第三用scipy.signal.stft的return_onesidedTrue默认只返回正频率部分数据量减半。第四如果只需要时频图的图像而不需要原始矩阵可以在计算完每个时间帧后立即转换为dB并存储为低精度格式如float16内存占用能减少一半以上。6. STFT之外什么时候该考虑其他时频方法STFT不是万能的。它的核心限制是窗长固定一旦选定时间分辨率和频率分辨率就固定了。但实际信号中不同时间段的特征可能需要在不同的分辨率下观察。比如变速过程中低频段的频率变化慢需要长窗来分辨高频段的频率变化快需要短窗来定位。固定窗长的STFT无法同时满足。这时候可以考虑小波变换CWT。小波变换用可变的窗长低频用长窗高频用短窗天然适合分析变速信号和瞬态信号。但小波变换的缺点是频率轴不是线性的而是对数的对于需要线性频率轴的振动分析解读起来不如STFT直观。另一种方法是Wigner-Ville分布WVD它的时频聚集性比STFT好得多但会有交叉项干扰对于多成分信号交叉项会形成虚假的能量分布需要做平滑处理如平滑伪Wigner-Ville分布。我的建议是先用STFT如果发现分辨率不够再考虑其他方法。STFT的物理意义最清晰参数最容易理解结果最容易解释。在大多数旋转机械故障诊断场景中STFT已经足够。只有在分析非平稳性极强的信号如冲击响应、变速扫频时才需要考虑小波或其他方法。还有一个折中方案用多分辨率STFT。对同一段信号用不同的窗长分别做STFT然后对比不同分辨率下的时频图。短窗看时间细节长窗看频率细节结合起来能获得更全面的信息。这个方法在工程实践中很实用虽然计算量翻倍但避免了选择单一窗长的纠结。7. 把STFT嵌入实际诊断流程的几个建议7.1 和FFT配合使用STFT不应该完全替代FFT。正确的流程是先用FFT做全局频谱分析确定主要的频率成分和关注频段然后用STFT在关注频段做时频分析看这些成分的时间行为。FFT负责有什么STFT负责什么时候有。两者配合效率最高。比如分析一台齿轮箱的振动先用FFT找到啮合频率和边带的位置然后针对啮合频率附近做STFT看边带是否随时间周期性出现。如果边带在时频图上表现为周期性的能量跳动那基本可以确认是齿轮故障。7.2 特征频率的计算与标注做STFT之前先把可能的故障特征频率算出来然后在时频图上标注这些频率的水平线。这样读图时能快速判断哪些能量带对应哪些故障。特征频率的计算需要轴承几何参数节径、滚珠直径、接触角、滚珠数和转速。如果这些参数拿不到可以用经验公式估算或者直接观察时频图上的周期性模式来反推。标注的方法是在时频图上画水平虚线用plt.axhline颜色选对比度高的比如白色或黑色线型用虚线避免遮挡时频图本身的能量分布。7.3 批量处理与自动化实际项目中往往有几十上百个测点的数据要分析手动一个个做STFT不现实。需要写批处理脚本自动读取数据文件、做STFT、生成时频图、保存结果。关键是要把参数配置化不同测点可能用不同的窗长和频率范围这些参数放在配置文件里脚本读取配置后自动处理。自动化还有一个好处是一致性。手动分析时不同时间、不同人做的STFT参数可能不一样结果没法对比。自动化保证所有数据用同一套参数处理结果可比。7.4 结果存档与追溯时频图生成后除了图片建议把STFT的矩阵也保存下来可以用np.save存为npy文件或者用HDF5格式。这样后续如果需要重新分析或者用不同的可视化方式不需要重新计算。同时记录下STFT的所有参数窗函数、窗长、重叠、采样率、时间范围这些元数据对于结果的可追溯性至关重要。我在一个项目中因为没有保存STFT参数三个月后需要复现一张时频图时完全想不起来当时用的什么窗长和重叠。只能重新试参数浪费了大半天时间。从那以后我养成了一个习惯每次生成时频图都在图片文件名里嵌入关键参数比如stft_hann_512_75_5120.png一看文件名就知道参数。7.5 常见问题的快速排查清单最后整理一个STFT分析中常见问题的排查清单遇到问题时可以按这个顺序检查现象可能原因排查方法时频图全黑或全白幅值未做对数变换检查是否转换为dB频率轴数值不对采样率设置错误核对采集设备的实际采样率时间轴对不上boundary参数导致设boundaryNone低频有虚假亮带直流偏置未去除做去均值或去趋势高频有虚假成分频率混叠检查抗混叠滤波器时频图模糊窗长或重叠不合适调整nperseg和noverlap幅值偏小窗函数增益未补偿做幅值标定计算太慢数据量太大分段处理或降采样这个清单基本覆盖了我这些年遇到的大部分STFT相关问题。每次调试时按这个顺序过一遍大部分问题都能快速定位。STFT是一个看起来简单但细节很多的工具。窗函数、窗长、重叠率、边界处理、幅值标定每一个环节都有坑。但一旦把这些细节都摸清楚了它就成了振动分析中最得力的工具之一。我现在的习惯是拿到任何一段非平稳振动信号第一件事就是做STFT看时频图先对信号的整体行为有个直观认识然后再决定下一步用什么方法深入分析。这个习惯帮我避免了很多盲人摸象式的分析错误。