简介:围绕凯斯西储大学轴承故障数据集的信号时频分析文档,面向机械故障诊断方向的学生与研究人员,帮助读者从原始振动信号出发理解STFT与连续小波变换的选型依据。内容先介绍CWRU实验台组成及驱动端、风扇端、基座三类加速度信号的特点,再以正常与0.021英寸内圈、滚珠、外圈故障信号为对象,对比STFT在不同尺度下的时频图,并讨论morl、cmor1-1、cmor1.5-2、cgau8等小波函数及多尺度CWT的辨识效果,最终给出尺度32与cmor1.5-2的参数选择思路,附有可复用的Python代码片段。资源包仅含1个docx文档,大小约1.05MB,便于集中阅读和按需检索。已有1582人学习,适合希望快速掌握轴承故障信号时频分析流程、复现实验并理解参数权衡的读者参考。
1. 从一份 docx 开始:时频图不是画着好看,是给特征工程定参数
某开发者把一份 docx 发给我,说图挺漂亮,但不知道为什么窗长定 32、小波选cmor1.5-2。打开一看,这是围绕一套公开的滚动轴承振动数据集做的时频分析笔记:1.5 kW 电机试验台、驱动端/风扇端/基座三路加速度计、0.021 英寸内圈滚珠外圈三种故障,配了短时傅里叶变换和 Morlet 连续小波两套可运行代码。它的价值不在那几张彩色热图,而在于把「窗长、重叠比例、小波函数、尺度序列」这四个参数怎么定、定歪了图会烂成什么样,用一条线串了下来。适合两类人:要给诊断模型准备时频图输入却卡在参数上的,以及被问「凭什么是 32 不是 64」当场答不上来的。
2. 数据集结构拆解:DE / FE / BA 三路信号先分清再动手
2.1 试验台组成与三路加速度计的分工
这套数据的采集平台不复杂:一台 1.5 kW(2 马力)电机在左,中间是扭矩传感器/译码器,右侧挂功率测试计,另有一台电子控制器负责调速和记录。故障不是「跑坏了自然形成」的,而是用放电加工在内圈滚道、滚动体、外圈滚道上人为开槽,直径分 0.007、0.014、0.021 英寸三档;负载分 0、1、2、3 马力四档,对应电机转速大致 1797、1772、1750、1730 rpm。理解这个分层很关键,因为后面做时频图对比时,样本必须锁在同一负载、同一转速下,否则你看到的差异来自工况而不是故障。
加速度计贴了三个位置,输出三路信号,物理含义完全不同。驱动端(DE)装在电机输出侧轴承座附近,离被测轴承最近,故障冲击的传递路径最短、衰减最小,信噪比最高,所以绝大多数论文只啃这一路。风扇端(FE)在电机尾部,主要反映风扇叶片和风扇侧轴承,故障特征被风阻噪声压得比较厉害。基座(BA)测的是整机振动,不平衡、地脚松动、联轴器不对中这类低频问题在它上面更明显,但轴承的高频冲击传到基座已经被结构滤波磨平了。我一般的做法是:做轴承故障分类只用 DE,做整机状态监测才把三路拼起来当多通道输入。
提示:三路信号的采样是同步的,但对应的是不同物理测点,不要当成「同一信号的三次重复采样」去做数据增强,那样等于自己给自己造泄漏。
2.2 文件名、内部键名与采样率的对应关系
从不同渠道拿到的压缩包,文件名和 mat 内部变量名经常对不上。比较稳妥的认知是:文件名里带故障类型和序号,mat 内部的键名带测点后缀。下面这张表是我在整理这份笔记时对齐出来的一组典型映射,实际下载到的版本可能略有差异,以keys()打印结果为准。
| 文件名 | 内部键名(典型) | 状态 | 故障直径 |
|---|---|---|---|
0_0.mat | X097_DE_time | 正常 | 无 |
21_1.mat | X209_DE_time | 内圈故障 | 0.021 in |
21_2.mat | X222_DE_time | 滚动体故障 | 0.021 in |
21_3.mat | X234_DE_time | 外圈故障 | 0.021 in |
采样率以 12 kHz 为主,部分文件是 48 kHz。这个数字决定了后面所有频率轴的量程:12 kHz 采样,奈奎斯特上限 6 kHz,STFT 纵轴顶到 6000 Hz;如果你按 48 kHz 的假设去画 12 kHz 的数据,整张图的频率刻度会整体放大四倍,看起来「故障频率跑到天上去了」,其实是标签错了。所以读文件的第一件事不是画图,是确认采样率。
2.3 把四段信号读进来并对齐时间轴
import numpy as np from scipy.io import loadmat import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # Linux 换 'Noto Sans CJK SC' plt.rcParams['axes.unicode_minus'] = False # 负号别显示成方块 FS = 12000 # 采样率,必须跟数据集说明一致 N = 1024 # 单段长度,约 85.3 ms files = { '正常': ('0_0.mat', 'X097_DE_time'), '内圈': ('21_1.mat', 'X209_DE_time'), '滚动体': ('21_2.mat', 'X222_DE_time'), '外圈': ('21_3.mat', 'X234_DE_time'), } seg = {} for label, (fname, key) in files.items(): mat = loadmat(fname) if key not in mat: # 键名不一致时兜底 cand = [k for k in mat if k.endswith('DE_time')] key = cand[0] sig = mat[key].reshape(-1).astype(np.float64) # (n,1) -> (n,) seg[label] = sig[20000:20000 + N] # 跳过启动瞬态 fig, axes = plt.subplots(2, 2, figsize=(14, 7)) for ax, (label, x) in zip(axes.ravel(), seg.items()): t = np.arange(x.size) / FS * 1000 # 毫秒 ax.plot(t, x, lw=0.8) ax.set_title(label) ax.set_xlabel('时间 / ms') ax.set_ylabel('加速度 / g') plt.tight_layout() plt.show()代码里reshape(-1)是把 mat 读出来的列向量压平,astype(np.float64)是为了避免整型数据在做 FFT 时溢出;20000这个起点不是玄学——原始记录开头往往有电机启动的非稳态段,直接从第 0 点切 1024 个点,画出来的波形前段常常是一条斜坡或者大幅摆动,做时频图会污染整个低频区。参数上N=1024是这套数据上的经验值:工频约 30 Hz,1024 点覆盖约 12 个工频周期,够看出调制,又不至于长到把冲击平均掉。
2.4 记住几个特征频率,看图才有靶子
时频图不是拿来欣赏的,你要在图上的特定频率位置找「横带」。这套试验台常用的深沟球轴承几何下,几个系数值得背下来:外圈通过频率约 3.585 倍转频,内圈通过频率约 5.415 倍转频,滚动体自转频率约 2.357 倍转频,保持架频率约 0.398 倍转频。转频取 1797 rpm 时约 29.95 Hz,于是外圈故障特征频率落在 107 Hz 附近,内圈在 162 Hz 附近,滚动体在 70 Hz 附近,外面还会挂一串谐波。
知道这三个数字之后,STFT 窗长选得对不对,评判标准就变得非常具体:图上 100~200 Hz 那几条横带是不是能分开、冲击的垂直条纹是不是还看得见。这比「感觉这张图更清楚」靠谱得多。
3. STFT 落地:窗长 16 / 32 / 64 差在哪,重叠为什么定 0.5
3.1 窗长就是分辨率天平上的砝码
短时傅里叶变换的本质是把信号切成一段段加窗再做 FFT,窗长 N 一旦定下来,你在时频平面上就同时买了两样东西:频率分辨率 Δf ≈ fs/N,时间分辨率 Δt ≈ N/fs。二者乘积是常数,谁也占不到便宜。放到 12 kHz 采样下算一遍就非常直观:
| 窗长 N | 频率分辨率 Δf | 时间分辨率 Δt | 能看清什么 |
|---|---|---|---|
| 16 | 750 Hz | 1.33 ms | 冲击时刻精准,但 107 Hz 和 162 Hz 挤在同一格里 |
| 32 | 375 Hz | 2.67 ms | 低频两条带勉强分开,冲击条带仍清晰 |
| 64 | 187.5 Hz | 5.33 ms | 谐波族干净,冲击被抹宽成一片 |
| 128 | 93.75 Hz | 10.67 ms | 频率精细,但 1~3 ms 的冲击基本消失 |
轴承故障冲击的持续时间通常在 1~3 ms 量级,是典型的宽带瞬态。窗长拉到 64 以上,冲击能量被摊进多个时间格,垂直条纹糊掉;窗长压到 16,频率轴粗到 375 Hz 一格,内圈 162 Hz 和外圈 107 Hz 根本不在同一行像素上可分辨。32 这个位置不是理论最优,是在这套采样率、这套轴承几何下的实用折中,也是那份笔记最终选定它的原因。
3.2 写一个可以循环调参的 STFT 函数
from scipy.signal import stft import numpy as np import matplotlib.pyplot as plt def stft_map(x, fs, nperseg, overlap=0.5, to_db=True): """返回 (freq, time, 幅值矩阵),幅值矩阵形状 (nperseg//2+1, 段数)""" noverlap = int(nperseg * overlap) f, t, Z = stft( x, fs=fs, window='hann', # 汉宁窗,抑制频谱泄漏 nperseg=nperseg, noverlap=noverlap, # 重叠样本数,不是比例 boundary=None, # 两端不补零,时间轴与原信号严格对齐 padded=False, scaling='spectrum', # 幅值与窗长解耦,方便横向比较 ) A = np.abs(Z) if to_db: A = 20 * np.log10(A + 1e-12) A = A - A.max() # 相对最大值归一化,上限 0 dB return f, t, A x = seg['内圈'] fig, axes = plt.subplots(2, 2, figsize=(14, 8)) for ax, n in zip(axes.ravel(), [16, 32, 64, 128]): f, t, A = stft_map(x, FS, n) ax.pcolormesh(t * 1000, f, A, shading='auto', cmap='jet', vmin=-60, vmax=0) ax.set_title(f'窗长 {n}') ax.set_xlabel('时间 / ms') ax.set_ylabel('频率 / Hz') ax.set_ylim(0, 1000) # 轴承特征频率都在 1 kHz 以内,先放大低频段 plt.tight_layout() plt.show()几个参数值得单独说。window='hann'是振动信号的默认选择,矩形窗泄漏太狠会把谐波糊成一片。noverlap传的是样本数而不是比例,int(nperseg*0.5)这一步不能省,直接写noverlap=0.5会被截断成 0,等于没有重叠,图会变得很「块」。boundary=None和padded=False一起用,是为了让返回的t精确对应原始采样点,不加这两句,scipy 默认会在两端补零,时间轴偏移半帧,做多图对齐时非常难受。scaling='spectrum'保证换窗长时幅值量级不跳变,否则你没法判断两张图的亮度差异是参数造成的还是能量造成的。
vmin=-60, vmax=0是显示范围,只影响观感不影响数据。真要看弱谐波,可以压到 -80;但压太低会把底噪抬成「假条带」,判断故障前先用正常样本的图做基线,正常件的图上不该出现稳定横带。
3.3 换到四类状态上,看的是辨识度不是好看
固定窗长 32、重叠 0.5,把正常、内圈、滚动体、外圈四段各跑一遍,并排放。这时候你会在图上看到三种完全不同的形态:正常件基本是一条贴着底部的噪声带,低频区有淡淡的转频横线;外圈故障的冲击周期最规整,因为外圈固定不动,冲击每隔固定角度出现一次,时频图上是等间距的垂直亮条,间隔对应 1/107 s 左右;内圈故障的冲击虽然也周期性出现,但故障点随轴旋转进出载荷区,幅值被转频调制,垂直条带的亮度呈现周期性起伏;滚动体故障最麻烦,冲击要经过保持架和内外圈两次传递,能量散、调制复杂,很多时候在时频图上只能看到一团模糊的高频能量,这也是四类里分类准确率最低的一类。
所以「选窗长 32」这个结论要分两层看:对外圈、内圈,32 已经能把谐波带和时间结构同时留住;对滚动体,32 其实偏乐观,真要抠它,得把窗长降到 16 甚至更短去看冲击时刻,再配合包络谱。那份笔记选 32 是面向四类整体辨识度的折中,不是每一类的最优解,这一点在复现时心里要有数。
3.4 重叠比例 0.5 的作用边界
重叠不是为了「让图更平滑」,它的实际作用是提高时间轴的采样密度,避免两个相邻窗之间恰好跨过一段短冲击而漏检。50% 重叠意味着时间步长等于半个窗长,32 点窗下时间步 1.33 ms,已经细于冲击持续时间。再往上加到 75%,时间步变 0.67 ms,图确实更连续,但计算量和内存翻倍,而信息增益很有限。我一般在 32~64 窗长时用 50%,窗长 128 以上才考虑 75%,因为长窗本身时间步就粗。
计算量粗略估一下:1024 点、窗长 32、重叠 16,段数是 (1024-32)/16+1 = 63 段,每段一次 32 点 FFT,几乎瞬间完成。但如果你按整段几十万点做,段数上万,再乘上四类状态和多个负载,内存就会开始叫。批量处理时建议先切片再变换,不要一次性把整个文件塞进去。
4. 连续小波变换:四个小波函数怎么比,尺度序列怎么构造
4.1 尺度与频率的换算关系
连续小波变换和 STFT 最大的区别在于窗是「自适应」的:尺度 a 大,小波在时间上被拉长、在频率上被压缩,看低频;尺度 a 小,小波在时间上被压窄、频带变宽,看高频。换算公式是 f ≈ fc·fs/a,其中 fc 是小波的中心频率,由小波函数本身决定。pywt 提供了pywt.scale2frequency做这件事,本质上和上面这个公式一致。
这解释了一个常见困惑:为什么同一段信号,尺度序列从 32 拉到 256,图底部会多出很多细节。因为大尺度对应低频,转频及其倍频那一簇密集的横线只有在大尺度下才分得开。
4.2 四个候选小波的取舍
那份笔记里对比了morl、cmor1-1、cmor1.5-2、cgau8四种,尺度统一设 128。这四个名字背后的差异,值得摊开说清楚:
| 小波名 | 类型 | 关键参数含义 | 时频特性 | 适用场景 |
|---|---|---|---|---|
morl | 实 Morlet | 无显式带宽/中心频率参数,fc 默认约 0.8125 | 时频局部化尚可,但实小波只保留实部,出现对称负频条带 | 快速看包络,不追求相位 |
cmor1-1 | 复 Morlet | 带宽 1,中心频率 1 | 频带窄,频率分辨率好,时域拖尾长 | 分析稳态谐波族 |
cmor1.5-2 | 复 Morlet | 带宽 1.5,中心频率 2 | 时频聚集较均衡,冲击与谐波兼顾 | 故障冲击类信号的默认选择 |
cgau8 | 复高斯 | 8 阶,阶数越高时域越窄 | 时域最集中,冲击定位最尖,频域拖尾明显 | 精确定位冲击时刻 |
cmorB-C里的 B 是带宽参数,C 是中心频率。B 越大,小波在频域越宽、时域越窄,时间定位变好但频率选择性变差;C 越大,中心频率越高,同样的尺度下对应更高的实际频率。这一对参数是耦合的,不能单独调一个。
复数小波相比实小波的优势在这里体现得很直接:复小波变换后系数是复数,取模得到的是解析信号的包络,对冲击调制非常敏感;实小波只有实部,正负频对称出现在图上,看着像「重影」,判断时容易误读成两个故障频率。所以除非只是快速预览,我一般不用morl。
4.3 尺度序列与 cparam 的构造
import numpy as np import pywt fs = 12000 totalscal = 128 # 尺度个数,决定频率轴的分辨密度 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) # 从大到小,共 totalscal 个 freqs = pywt.scale2frequency(name, scales) / (1 / fs) print(f'{name:12s} fc={fc:.4f} scale=[{scales[0]:.2f}, {scales[-1]:.2f}] ' f'freq=[{freqs[-1]:.1f}, {freqs[0]:.1f}] Hz')cparam = 2*fc*totalscal这个写法的作用,是让最大尺度对应的频率落在接近奈奎斯特的位置,最小尺度对应到低频端,从而让整条尺度序列把感兴趣的频段覆盖满。np.arange(totalscal, 0, -1)生成的是从 128 递减到 1 的整数序列,做除数之后 scales 变成递增序列,符合 pywt 对尺度「由小到大」的期望。写反了不会报错,但纵轴方向会倒过来,看图时会以为高频在下面。
pywt.central_frequency返回的是归一化频率(以采样频率为单位),所以后面算实际频率要除以采样周期1/fs。这一步是最容易翻车的地方,下面避坑章还会再提。
4.4 四种小波出图对比与选型结论
import matplotlib.pyplot as plt x = seg['内圈'] t = np.arange(x.size) / FS fig, axes = plt.subplots(2, 2, figsize=(14, 8)) for ax, name in zip(axes.ravel(), ['cgau8', 'morl', 'cmor1-1', 'cmor1.5-2']): fc = pywt.central_frequency(name) scales = (2 * fc * totalscal) / np.arange(totalscal, 0, -1) coef, freqs = pywt.cwt(x, scales, name, sampling_period=1 / FS) amp = np.abs(coef) ax.contourf(t * 1000, freqs, amp, levels=40, cmap='jet') ax.set_title(f'内圈 - {name}') ax.set_xlabel('时间 / ms') ax.set_ylabel('频率 / Hz') ax.set_ylim(0, 1000) plt.tight_layout() plt.show()sampling_period=1/FS这一句必须传对:它的单位是秒,传1/12000得到正确的赫兹;传12000会让频率轴整体错四个数量级。contourf的levels控制色阶数量,40 左右对这类图够用,太少会出现大色块把细节吃掉。
摆在四张图里对比,结论比较清楚:cgau8的冲击竖条最锐利,但每条竖条后面拖着长长的频率尾巴,谐波带边界发虚;morl出现明显的上下对称结构,负频和中频混在一起;cmor1-1频带干净,但时间方向扩散严重,冲击被拉成一片;cmor1.5-2在两者之间取得平衡,107 Hz 和 162 Hz 附近的横带清晰,冲击竖条也保住了。所以笔记最终选cmor1.5-2,cgau8作为冲击定位的备选保留。
4.5 尺度 32 / 64 / 128 / 256 的边界在哪
固定cmor1.5-2,把totalscal依次设成 32、64、128、256,重跑滚动体那一段。规律很稳定:totalscal 越大,频率轴采样越密,图底部的低频横纹(转频、2 倍频、3 倍频)越分明;但尺度上限变大之后,小波在时间上被拉长,冲击的起始和结束时刻变糊,边界处还会出现明显的补零伪影。
我的经验是 128 起步,能覆盖到 100 Hz 以下的调制结构;要看 50 Hz 以下的极低频(比如转频本身和保持架频率),再上到 256。32 和 64 只在信号很短、或者你只关心高频冲击包络时才用,它们对低频几乎无分辨力,图底部会糊成一整条。
注意:
pywt.cwt在两端会做补零处理,最左和最右各约 10% 的时间范围不可信,画图时可以保留,但取特征时要裁掉。
5. 避坑清单:这五处我照着笔记跑全翻车了
5.1 键名对不上,KeyError卡在第一步
现象:loadmat读进来一切正常,取data['X097_DE_time']直接抛KeyError,但文件明明存在。
原因:这套数据在网上有多份转存版本,内部变量名不统一,有的保留完整后缀X097_DE_time,有的已经改成了DE_time,还有的被重新打包成.npz。
解决:不要硬编码键名,先打印再取。print([k for k in mat.keys() if not k.startswith('__')]),然后按后缀匹配DE_time、FE_time、BA_time,取不到就抛明确的错误信息,别让它静默返回 None。
5.2sampling_period传成采样率,轴全错但不报错
现象:CWT 图能画出来,形状看着也还行,但纵轴最大值只有零点几赫兹,怎么调尺度都不对。
原因:pywt.cwt(x, scales, wavelet, sampling_period)的第四个参数是采样周期(秒),不是采样频率。写成sampling_period=1024或sampling_period=fs都不报错,只是频率换算全崩。
解决:统一写1/FS,并在函数封装里加一句断言,assert 1e-5 < sampling_period < 1,越界直接抛异常,比事后看图猜快得多。
5.3 变量名被注释掉还继续用,NameError
现象:跑那段 CWT 对比代码,第一组cgau8正常,第二组morl直接NameError: name 'fc2' is not defined。
原因:笔记里fc2 = pywt.central_frequency(wavename2)这一行被注释掉了(可能是当时调试遗留),但下面cparam2 = 2 * fc2 * totalscal还在用。
解决:把注释还原;更稳妥的做法是把「小波名 → fc → cparam → scales」抽成一个函数,循环调用,杜绝这种复制粘贴带来的变量名错位。四份几乎相同的代码块本来就是 bug 温床。
5.4pcolormesh维度不匹配或shading警告
现象:TypeError: Dimensions of C are incompatible with X and/or Y,或者图能出但整体翻转、频率轴上下颠倒。
原因:scipy.signal.stft返回的Z形状是(频率, 时间),pcolormesh要求前两个参数分别是 X 轴(时间)和 Y 轴(频率),顺序写反就报维度错;shading='gouraud'在部分 matplotlib 版本上要求 X/Y 与 C 同尺寸,而stft返回的是网格边界而非网格中心。
解决:固定写pcolormesh(t, f, np.abs(Z), shading='auto'),shading='auto'交给库自己判断;同时在画图前打印一次Z.shape和t.size, f.size,对不上立刻能看出来。
5.5 中文标题变方块,负号也变方块
现象:标题里的「正常」「内圈」全变成豆腐块,dB 刻度上的负号变成空心方框。
原因:matplotlib 默认字体不含中文字形,负号用的unicode_minus在部分字体里也没有对应字形。
解决:设plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']并紧跟plt.rcParams['axes.unicode_minus'] = False。在无桌面环境的服务器上,雅黑通常不存在,换成本机已装的 CJK 字体,比如Noto Sans CJK SC、WenQuanYi Zen Hei,用fc-list :lang=zh先查一遍再写死,比反复试错快。
6. 把时频图喂给模型:尺寸、归一化与划分的三个硬约束
时频图分析做完,绝大多数人的下一步是把它当图像塞进 CNN。这一步有个隐形门槛:scipy.signal.stft在 1024 点、窗长 32、重叠 16 下,输出矩阵只有 17×63,直接 resize 到 64×64 等于把 63 列插值成 64 列,信息量没增加,只是变好看了。真正合适的做法是:原始信号按 4096 点切一段,用窗长 64、重叠 48 跑 STFT,得到 33×135 的矩阵,再缩放到 64×64 或 96×96,频率轴保留 0~2000 Hz 区间,把无关的高频段裁掉。
第二件事是归一化。时频图不能按像素做全局均值方差标准化,因为绝对能量本身携带工况信息(不同负载下幅值差好几倍),全局标准化会把这种信息抹平。我一般按样本做 dB 归一化再裁剪到 [-60, 0],最后除以 60 映射到 [0,1],这样每个样本都在同一亮度区间,模型学到的是形态而不是能量。
第三件事是划分,也是最容易虚高准确率的地方。同一段连续信号切出来的相邻切片,相关性极高,随机打乱划分会让验证集里混进训练集的「近邻」,准确率能冲到 99% 但换个文件就崩。正确做法是按原始文件、或至少按负载分块划分:训练集用 0、1、2 马力,测试集整体用 3 马力。下表是我常用的整理格式:
| 项目 | 规格 | 说明 |
|---|---|---|
| 单样本长度 | 4096 点 | 约 341 ms,覆盖 10 个以上冲击周期 |
| STFT 参数 | 窗长 64,重叠 48,Hann | 输出 33×135 |
| 频率裁剪 | 0~2000 Hz | 保留全部轴承特征频率及前几阶谐波 |
| 图像尺寸 | 64×64,灰度 | 三通道没有额外信息,不必复制 |
| 归一化 | 逐样本 dB,裁剪至 [-60,0] | 保留形态、消除工况幅值差异 |
| 划分方式 | 按负载分块 | 避免相邻切片泄漏 |
验证阶段别急着上大模型。先用 1 近邻在 64×64 灰度图上跑一遍,如果四分类能到 85% 以上,说明时频图确实编码了判别信息,再上 CNN 才有意义;如果 1NN 只有 30%,问题不在模型,回去检查窗长、频率裁剪和归一化。这个「先笨后巧」的顺序,比直接调参省太多时间。
后面我每次做时频特征,都强制先跑一遍最朴素的 1NN 基线,再决定要不要堆网络——血泪经验,跳过这步的代价通常是两周的无效调参。希望帮到你。
本文还有配套的精品资源,点击获取