ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

轴承故障诊断中的STFT窗长与CWT尺度参数选择

轴承故障诊断中的STFT窗长与CWT尺度参数选择

简介:这份文档围绕凯斯西储大学(CWRU)轴承故障数据集展开信号时频分析,面向从事旋转机械故障诊断、健康监测与振动信号处理的学习者和研究人员,帮助其理解如何从实测振动数据中提取可用于故障辨识的时频特征。内容先说明实验台组成与驱动端、风扇端、基座三处加速度计数据的差异,再以正常信号和0.021英寸内圈、滚珠、外圈故障信号为对象,比较短时傅里叶变换在重叠比例0.5、尺度16/32/64下的时频图表现,并给出选择尺度32的依据;连续小波变换部分则对比morl、cmor1-1、cmor1.5-2、cgau8等小波函数,在尺度128下筛选出cmor1.5-2,并进一步讨论32、64、128、256尺度对高低频特征的取舍。文档还附有Python读取mat数据、绘制原始信号与STFT时频图的代码片段,便于对照复现和调整参数。资源包为1个docx文件,大小约1.05MB,已有1584人学习,适合作为故障诊断入门与实验参数选择的参考材料。

1. 一张时频图能分辨什么:轴承故障数据集里被忽略的 85 毫秒

很多人拿到某高校公开的轴承故障数据集,第一反应是把0_0.mat和21_1.mat读进来,跑一行stft,撒一把 jet 色图,然后在四个子图之间找差别。真正决定这张图能不能分辨内圈、滚珠、外圈的,不是用 STFT 还是 CWT,而是窗口长度与故障冲击节拍之间的比例。数据集里一个 0.021 英寸(约 0.53 毫米)的缺陷,在 30 Hz 上下的轴转频下会激出周期仅几毫秒的冲击串;窗口一旦长过几个冲击周期,冲击就被平均成一条平滑亮带,故障之间的差别随之消失。反过来,窗口短到能咬住每一次冲击,频率轴就粗到看不清特征频率。这篇把窗长、尺度、小波函数这三层参数各自在"开"什么价讲清楚,也讲清那份时频分析文档里的参数是怎么被一步步逼出来的。

2. STFT 的窗长:为什么"分辨率"这个词在这份数据集上会骗人

2.1 短时傅里叶变换在切的到底是什么

短时傅里叶变换的做法可以一句话说完:拿一个中心位于时间 α 的窗 g(t−α) 在信号上滑动,在每个位置只对窗内那一小段做傅里叶变换。窗长固定,窗的形状固定,于是"用多长的尺子去量信号"这件事被一次性钉死了。

钉死的代价就是不确定性原理:时域窗越窄,频域主瓣越宽,两者乘积有下界。落到代码里,这个下界表现为两个可以直接算出来的量——频率分辨率 Δf = fs / nperseg,和时间分辨率 Δt = nperseg / fs。它们不是两个独立可调的旋钮,而是同一根杠杆的两端,动一个必然动另一个。

这份数据集的驱动端加速度计采样率为 12 kHz,单次分析截取 1024 点,约 85.3 毫秒。85 毫秒里发生了什么,值得先算清楚:轴转频约 30 Hz,一个转频周期 33 毫秒,1024 点大约覆盖 2.5 个转频周期;内圈故障特征频率的经验系数是 5.4152 倍转频,约 162 Hz,对应冲击重复周期 6.2 毫秒,85 毫秒里大约能装下 13 到 14 次冲击;外圈系数 3.5848 倍,约 107 Hz,周期 9.3 毫秒;滚珠系数 4.7135 倍,约 141 Hz,周期 7.1 毫秒。这三个周期都落在 6 到 10 毫秒这个区间,而窗长的候选值从 2.67 毫秒一路跨到 21.3 毫秒,正好横跨"能分辨冲击"和"糊掉冲击"的分界线。

2.2 窗长 16、32、64、256 的账:Δf、Δt 与冲击节拍

把四个候选窗长的账摆在一起,很多结论就不用猜了。

npersegΔf = fs/npersegΔt = nperseg/fs帧移(重叠 50%)频点个数(0~6 kHz)
16750 Hz1.33 ms0.67 ms9
32375 Hz2.67 ms1.33 ms17
64187.5 Hz5.33 ms2.67 ms33
25646.875 Hz21.33 ms10.67 ms129

窗长 16 时只剩 9 个频点,整张图几乎是空的,谈不上"分辨率高低",是信息量不够。窗长 256 的频率分辨率最好,46.875 Hz,理论上能把 162 Hz 和 107 Hz 的谱线分开——但它的时间窗是 21.33 毫秒,一个窗里塞进 3 个以上的内圈冲击周期,冲击串被卷积成一团,时频图上只剩一条持续亮带,四种状态长得越来越像。

窗长 32 的处境刚好相反:375 Hz 的频率分辨率连 162 Hz 与 107 Hz 都分不开,可它的时间窗 2.67 毫秒,比任何一个故障冲击周期都短,每一次冲击都能被单独框住,图上呈现的是一串等间隔的竖条,间隔 6.2 毫秒还是 9.3 毫秒一眼可辨。

注意:这里有一个容易想反的地方。窗长 32 之所以在辨识度上胜出,靠的不是频率分辨率,而是它抓住的是时域冲击节拍,不是频域谱线位置。想看清特征频率本身,靠 STFT 图本来就不现实,得走包络谱那条路。

我一般会这么理解这个过程:STFT 在这份数据集上实际充当的是一个"冲击节奏计",它回答"每隔多久被敲一下",至于"敲在哪个频率上",那是另一套工具的事。把这个前提想清楚,参数的取舍就不再是玄学,而是一道有明确约束的算术题。

2.3 重叠 0.5 与帧移:为什么不是 0.9

重叠比例决定帧移,帧移决定时间轴上的采样密度。重叠 0.5 意味着帧移等于窗长的一半,窗长 32 时帧移 1.33 毫秒,1024 点能得到约 64 帧。这个密度够用:冲击周期 6.2 毫秒,每 1.33 毫秒一帧,一个冲击周期里有 4 到 5 帧,冲击的起落能被描出来。

重叠拉到 0.9,帧数涨到 5 倍,图更平滑,但每一帧用的还是同样长度的窗,信息量并没有增加,多出来的只是插值密度,计算量却翻了五倍。更麻烦的是重叠过高会让相邻帧高度相关,时频图上出现横向的拖影,看着"连续",实际是重复计数的假象。重叠降到 0,帧移等于窗长,冲击很可能正好落在两帧的交界处,被两个窗各切一半,幅度塌下去一半,反而看不清。

重叠 0.5 是这份分析的合理落点,它让帧移小于最短的冲击周期,又不至于制造冗余。

import numpy as np from scipy.io import loadmat from scipy.signal import stft FS = 12000 # 驱动端加速度计采样率 N = 1024 # 单次分析窗长,1024 点 ≈ 85.3 ms def load_de(path, key): """CWRU 风格的 mat 文件读出来是 dict,键名随文件变化,必须显式指定""" return loadmat(path)[key].reshape(-1)[:N] x = load_de('21_1.mat', 'X209_DE_time') # 0.021 英寸内圈 for nperseg in (16, 32, 64, 256): noverlap = nperseg // 2 # 重叠 50% → 帧移 = nperseg/2 f, t, Z = stft(x, fs=FS, nperseg=nperseg, noverlap=noverlap, window='hann', boundary=None, padded=False) df = FS / nperseg # 频率分辨率 dt = nperseg / FS # 时间分辨率(单帧覆盖时长) print(f'nperseg={nperseg:3d} df={df:7.2f} Hz dt={dt*1e3:6.2f} ms bins={len(f)}')

这段代码只做一件事:把窗长扫描的代价量化出来。nperseg是每次傅里叶变换的点数,直接决定df;noverlap只影响时间轴上帧的密度,不影响df和dt;boundary=None, padded=False关掉 scipy 默认的两端补零,避免时间轴被悄悄延长半个窗。跑完打印出来的四行数字,就是上面那张表的来源,也是判断"某个窗长到底行不行"的第一手依据,不用靠看图猜。

3. 连续小波变换:cparam = 2·fc·totalscal 这行为什么决定了尺度可比性

3.1 尺度-平移坐标与时间-频率坐标的换算

连续小波变换把 STFT 里"固定窗长、滑动位置"的两参数结构,换成了"伸缩尺度、平移位置"。小波函数 ψ(t) 被拉伸成 ψ((t−b)/a),a 是尺度,b 是平移。尺度 a 变大的时候,小波在时域被拉长、在频域被压窄并整体往低频挪,于是一个参数同时改变了时间窗和频率窗——STFT 里那根固定的杠杆,在这里变成了随频率自适应移动的杠杆。

代价是坐标变了。小波变换天然活在"尺度-平移"平面上,画图时要换回"频率-时间"平面,换算关系是 f = fc / (a · dt),fc 是小波的中心频率,dt 是采样间隔。这个式子看起来平淡,却是后面所有坑的源头:尺度 a 本身没有物理意义,只有乘上 fc 和 dt 之后才有赫兹。

尺度与频率成反比这件事,直接推出小波变换的双尺度性质。要关注高频特征,选小尺度,小波被压短,时间定位准;要关注低频特征,选大尺度,小波被拉长,频率定位准。原文里那句"尺度长度越大,对低频特征关注越多",说的正是这个方向性,但如果只停在这句定性描述上,调参还是靠手感。

3.2 Morlet 的时频盒:cmor 为什么能给出平滑包络

Morlet 小波是一个复指数乘高斯包络,写成 ψ(t) = exp(−t²/2)·exp(jω₀t)。高斯包络保证时域局部化,复指数保证频域是单峰。这两条性质合起来,使它成为振动信号分析里最常用的小波之一——振动是准周期的冲击串,既需要时间定位,也需要频率定位。

复数这个细节值得单独说。实小波(比如实 Morlet、墨西哥帽)的频谱关于零频对称,正负频率各占一半,对实信号做变换后取模,正负频率的响应会叠在一起互相干涉,幅值图上出现细密的振荡纹路。复小波是解析小波,频谱只在正频率一侧有支撑,负频部分几乎为零,取模得到的是干净的包络,不会自干涉。这就是为什么同样做 CWT,cmor系列的幅值图看起来比morl干净,冲击的位置和强度更直观。

PyWavelets 里复数 Morlet 的参数写成cmorB-C,B 是带宽参数,C 是中心频率。cmor1.5-2意味着带宽 1.5、中心频率 2;cmor1-1意味着带宽 1、中心频率 1。带宽参数控制高斯包络在频域的宽窄,B 越大,频域越宽、时域越短,频率选择性下降但时间定位变好;中心频率 C 决定小波的主频位置。cgau8是 8 阶复数高斯小波,它是高斯函数取 8 阶导数,没有单独的带宽参数,阶数越高频率选择性越好、时域支撑越长。

3.3 cparam = 2·fc·totalscal 这行代码在做什么

原文的尺度序列是这样生成的:

cparam = 2 * fc * totalscal scales = cparam / np.arange(totalscal, 0, -1)

np.arange(totalscal, 0, -1)产生从 totalscal 递减到 1 的整数序列,除法之后 scales 从大到小排列,最小值是 cparam/totalscal = 2·fc,最大值是 cparam = 2·fc·totalscal。代入频率换算 f = fc/(a·dt):

  • 最小尺度 2·fc 对应最高频率 f_max = fc / (2·fc·dt) = 1/(2·dt) = fs/2,正好是奈奎斯特频率;
  • 最大尺度 2·fc·totalscal 对应最低频率 f_min = fc / (2·fc·totalscal·dt) = fs / (2·totalscal)。

两个结果里 fc 都被约掉了。这意味着无论选cmor1-1、cmor1.5-2还是cgau8,只要各自的cparam用同一个2*fc*totalscal算,频率覆盖区间就完全相同,恒为 [fs/(2·totalscal), fs/2]。12 kHz 采样、totalscal=128 时,区间是 [46.875, 6000] Hz。

import numpy as np, pywt FS, totalscal = 12000, 128 dt = 1.0 / FS for name in ('cgau8', 'morl', 'cmor1-1', 'cmor1.5-2'): fc = pywt.central_frequency(name) # 数值积分得到的中心频率 cparam = 2 * fc * totalscal scales = cparam / np.arange(totalscal, 0, -1) freqs = fc / (scales * dt) # 尺度 → 频率 df = np.abs(np.diff(freqs)) print(f'{name:11s} fc={fc:6.3f} scales=[{scales.min():7.2f},{scales.max():7.2f}] ' f'f=[{freqs.min():7.2f},{freqs.max():7.2f}] Hz ' f'相邻尺度频率差: 低端 {df.min():.3f} Hz / 高端 {df.max():.1f} Hz')

跑一遍会看到两件事。第一,四种小波打印出的f=[46.88, 6000.00]完全一致,fc列各不相同但都被cparam吸收掉了,这正是这行代码存在的目的:把中心频率这个自由参数从频率覆盖范围里剔除,让"换小波"只改变时频盒形状,不改变分析的频段,比较才公平。第二,df在低频端只有零点几赫兹,在高频端上千赫兹——尺度是等间隔的,频率是 1/a 关系,所以频点在低频密集、高频稀疏。这是 CWT 的固有特性,也是它相比 STFT 更适合看低频故障特征的原因。

提示:scales必须保持从大到小,对应的freqs才会从小到大单调递增。contourf的 y 轴依赖这个单调性,顺序反了图会上下颠倒,低频跑到顶部,看起来"和别人的结果不一样"。

4. 小波函数与尺度实测:cgau8、morl、cmor1-1、cmor1.5-2 差在哪

4.1 复小波、实小波与辨识度的关系

原文在尺度固定为 128 的前提下横向比了四种小波,结论是cgau8和cmor1.5-2对不同故障的辨识度较高。这个结论背后的机制可以从三个角度拆。

第一个是负频泄漏。morl在 PyWavelets 里是实 Morlet,频谱对称,取模后有自干涉纹路,冲击之间的低能量区被填上振荡条纹;三种复小波没有这个问题,冲击与冲击之间的背景更干净,对比度更高。

第二个是时频盒形状。cgau8因为是 8 阶高斯导数,时域支撑长、频域尖,对窄带成分敏感,冲击串出现时能量集中得很紧;cmor1.5-2的带宽 1.5 比cmor1-1的 1 更宽,时域更短,对每次冲击的起落描得更细,代价是频率方向的涂抹多一点。在这份数据上,冲击节拍是主要判别信息,时间定位更值钱,所以cmor1.5-2的宽带宽反而成了优点。

第三个是参数一致性。cgau8没有可调的带宽参数,它的时频盒形状是被阶数锁死的;cmor系列可以通过 B 和 C 调形状,这意味着cmor的结论能往别的工况迁移,cgau8的结论更依赖当前数据的冲击形态。

综合下来选cmor1.5-2,不是因为它在某张图上最漂亮,而是因为它同时满足"复小波无负频泄漏"和"时频盒可调"这两条可迁移的性质。

4.2 totalscal 从 32 到 256:尺度长度变大到底改变了什么

原文比较了 totalscal 取 32、64、128、256 时滚珠故障的时频图,观察到尺度越长对低频特征关注越多。用上一节的公式可以把这个观察量化:f_min = fs/(2·totalscal),f_max 恒为 fs/2。

totalscalf_minf_max低频端相邻尺度频率差尺度序列长度
32187.5 Hz6000 Hz约 0.36 Hz32
6493.75 Hz6000 Hz约 0.18 Hz64
12846.875 Hz6000 Hz约 0.09 Hz128
25623.4 Hz6000 Hz约 0.05 Hz256

两件事同时发生:频率下限往下探,同一频段的划分变密。totalscal 从 128 加到 256,下限从 46.875 Hz 降到 23.4 Hz,180 Hz 以下的低频区从十几个尺度变成三十几个尺度,图上低频块的分层明显更细。但高频端一点也不受益,6000 Hz 附近的尺度间隔几乎没变,还是稀疏的。

这解释了图上"底部低频的区别"最明显。同时也提醒一件事:totalscal 不是越大越好。总尺度数翻倍,计算量翻倍,而新增的分辨率全部堆在低频。如果关心的频段在 1 kHz 以上,把 totalscal 加到 256 只是白白多跑一倍时间。

4.3 从 loadmat 到 contourf 的完整链路

import numpy as np, pywt import matplotlib.pyplot as plt from scipy.io import loadmat FS, N = 12000, 1024 dt = 1.0 / FS def cwt_map(path, key, wname, totalscal): x = loadmat(path)[key].reshape(-1)[:N] fc = pywt.central_frequency(wname) cparam = 2 * fc * totalscal scales = cparam / np.arange(totalscal, 0, -1) # 大到小 → 频率低到高 coef, freqs = pywt.cwt(x, scales, wname, sampling_period=dt) amp = np.abs(coef) # 复小波取模 → 包络 db = 20 * np.log10(amp / amp.max() + 1e-12) # 归一化 dB,压掉量级差 return freqs, db fig, axes = plt.subplots(1, 4, figsize=(20, 5), sharey=True) for ax, ts in zip(axes, (32, 64, 128, 256)): freqs, db = cwt_map('21_2.mat', 'X222_DE_time', 'cmor1.5-2', ts) t = np.arange(N) * dt edge = N // 8 # 掐掉两端边界效应 ax.contourf(t[edge:-edge], freqs, db[:, edge:-edge], levels=30, cmap='jet', vmin=-40, vmax=0) ax.set_title(f'totalscal={ts}') ax.set_xlabel('t / s') axes[0].set_ylabel('f / Hz') plt.show()

几个参数的作用需要交代清楚。sampling_period=dt是pywt.cwt换算频率轴必需的,漏掉它默认按 1 秒采样处理,频率轴整体错 12000 倍。np.abs(coef)取模是针对复小波的操作,实小波取模意义不大。20*log10归一化很关键——原始幅值的动态范围常常跨越三四个数量级,线性色标下只有最强的那几个点有颜色,其余全被压成背景色,看起来"没有特征",实际是显示问题。vmin=-40, vmax=0把显示窗口固定住,四张图才能横向比较,否则每张图各自归一化,亮度的相对关系就丢了。edge = N // 8裁掉两端,因为边界处小波支撑越界,会出现两条亮的竖边,不裁掉会误以为是故障冲击。

5. 避坑与排查:五类把时频图做废的现场

5.1 坐标轴类:时间轴和频率轴最容易一起错

第一种情况:时间轴长度对不上,或者刻度的物理含义完全错。现象是横轴标着秒,但从 0 到 1024,看不出任何冲击间隔。原因是把窗长当成了采样率——原文代码里sampling_rate = 1024,紧接着t = np.linspace(0, 1.0/sampling_rate, sampling_rate),这个 1024 是单次分析的点数,不是每秒的采样点数。12 kHz 采样下 1024 点只覆盖 85.3 毫秒,时间轴应该是 0 到 0.0853 秒。解决方法是单独定义一个FS = 12000,时间轴用np.arange(N) / FS,别让窗长和采样率共用一个变量名。

第二种情况:CWT 图上下颠倒,低频跑到顶部。现象是和别人同样的参数,图却像镜像。原因是scales没有按从大到小排列,pywt.cwt返回的freqs随之从大到小,contourf按数组顺序铺 y 轴,图就翻了。解决方法是坚持用cparam / np.arange(totalscal, 0, -1)生成尺度,画图前打印一下freqs[:3]和freqs[-3:],确认单调递增。

5.2 参数可比性类:换个小波,尺度就不是原来的尺度

第三种情况:跑morl那一组时直接抛NameError: name 'fc2' is not defined。现象是前三组正常,第四组或者第二组一运行就崩。原因是fc2 = pywt.central_frequency(wavename2)那一行被注释掉了,但下一行cparam2 = 2 * fc2 * totalscal还在用这个变量。照抄代码时最容易踩这个坑,而且报错位置和真正的问题不在同一行。解决方法是把注释行放开,或者在循环里统一用fc = pywt.central_frequency(name)现算,从结构上消灭fc1、fc2、fc3、fc4这种容易错位的命名。

第四种情况:直接把scales = np.arange(1, 129)喂给pywt.cwt,然后拿不同小波的结果横向比。现象是每种小波的图频率范围都不一样,cmor1.5-2的图明显比cmor1-1更"高",没法比。原因是没有用cparam = 2*fc*totalscal做归一化,f = fc/(a·dt)里的fc没有被吸收,中心频率 2 的小波频率整体高一倍。解决方法是统一走cparam那条路,让四种小波的频率区间都落在 [fs/(2·totalscal), fs/2]。

还有一个相邻的概念坑:cmor1.5-2里的 1.5 是带宽参数、2 是中心频率,顺序是cmorB-C。把它读成"中心频率 1.5"会导致后面算cparam时对不上,尺度序列整体偏一截。写完参数名顺手print(pywt.central_frequency('cmor1.5-2'))验一下,返回值应该接近 2。

5.3 观感失真:图好看不等于信号对

第五种情况:时频图很"干净",但和原始波形对不上时间。现象是图上能量块的位置比实际冲击晚了一截,或者两端莫名出现亮条。原因有两个叠加:scipy.signal.stft默认boundary='zeros',会在信号两端各补半个窗长的零,时间轴被悄悄拉长;同时默认返回的是复数谱,直接np.abs画图没做对数压缩,弱成分全被强成分盖住。解决方法是显式写boundary=None, padded=False,画图前做20*log10(amp/amp.max())并固定vmin/vmax。

顺带一个显示层的坑:pcolormesh(..., shading='gouraud')会对网格做插值平滑,图看起来更漂亮,但短时冲击那几帧会被抹圆,冲击间隔这种关键信息反而模糊。排查阶段用shading='auto'或'nearest',等参数定下来再考虑出图样式。中文标题也一样,matplotlib.rc("font", family='Microsoft YaHei')在缺少该字体的机器上会退化成方块和警告,换成系统里实际存在的字体名,或者干脆用英文标题。

6. 取舍:判据先于图像,参数先于结论

回过头看这份时频分析里那几个被选中的数字——STFT 窗长 32、CWT 尺度 128、小波cmor1.5-2——它们都不是"看起来最好"挑出来的,而是各自被一个约束条件逼出来的。窗长 32 是时间窗必须短于 6 到 10 毫秒的冲击周期,同时频点不能少到没法成像;totalscal 128 是频率下限要压到 50 Hz 以下、又不愿意为低频多付一倍计算;cmor1.5-2是复小波消除负频泄漏、带宽可调保证结论可迁移这两条同时成立。每条都对应一个能在纸上算出来的不等式,而不是一句"效果更好"。

这也是我看这类分析文档时最先找的东西:作者有没有把判据写出来。判据在,参数就能复现,换一份工况数据也能照着重推一遍;判据不在,参数就成了一组玄学数字,换台设备立刻失效。

有一个边界值得说清楚。这份分析用的是 STFT 加 CWT 的时频图路线,它能回答"冲击多久来一次、能量集中在哪个时间段",但回答不了"特征频率到底是多少赫兹"。原因在前面算过:窗长 32 的频率分辨率 375 Hz,比内圈特征频率 162 Hz 还大,谱线上根本立不住。真要定位特征频率,走的是包络解调那条路——Hilbert 变换取包络、去直流、再做 FFT,在包络谱上找 162 Hz、107 Hz、141 Hz 这几根谱线及其转频边带。时频图和包络谱是互补的两件事,别指望一张图全包。

我早年做这类分析时踩过一次典型的坑:为了让时频图"更好看",把窗长一路加到 256,图确实细腻了,故障之间的差别也没了,我还以为是数据集本身的问题,翻来覆去换小波、换尺度,折腾了两天才想起来回去看时间分辨率那一列数字。从那以后我每次调时频参数,都强制先跑一遍"Δf、Δt、冲击周期"这三个数的对照表,写进注释里,再动手画图。参数对不对,纸上就能判,不用等图出来。

这份资料把数据集读取、STFT 窗长扫描、四种小波横向对比、totalscal 尺度对比这几段串成了一条完整链路,代码和参数判断都在里面,拿过去对着自己的数据重跑一遍,那条链路上的每个取舍点都能亲手验一次。希望帮到你。

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

返回列表