简介这份文档面向机械故障诊断与信号处理方向的研究者、研究生及工程技术人员围绕凯斯西储大学CWRU轴承故障数据集展开时频分析实践。内容涵盖数据集实验台构成与驱动端、风扇端、基座三路振动信号的物理含义并系统演示短时傅里叶变换STFT与连续小波变换CWT两条分析路线STFT部分对比正常与0.021英寸内圈、滚珠、外圈故障信号在0.5重叠比例下比较16、32、64三种尺度最终选定尺度32CWT部分则对比morl、cmor1-1、cmor1.5-2、cgau8等小波函数确定cmor1.5-2并进一步比较32至256尺度下的时频表现。资源包为1个docx文档约1.05MB内含完整分析流程与配套Python代码便于读者复现实验、理解时频参数选择依据。目前已有1582人学习适合希望快速上手轴承故障特征提取与分类辨识的读者参考。1. 轴承故障数据集做时频分析为什么只看时域波形会漏掉早期故障很多人第一次拿到凯斯西储大学的轴承故障数据集习惯先画一段振动加速度的时域波形看冲击、看幅值、看 RMS然后下结论说“内圈故障特征明显”。这个流程在故障已经发展到中后期时确实够用但一旦面对早期微弱故障时域波形往往被背景噪声和工频干扰淹没RMS 变化不到几个百分点包络谱里也找不到干净的故障特征频率。问题不在数据而在“只看一个域”。轴承故障的本质是周期性冲击激励系统的高频固有频率冲击重复频率对应内圈、外圈、滚动体的通过频率。时域给的是“有没有冲击”频域给的是“能量落在哪些频率”而时频分析给的是“这些频率在什么时刻出现、持续多久、重复间隔多少”。对于变转速、启停过程、冲击微弱且非平稳的工况时频图几乎是唯一能把故障特征从噪声里“拎出来”的手段。这套数据集之所以经典是因为它把驱动端、风扇端、基座的加速度信号按不同故障直径、不同负载、不同转速分门别类地采了下来采样率常见为 12 kHz 和 48 kHz 两档。对做信号处理、故障诊断、深度学习特征工程的人来说它是一个可以反复验证算法的基准。这篇笔记面向两类人一类是刚接触振动信号、想跑通时频分析最小闭环的新手另一类是做诊断模型、需要判断“该用 STFT 还是 CWT、窗长怎么定、时频图怎么喂给网络”的熟手。下面按“数据怎么读 → 时频方法怎么选 → 参数怎么调 → 坑在哪 → 怎么验证”的顺序讲透。2. 从 .mat 文件到可分析的时频矩阵读取、分段与预处理2.1 数据集的文件组织与命名规律这套数据通常按“故障位置 故障直径 负载 转速”组织成多个 .mat 文件每个文件里保存若干条振动信号通道常见键名类似X123_DE_time、X123_FE_time、X123_BA_time分别对应驱动端、风扇端、基座。故障直径常见有 0.007、0.014、0.021、0.028 英寸几档负载用 0 到 3 马力表示转速随之在 1730 到 1797 rpm 附近变化。正常基线数据也会单独给出。读之前先确认三件事采样率是 12 kHz 还是 48 kHz通道是哪一个信号长度是否够做一次完整的时频变换。很多人拿到文件直接load然后对整段做 STFT结果内存爆掉或者图糊成一片就是因为没先看长度和采样率。import scipy.io as sio import numpy as np # 读取一个 .mat 文件先看里面有哪些键 mat sio.loadmat(bearing_data.mat) keys [k for k in mat.keys() if not k.startswith(__)] print(keys) # 常见做法取驱动端信号转成一维 float sig mat[X123_DE_time].ravel().astype(np.float64) fs 12000 # 根据实际文件确认12k 或 48k print(采样率, fs, 样本数, sig.shape[0], 时长(s), sig.shape[0] / fs)逻辑说明ravel()把列向量压平避免后续 FFT 维度出错astype(np.float64)防止整型溢出。参数上fs必须和采集设置一致用错采样率会让所有频率轴整体缩放故障特征频率对不上这是最隐蔽也最致命的错误之一。2.2 去直流、去趋势与分段策略原始信号往往带一个不小的直流偏置直接做时频分析会在 0 Hz 附近堆一大块能量把低频特征盖住。常见做法是先减去均值再用高通或带通滤波去掉工频和极低频漂移。滤波不是必须的但如果你关注的是高频共振带建议保留 500 Hz 到 5 kHz 这一段。分段策略取决于你的目标。做故障特征提取通常取 1024 或 2048 点一段保证频率分辨率够用做深度学习样本常按 1024 点切、重叠 50%扩增样本量。分段前一定要确认每段里至少包含一个完整的冲击周期否则时频图上看不到重复结构。from scipy import signal # 去直流 sig sig - np.mean(sig) # 带通滤波保留 500-5000 Hz突出共振带 sos signal.butter(4, [500, 5000], btypebandpass, fsfs, outputsos) sig_f signal.sosfiltfilt(sos, sig) # 分段2048 点一段重叠 50% win 2048 step win // 2 segments [sig_f[i:iwin] for i in range(0, len(sig_f)-win, step)] print(段数, len(segments), 每段长度, segments[0].shape)逻辑说明sosfiltfilt做零相位滤波避免相位失真影响冲击时刻定位butter的阶数 4 是经验值太高会振铃太低过渡带太宽。参数上带通范围要结合你的采样率和关注频带调整48 kHz 采样时可以放宽到 10 kHz 以上。分段长度 2048 在 12 kHz 下对应约 170 ms频率分辨率约 5.86 Hz足够分辨常见的故障特征频率间隔。2.3 时频分析前的三个检查项动手做 STFT 或 CWT 之前先做三个检查信号是否还有明显趋势项、是否存在削顶或异常尖峰、分段边界是否切在冲击中间。趋势项会让低频能量虚高削顶会引入谐波污染高频边界切在冲击上会让相邻两段都失真。这三个问题在时域波形上不一定显眼但在时频图上会直接表现为横贯全频带的亮线或块状伪影。提示如果发现某段信号幅值远超其他段先别急着归一化回头查采集时是否发生过载。过载信号做时频分析得到的结论基本不可信。3. STFT、CWT 与小波包三种时频方法在轴承信号上的选型对比3.1 STFT 的窗长权衡与参数设置短时傅里叶变换是最容易上手的方法核心参数只有一个窗长。窗长决定时间分辨率和频率分辨率的平衡。窗长越长频率分辨率越高但时间定位越模糊窗长越短时间定位越准但频率轴糊成一片。对轴承冲击信号冲击持续时间通常在毫秒级重复间隔在 5 到 20 ms 量级窗长一般取 128 到 512 点比较合适。from scipy import signal import matplotlib.pyplot as plt f, t, Zxx signal.stft( segments[0], fsfs, windowhann, # 汉宁窗旁瓣低适合冲击信号 nperseg256, # 窗长决定时频分辨率 noverlap192, # 重叠 75%让时频图更平滑 nfft512 # FFT 点数补零到 512 提高频率采样密度 ) plt.pcolormesh(t, f, np.abs(Zxx), shadinggouraud) plt.ylabel(频率 (Hz)) plt.xlabel(时间 (s)) plt.title(STFT 时频图) plt.show()逻辑说明nperseg256在 12 kHz 下对应约 21 ms 窗频率分辨率约 47 Hz时间分辨率约 21 ms对重复间隔 10 ms 左右的冲击勉强能分辨。noverlap192即 75% 重叠是常用折中重叠太低图会有栅栏感太高计算量翻倍。nfft512补零不增加真实分辨率但让频率轴更密画图更好看。参数怎么改如果冲击间隔更短把nperseg降到 128如果关注低频特征频率把nperseg加到 512 甚至 1024。3.2 连续小波变换尺度怎么选、复小波为什么更合适CWT 的优势是变分辨率高频处时间分辨率高低频处频率分辨率高天然适合冲击这类宽带瞬态。做轴承信号常用复 Morlet 小波因为它能同时给出幅值和相位时频图上的脊线更清晰。import pywt import numpy as np # 复 Morlet 小波中心频率 1 Hz带宽 1.5 wavelet cmor1.0-1.5 scales np.arange(1, 128) # 尺度范围对应不同频率 coeffs, freqs pywt.cwt(segments[0], scales, wavelet, sampling_period1/fs) plt.pcolormesh(np.arange(len(segments[0]))/fs, freqs, np.abs(coeffs), shadinggouraud) plt.ylabel(频率 (Hz)) plt.xlabel(时间 (s)) plt.show()逻辑说明cmor1.0-1.5中前一个数控制中心频率后一个数控制带宽带宽越大频率分辨率越低但时间定位越好。scales的范围决定覆盖的频带尺度小对应高频尺度大对应低频。参数怎么改想让高频段更细把scales上限调大想突出低频故障特征频率把scales下限调小并增加密度。注意pywt.cwt返回的freqs是每个尺度对应的中心频率画图时直接用不要自己换算。3.3 小波包分解把故障特征频带单独拎出来小波包比 CWT 更适合做“定向提取”把信号分解到多个频带然后挑出故障共振带对应的节点重构再做包络谱。这样得到的包络谱比直接对原始信号做 Hilbert 包络干净得多。import pywt # 三层小波包分解使用 db4 小波 wp pywt.WaveletPacket(datasegments[0], waveletdb4, modesymmetric, maxlevel3) # 查看第三层各节点的频带能量 nodes [node.path for node in wp.get_level(3, natural)] energies [np.sum(np.abs(wp[node].data)**2) for node in nodes] for n, e in zip(nodes, energies): print(n, e)逻辑说明db4是常用正交小波消失矩够用且计算量不大maxlevel3把 0 到 fs/2 分成 8 个频带每个带宽 fs/16。参数怎么改分解层数越高频带越细但边界效应越明显一般 3 到 5 层够用。挑节点时不要只看能量最大要结合轴承故障特征频率所在频带选能量最大节点有时是工频或噪声。3.4 三种方法的适用边界方法时间分辨率频率分辨率适合场景主要缺点STFT固定由窗长决定固定由窗长决定平稳或准平稳信号、快速出图窗长一选定就全局固定CWT高频高、低频低高频低、低频高冲击、瞬态、变转速计算量大尺度选择靠经验小波包取决于分解层数取决于分解层数定向提取特定频带边界效应重构需谨慎选型建议如果只是快速看故障有没有、大概在哪个频带STFT 够用如果要精确定位冲击时刻和重复间隔CWT 更合适如果已经知道共振带大致范围、要做包络谱定量诊断小波包分解加重构是最稳的路径。4. 参数调优与特征提取从时频图到可判别的故障指标4.1 窗长、重叠率与频率分辨率的定量关系频率分辨率等于采样率除以 FFT 点数时间分辨率等于窗长除以采样率。这两个量此消彼长。以 12 kHz 采样为例窗长 256 点频率分辨率约 47 Hz时间分辨率约 21 ms窗长 512 点频率分辨率约 23 Hz时间分辨率约 43 ms。轴承内圈故障特征频率在 160 Hz 左右外圈在 100 Hz 左右要分辨这些低频特征频率分辨率至少要到 10 Hz 量级意味着窗长要 1024 点以上。但窗长一大冲击的时间定位就糊了。常见做法是分两步先用短窗128 到 256 点看冲击时刻和重复间隔再用长窗1024 点以上看低频特征频率。不要指望一张图同时满足两个需求。4.2 时频图转特征向量能量熵、脊线提取与统计量时频图本身是二维矩阵直接喂给分类器维度太高。常见做法是沿时间轴或频率轴做统计得到低维特征向量。能量熵衡量能量在时频面上的分散程度故障冲击会让能量集中熵值下降脊线提取跟踪能量最大值的频率轨迹故障特征频率的脊线会呈现周期性起伏。def tf_features(Zxx): mag np.abs(Zxx) # 能量熵 p mag / (np.sum(mag) 1e-12) entropy -np.sum(p * np.log(p 1e-12)) # 沿频率轴的能量均值与方差 f_mean np.mean(mag, axis0) f_var np.var(mag, axis0) # 沿时间轴的能量均值与方差 t_mean np.mean(mag, axis1) t_var np.var(mag, axis1) return np.array([entropy, np.mean(f_mean), np.mean(f_var), np.mean(t_mean), np.mean(t_var)]) feat tf_features(Zxx) print(特征向量, feat)逻辑说明p是归一化后的时频能量分布entropy越小说明能量越集中。f_mean和f_var描述每个时刻的能量频率分布t_mean和t_var描述每个频率的能量时间分布。参数上1e-12防止 log 零。这套特征可以直接送进 SVM 或随机森林也可以作为神经网络的辅助输入。4.3 包络谱与故障特征频率的对应关系时频分析最终要落到“能不能判断故障类型”。轴承内圈、外圈、滚动体的故障特征频率有明确公式和转速、节径、滚珠数、接触角有关。做包络谱时先对共振带做带通滤波再 Hilbert 包络最后 FFT。时频图帮你确认共振带位置包络谱帮你读出特征频率。from scipy.signal import hilbert # 对共振带做带通滤波后取包络 sos_bp signal.butter(4, [2000, 4000], btypebandpass, fsfs, outputsos) band signal.sosfiltfilt(sos_bp, segments[0]) env np.abs(hilbert(band)) # 包络谱 f_env, P_env signal.welch(env, fsfs, nperseg1024) peak_idx np.argsort(P_env)[-5:] print(包络谱峰值频率, f_env[peak_idx])逻辑说明hilbert构造解析信号取模得到包络welch做功率谱估计比直接 FFT 更平滑。参数上带通范围要结合时频图选通常选能量最集中的共振带。峰值频率和理论故障特征频率对比偏差在几个百分点内就可以认为对应上了。5. 避坑与排查时频分析里最容易翻车的五个地方5.1 采样率用错导致频率轴整体偏移现象时频图上所有特征频率都比理论值大或小一个固定比例包络谱峰值对不上任何故障特征频率。原因读取文件时没确认采样率12 kHz 的数据当成 48 kHz 处理或者反过来。解决打开文件后先看信号长度和采集说明用已知转速和故障特征频率反推采样率是否合理。一个快速验证方法是看工频及其谐波是否落在 60 Hz 的整数倍附近。5.2 窗长选得太极端导致时频图不可读现象窗长取 64 点频率轴糊成一片看不出任何频带结构窗长取 4096 点时间轴上冲击全连成一条横线。原因没有根据冲击持续时间和重复间隔选窗长。解决先算冲击重复频率窗长取重复间隔的 1/4 到 1/2再算关注的频率分辨率窗长取采样率除以目标分辨率的 2 到 4 倍。两个条件冲突时分两张图做。5.3 滤波不当把故障冲击一起滤掉现象带通滤波后时域波形变干净了但时频图上的冲击特征也消失了。原因带通范围选在了工频附近而故障冲击能量集中在高频共振带。解决先对原始信号做一次 STFT 或 CWT看能量集中在哪些频带再定滤波范围。不要凭经验直接设 500 到 2000 Hz。5.4 分段边界切在冲击上造成伪影现象时频图在每段边界处出现竖直亮线误以为是周期性冲击。原因分段时没有做重叠或没有加窗边界不连续。解决分段重叠 50% 以上每段做时频分析前加汉宁窗或汉明窗。如果伪影还在检查是不是信号本身有周期性干扰。5.5 把时频图直接当图像喂给 CNN 却不做归一化现象模型训练 loss 震荡验证集准确率上不去。原因不同样本的时频图能量量级差异大有的样本幅值高、有的低网络学到的更多是幅值而不是模式。解决对每张时频图做 z-score 归一化或对数变换后再送网络。对数变换np.log1p(np.abs(Zxx))能压缩动态范围让弱特征也可见。注意时频图的坐标轴刻度不要直接当特征频率轴和时间轴的物理意义要保留否则模型学到的可能是采集批次差异而不是故障差异。6. 验证时频分析是否做对了三个可复现的检查与一个进阶技巧做完时频分析怎么知道结果可信我一般用三个检查。第一个检查是合成信号验证构造一个已知冲击间隔和载波频率的仿真信号跑一遍你的时频流程看能不能在预设的时间和频率上看到能量团。这个检查能排除大部分参数错误。第二个检查是正常基线对比拿正常轴承信号做同样的时频分析故障样本的时频图应该在特定频带出现正常样本没有的周期性结构。如果两者看起来差不多要么故障太微弱要么预处理把特征洗掉了。第三个检查是包络谱交叉验证时频图上读出的共振带做带通滤波加包络谱看峰值是否对应理论故障特征频率。三个检查都过了结论才站得住。# 合成验证信号载波 3000 Hz冲击重复间隔 10 ms t np.arange(0, 0.5, 1/fs) impulse_train np.zeros_like(t) for k in range(0, len(t), int(0.01*fs)): impulse_train[k] 1 carrier np.sin(2*np.pi*3000*t) sim impulse_train * carrier 0.1*np.random.randn(len(t)) f_s, t_s, Z_s signal.stft(sim, fsfs, nperseg256, noverlap192, nfft512) plt.pcolormesh(t_s, f_s, np.abs(Z_s), shadinggouraud) plt.ylabel(频率 (Hz)) plt.xlabel(时间 (s)) plt.title(合成信号 STFT 验证) plt.show()逻辑说明impulse_train每 10 ms 一个冲击carrier是 3000 Hz 载波sim是冲击调制载波加噪声。正确的时频图应该在 3000 Hz 附近看到每隔 10 ms 一个亮斑。如果亮斑间隔不对或频率位置偏移说明窗长、重叠或采样率设置有问题。参数上冲击间隔和载波频率可以按你的实际数据调整目的是让验证信号尽量接近真实工况。进阶技巧如果要做变转速下的时频分析普通 STFT 和 CWT 都会出现频率模糊因为转速变化让故障特征频率随时间漂移。常见做法是先用转速信号做角度域重采样把时间域信号转成角度域信号再做时频分析这样故障特征频率在角度域上是恒定的时频图上的脊线变成水平直线提取和判别都容易得多。没有转速信号时可以用时频图上的脊线斜率估计转速变化再做补偿。这一步是区分“会做时频分析”和“能用时频分析解决实际问题”的分水岭。我自己在这套数据上翻过的最大的车是早期为了图快直接对整段信号做 STFT 然后取平均谱结果把冲击的时变信息全平均掉了故障特征频率在平均谱里几乎看不见。后来改成先分段、再逐段做时频、最后沿时间轴看能量起伏才把早期故障和正常状态区分开。时频分析的价值就在“时”和“频”的联合任何把它退化成单一域的操作都是在丢掉它最核心的信息。希望帮到你。本文还有配套的精品资源点击获取