简介:分享一个MATLAB连续小波变换源码包,面向信号处理学习者与科研人员,帮助在MATLAB环境下直接实现CWT并对非平稳信号进行频谱分析。资源包含cwt1d、cwt2d、cwt3d_layer三个m文件,分别覆盖一维信号、二维图像以及分层三维数据的小波变换处理,代码思路清晰,便于二次修改与嵌入到自己的项目中,适合作为课程设计、毕业设计或论文实验的参考实现。整个压缩包共3个文件,大小仅4KB,轻量便携;目前已有1023人浏览学习。文件内涉及的尺度参数设置、Morlet小波选择、小波系数图绘制与频谱信息提取等要点,可帮助读者快速理解连续小波变换从原理到编码落地的过程,节省摸索时间。
1. 用连续小波变换做频谱分析,先搞清楚它和 FFT 的差别在哪
拿到一段振动信号、音频或者生理电信号,第一反应通常是打开 matlab 直接fft()。但 FFT 给出的是一个全局平均的频谱:它把所有时间点的能量折在了一起,信号里某个时刻突然出现的冲击、调频成分或者短暂谐振,会被平均掉,甚至在频谱图上根本看不见。连续小波变换(Continuous Wavelet Transform, CWT)解决的是这个问题——它在时间轴和频率轴上同时展开信号,输出一张时频图,既能看出哪些频率成分存在,也能看出它们什么时候出现、持续时间多长。做振动频谱分析、故障诊断、脑电节律分析的人,真正需要的其实是这种"频率随时间变化"的可视化结果。
matlab 里实现 CWT 并不复杂,核心函数就是一个cwt()。但很多人第一次上手就卡在三个地方:小波基怎么选、尺度向量怎么设、输出的coefs矩阵和频率轴的对应关系是什么。这三个问题不解决,跑出来的图要么看不出特征,要么频率轴标注得完全不对。这篇文直接从理论铺到代码,把从原始信号到输出频谱图的最小流程拆开讲,参数逐个对齐,最后把最容易翻车的边界效应和时间窗问题单独拿出来处理。适合已经会用fft()、想进一步做时频分析的人,也适合做振动频谱图分析、想看懂公开代码里cwt参数的人。
2. CWT 的时间-尺度原理与 matlab 里的核心函数
2.1 连续小波变换的数学表达和物理含义
连续小波变换的定义式是:
$C(a,b) = \frac{1}{\sqrt{a}} \int_{-\infty}^{\infty} x(t) \psi^*(\frac{t-b}{a}) dt$
其中a是尺度因子,对应频率的倒数;b是平移因子,对应时间位置;$\psi(t)$ 是小波母函数。和短时傅里叶变换(STFT)用固定窗长不同,CWT 的窗长随尺度自动变化:高频时小波被压缩,时间分辨率高、频率分辨率低;低频时小波被拉伸,频率分辨率高、时间分辨率低。这个特性使得 CWT 特别适合处理频率跨度很大的信号,比如机械振动里既有几千赫兹的冲击成分、又有几十赫兹的转频成分。
注意:尺度
a和频率f之间存在反比关系,但具体换算公式依赖小波母函数的中心频率,不是简单取倒数。用 matlab 的cwt()时直接用freqs = cwtfreqparams()或者输出参数里的f向量即可,千万别自己手写尺度转频率。
2.2 matlab 里 CWT 的三种调用方式
现在 matlab 的 CWT 主要在 Wavelet Toolbox 里,核心函数是cwt()。它有三种常见用法:
% 方式一:只传信号,自动选参数,快速看结果 [cfs, frq] = cwt(x); % 方式二:指定采样频率,frq 输出真实频率(Hz) [cfs, frq] = cwt(x, fs); % 方式三:指定小波基和频率范围,做精细控制 [cfs, frq] = cwt(x, fs, 'voicesperoctave', 16, ... 'wavelet', 'amor', 'FrequencyLimits', [0.5, 100]);cfs是复矩阵,行对应尺度/频率,列对应时间点,每个元素是复数,模值代表该时频点上的能量强度。frq是和行对应的频率向量,画时频图时直接作为y-axis使用。
参数说明:
fs:采样频率,单位 Hz,决定了频率轴的上限(奈奎斯特频率 = fs/2)。不传默认是 1,此时所有频率都是归一化的,看频谱分析结果时要格外小心。voicesperoctave:每倍频程内的频率细分数量,默认 10 或 12。调大到 16 或 32 会让时频图更平滑,但计算量会增加。wavelet:小波基类型。最常用的三个是amor(复 Morlet,适合看相位和振荡信号)、morse(默认,通用性强)、bump(频率局部性好,适合尖峰成分)。FrequencyLimits:限制分析的最低和最高频率,超过范围的cfs不计算,能显著加快运行速度。
fs = 1000; % 采样率 1000 Hz t = 0:1/fs:1-1/fs; % 1 秒时间轴 % 构造一个 10Hz 正弦 + 250Hz 冲击的混合信号 x = sin(2*pi*10*t) + 0.8*sin(2*pi*250*t).*exp(-((t-0.5)/0.01).^2); [cfs, frq] = cwt(x, fs, 'voicesperoctave', 16); % 画时频图 t_axis = 0:1/fs:1-1/fs; imagesc(t_axis, frq, abs(cfs)); axis xy; % 让频率轴从低到高显示 ylim([0 500]); xlabel('时间 (s)'); ylabel('频率 (Hz)'); title('CWT 时频图'); colorbar;2.3 小波基的选择逻辑:不是越复杂越好
很多刚开始做时频分析的人会纠结该选amor还是morse。我的经验是:先用默认的morse,根据效果再决定。Morse 小波是 matlab 从 R2016b 起的默认选择,它在时间和频率分辨率之间取了一个平衡折中,适合大多数振动信号和生物信号。如果你需要提取瞬时相位——比如分析脑电的相位同步,那优先用复值小波amor,它的实部和虚部正交,相位信息更干净。如果信号里的成分是很窄的频带脉冲,比如齿轮箱的啮合频率,bump小波的频率局部性最好,能把两个靠得很近的频谱峰分开。
3. 从时频图到频谱分析的完整 matlab 实现流程
3.1 构造测试信号:验证 CWT 是否正确的最短路径
做频谱分析之前,强烈建议先跑一个"已知答案"的合成信号。因为信号是你自己造的,理想结果长什么样你心里完全有数,如果 CWT 跑出来的图对不上,那一定是你参数设错了,趁早排查。
常见的做法是构造一个频率随时间线性变化的 chirp 信号,外加一个短时冲击:
clear; clc; close all; % 基础参数 fs = 2000; % 采样率 2000 Hz T = 2; % 信号时长 2 秒 N = T * fs; % 总采样点数 t = (0:N-1) / fs; % 时间轴 % 信号1:chirp 信号,频率从 50 Hz 线性扫到 300 Hz f0 = 50; f1 = 300; x1 = chirp(t, f0, T, f1, 'linear'); % 信号2:在 1.2 秒处的短时冲击 x2 = 2 * sin(2*pi*200*t) .* exp(-((t-1.2)/0.005).^2); % 叠加成总信号,并加入一点高斯白噪声模拟真实采集 rng(42); % 固定随机种子,保证可复现 x = x1 + x2 + 0.05 * randn(size(t));逻辑说明:chirp 信号的频率是连续变化的,适合检验 CWT 的时间-频率对应关系。冲击信号用来验证 CWT 对瞬态事件的捕捉能力——FFT 里这种短时特征几乎不可能看得到。加入噪声是模拟真实传感器采集的环境,顺便测一下 CWT 的抗噪能力。
注意rng(42)这一行:固定随机数生成器种子,这样每次运行噪声序列完全一致,方便对比参数改动前后的效果。不固定种子的话,同样的代码每次跑出来的图都会有细微差异,不利于调试。
3.2 CWT 参数设置:采样率、倍频程数和频率边界的配合
拿到信号后,CWT 参数怎么设?这三个参数是关键。
| 参数名 | 推荐值 | 说明 |
|---|---|---|
fs | 实际采集的采样频率 | 必须和信号真实情况一致,否则频率轴全部漂移 |
voicesperoctave | 16 | 默认 10 够用,16 更平滑,32 计算量明显增大 |
FrequencyLimits | 从关注的最低频率到 fs/2 | 设得越窄,计算越快,图上频率分辨率越好 |
% CWT 主程序 [cfs, frq] = cwt(x, fs, ... 'voicesperoctave', 16, ... 'wavelet', 'morse', ... 'FrequencyLimits', [20, fs/2]); % 提取幅度谱:复数 cfs 取模,转成分贝值更直观 amp = abs(cfs); amp_db = 20 * log10(amp / max(amp(:)) + eps); % 绘制时频图 figure('Color', 'w', 'Position', [100 100 1200 400]); imagesc(t, frq, amp_db); axis xy; colormap('jet'); % 工程上常用 jet 色带,冷色低、暖色高 clim([-40, 0]); % 只显示最高以下 40 dB 的成分 xlabel('时间 (s)', 'FontSize', 12); ylabel('频率 (Hz)', 'FontSize', 12); title('连续小波变换时频图(幅度归一化)', 'FontSize', 14); h = colorbar; ylabel(h, '幅度 (dB)', 'FontSize', 12);这里clim([-40, 0])是一个值得强调的细节。如果直接用线性幅度abs(cfs)画图,能量强的高频冲击成分会完全压制低频的 chirp 信号,图上只能看到一条亮线,细节全被淹没。转成 dB 单位并把范围限制在最高值以下 40 dB,相当于给画面做了一个动态范围压缩,这是工程上处理频谱图的标准做法。
3.3 从 CWT 结果中提取单一时间点的频谱切片
时频图整体看趋势,但如果要定量分析某个时刻的频谱构成怎么办?比如想知道 0.8 秒处有哪些频率成分,这时可以从cfs矩阵里抽一列出来画频谱图:
% 取出 0.8 秒最近的时间索引 [~, idx_t] = min(abs(t - 0.8)); % 该时刻的频谱切片:取这一时间列的所有频率点 slice_amp = abs(cfs(:, idx_t)); slice_amp_db = 20 * log10(slice_amp / max(slice_amp) + eps); figure('Color', 'w'); semilogy(frq, slice_amp); % 对数幅度更清晰 grid on; xlabel('频率 (Hz)'); ylabel('幅度'); title(sprintf('t = %.2f s 时刻的频谱切片', t(idx_t))); xlim([20 fs/2]);逻辑说明:cfs矩阵的列索引和时间轴一一对应,通过min(abs(t - 0.8))找到最近的列号,这一列就是该时刻的频域切片。它相当于短时傅里叶变换里某一帧的频谱,但窗口类型和长度是由高频截止、低频截止动态决定的,比 STFT 固定窗更适合宽频分析。
提示:如果信号里有 50 Hz 工频干扰,这里会看到 50 Hz 处有一个稳定的窄峰;如果观察到频率在漂移的谱峰,那就是机器转动频率随负载变化。
3.4 用 CWT 做频谱分析时的频率轴校准
matlab 的cwt()输出的frq是归一化频率还是绝对频率,取决于你有没有传fs。一个很容易踩的坑:采样率是 1000 Hz,信号里实际包含 250 Hz 成分,但画的图上峰值出现在 0.25(归一化频率),数值上不明显,容易误导判断。传了fs后frq直接就是 Hz 单位,250 Hz 就标在 250 Hz 上。多花半秒钟传参,省掉一小时的换算排查时间。还要注意不管是cwt还是cwtft,返回的frq都是频率中心点的向量,不是频率范围区间,画图时直接作为 y 轴即可,不需要位移半格。
4. 边界效应、低分辨率区和参数微调:CWT 最常踩的坑
4.1 边界效应:信号两端的数据要谨慎解释
CWT 在计算每个尺度的小波系数时,小波在时间轴上滑动,靠近端点的时候小波有一部分伸到了信号外面。matlab 内部会根据'Boundary'参数决定如何处理边缘,默认是对信号做对称扩展,但这只是一种数值补丁,端点附近的系数仍然不可靠。
% 对比不同边界处理方式 [cfs_a, frq_a] = cwt(x, fs, 'Boundary', 'symmetric'); [cfs_b, frq_b] = cwt(x, fs, 'Boundary', 'periodic'); [cfs_c, frq_c] = cwt(x, fs, 'Boundary', 'reflective'); % 在时频图中把边缘区域标出来 figure('Color', 'w'); subplot(1,3,1); imagesc(t, frq_a, abs(cfs_a)); axis xy; title('symmetric'); subplot(1,3,2); imagesc(t, frq_b, abs(cfs_b)); axis xy; title('periodic'); subplot(1,3,3); imagesc(t, frq_c, abs(cfs_c)); axis xy; title('reflective');边界处理参数的影响在低频段尤其明显:低频对应大尺度,小波被拉长,覆盖的时间范围大,边缘区域占比自然更高。如果你做的是比较小波系数能量、量化瞬时频率这类定量分析,建议干脆把前面和后面各floor(10*scale_max)个点当作无效区,只分析中间段。尺度最大值可以从frq(1)反推大概时间跨度:duration = 1/frq(1)秒。
4.2 低频段的高频分辨率幻觉
CWT 里低频段的频率分辨率好、时间分辨率差,这是它和 STFT 最大的区别,也是新手最容易误读的地方。举个例子,10 Hz 和 12 Hz 两个成分,在 CWT 图上可能会看到两条靠得很近的水平亮线,但在某个时间点上它们的幅度值可能完全混在一起分不开。正确做法是:要区分两个接近的频点,看时频图上的俯视结构;要精确定位某个事件的时刻,看高频段的尖锐响应。
| 信号特征 | 用 STFT 合适 | 用 CWT 更合适 |
|---|---|---|
| 平稳正弦多频叠加 | 是 | 是,但没明显优势 |
| 频率调制、扫频信号 | 一般 | 是,chirp 轨迹清晰 |
| 短时冲击、瞬态故障 | 较差 | 是,时间定位准确 |
| 超低频长时间成分 | 较难选窗长 | 是,低频分辨率高 |
4.3 盲目调voicesperoctave带来的性能问题
voicesperoctave控制频域采样密度,默认 10 时每个倍频程里有 10 个频率点。调大到 32,cfs的行数会增加约 3 倍,计算时间显著上升。对一段 10 秒、采样率 48 kHz 的信号,默认参数跑下来可能只要两三秒,调到 32 可能要半分钟以上。
实际项目经验:先用默认参数快速预览整体时频结构,确认频率范围设置没问题后,再决定是否增大voicesperoctave。对于最终出图的场景——比如频谱分析报告或者论文插图——16 已经是视觉上足够平滑的值。另一个技巧是用cwtfreqparams()获取当前参数下的频率轴分布,帮助判断频率分辨率是否满足需要:
wp = cwtfreqparams('sv', 16); % wp 返回一个结构体,包含频率轴、尺度、小波参数等这个方法适合调试时快速确认frq的范围和分布密度是否合理,不用反复跑完整cwt。
4.4 信号长度不足时的处理方案
CWT 对信号长度没有硬性下限,但太短的话低频段几乎没有有效数据。假设采样率 1000 Hz,你想分析低到 1 Hz 的成分,对应周期 1 秒,至少需要几个周期的信号长度才可信,比如 5 秒以上。如果只有 0.5 秒的数据,那 1 Hz 的分析结果基本全是边界伪影。遇到这种短数据,通常的做法有两个:一是提高最低分析频率到FrequencyLimits的下限比如 10 Hz 以上;二是对信号做零填充,但这会显著增加计算量,很多情况下并不值得。稳妥方案是重新审视数据采集配置,让记录时长至少覆盖最低目标频率的 5~10 个周期。
5. 用 CWT 找回 FFT 看不到的瞬态特征:一个轴承振动案例
把前面所有内容落到一个具体场景里:轴承故障振动信号。这种信号的典型特征是低频转频成分(每分钟转速对应的频率)持续存在,同时伴随高频的周期性冲击(故障特征频率),冲击本身有衰减振荡。用 FFT 看频谱只能看到宽泛的隆起,很难定位冲击的发生时刻和重复间隔——而这两个信息恰好是判断轴承故障阶段的关键。
% 模拟轴承内圈故障振动信号 fs = 12000; T = 1; t = (0:fs*T-1) / fs; % 转频 30 Hz,故障特征频率 120 Hz fr_rot = 30; fr_fault = 120; x_bearing = sin(2*pi*fr_rot*t); % 转频成分 % 每 1/fr_fault 秒出现一次冲击,冲击频率 800 Hz,衰减系数 150 for k = 0:fr_fault-1 tk = k / fr_fault; idx = find(t >= tk & t < tk + 0.02); x_bearing(idx) = x_bearing(idx) + ... 0.8 * sin(2*pi*800*(t(idx)-tk)) .* exp(-150*(t(idx)-tk)); end % 加噪声,降低信噪比到比较真实的地步 x_bearing = x_bearing + 0.1 * randn(size(t)); % CWT 分析 [cfs_b, frq_b] = cwt(x_bearing, fs, 'voicesperoctave', 32, ... 'FrequencyLimits', [20, 2000]); figure('Color', 'w', 'Position', [100 100 1100 700]); % 第一行:时域波形 subplot(3,1,1); plot(t, x_bearing); xlabel('时间 (s)'); ylabel('幅值'); title('轴承振动时域波形'); xlim([0 1]); % 第二行:FFT 频谱 subplot(3,1,2); Xf = fft(x_bearing); f_fft = (0:length(Xf)/2-1) * fs / length(Xf); plot(f_fft, 2*abs(Xf(1:length(Xf)/2))/length(x_bearing)); xlabel('频率 (Hz)'); ylabel('幅值'); title('FFT 频谱'); xlim([0 2000]); % 第三行:CWT 时频图 subplot(3,1,3); amp_b = abs(cfs_b); amp_b_db = 20*log10(amp_b/max(amp_b(:)) + eps); imagesc(t, frq_b, amp_b_db); axis xy; colormap('jet'); clim([-35, 0]); xlabel('时间 (s)'); ylabel('频率 (Hz)'); title('CWT 时频图:可见周期性冲击'); colorbar;运行这三行对比,FFT 频谱图上你会在 30 Hz 处看到转频峰、在 800 Hz 附近看到一个鼓包,但冲击的周期性、每秒多少次、有无间歇,完全看不出来。CWT 时频图上则是另一番景象:水平方向有一条贯穿始终的 30 Hz 亮线,同时每隔约 0.0083 秒(1/120 Hz)会出现一条竖直的高频亮带,衰减过程清晰可见。
在这个案例里,CWT 真正帮你做的事情有两个。一是直接数图中的竖条亮带数量,快速验证故障特征频率;二是看亮带的幅度变化,如果故障冲击的幅度时大时小并呈规律性波动,往往对应轴承某一损伤点在负载区内外交替。这一层提取能力是 FFT 做频谱分析完全给不出来的,也正是matlab实现连续小波变换对信号做频谱分析、而不是简单用 FFT 处理的核心理由。
进一步量化冲击间隔,可以把 CWT 高频带的幅值包络提取出来再做一次 FFT,得到包络谱的故障特征峰,这属于包络分析的标准流程。一句话总结这个方法的价值:CWT 不是要替代 FFT,而是负责把 FFT 抹平的瞬态时间信息找回来。先用 CWT 粗看全局,确认感兴趣的时频区域,再用 FFT 做该区域内的精细频谱计算,两种工具配合使用,才算把频谱分析这件事做完整。
本文还有配套的精品资源,点击获取