简介:瞬时时间差分(ITD)分解是一种基于时间差分的信号处理技术,在机械设备故障诊断中广泛用于从振动等复杂信号中提取有用信息。通过对故障信号逐级分解,可将基频、高频损伤成分与噪声分离,帮助区分正常运行与异常状态,适合旋转机械、滚动轴承等部件的早期故障预警与特征识别。这份压缩包提供该算法的Matlab实现,并涉及具有宽频带响应特点的Widevcb处理方法,可增强对不同频段故障特征的检测能力,面向信号处理、故障诊断领域的研究人员和设备维护工程师,可作为算法学习或二次开发的起点。包内共5个文件,全部为.m脚本,压缩包整体仅4KB,体积紧凑;包含主分解模块、基分解模块、极值点提取模块以及可运行示例脚本,覆盖从原始信号读入、极值搜索到分量输出的完整流程。目前已有452人学习/下载。通过示例脚本可直接观察信号被拆分后的分量形态,理解各处理步骤的方法逻辑,也可将极值点提取等功能迁移到其他故障诊断算法中,服务于磨损、冲击等异常特征的识别。
1. ITD信号分解到底是什么:一条比EMD更适合故障冲击的分解路线
设备检修季你大概率遇过这种事:滚动轴承的加速度传感器采回来一段 12800 Hz 的振动信号,频谱上全是噪声毛刺,找不到 107.5 Hz 的外圈故障特征频率。这时候把信号做一次 ITD 分解,就能把一个混合波形拆成若干个“固有旋转分量(PRC)”和一个趋势项,再对冲击特征明显的分量做包络谱,故障频率一下子就出来了。ITD 全称 Intrinsic Time-Scale Decomposition,是 2007 年前后提出的自适应时频分解方法,和 EMD(经验模态分解)解决的是同一类问题,但它的基线提取用线性插值,计算代价小得多,非常适合故障信号分解这种需要快速迭代、反复试参的现场场景。这篇文章不讲空泛理论,直接站在“我要把一段故障信号分解开,找出故障频率”的角度,把 ITD 的原理、代码、参数设置和踩坑记录一次讲清楚,适合做轴承齿轮诊断、状态监测和信号处理算法落地的工程师照着复现。
2. ITD的分解原理与最小实现:一个基线提取算子讲透
2.1 一个基本公式和参数α:ITD到底在算什么
ITD 的数学出发点非常直白:任意一段实信号 X(t) 可以写成
X_t = L X_t + H X_t
其中 L X_t 叫基线信号,代表信号里的低频趋势和缓变成分;H X_t 叫固有旋转分量,代表局部振荡和冲击成分。每次分解只要能从原信号里估计出一条合理的基线,剩下的部分就是一个分量。这个“估计基线”的动作,ITD 用的是极值点的局部加权,而不是像 EMD 那样用三次样条拟合上下包络。
具体做法是这样:先从当前信号里找到所有局部极值点,记极值出现时刻为 τ_k,对应极值为 X_k。在两个连续极值 X_k 和 X_{k+1} 之间,ITD 定义了一个基线控制点
L_{k+1} = α·X_{k+1} + (1−α)·X_k
也就是说,每个极值点位置上的基线值,是当前极值和前一个极值的加权平均。α 默认取 0.5,相当于在相邻两个极值中间取一条“折中基线”。得到所有控制点之后,用分段线性插值把离散控制点连成完整基线,再从原信号里减去这条基线,就得到了第一个固有旋转分量 PRC1。之后把基线当作新的“原信号”继续重复这个过程,直到残差只剩单调趋势或极值点数量不足为止。
这个过程中真正影响分解质量的参数只有一个 α。α 越接近 1,基线越贴近当前极值,分量里保留的振荡越少,分解出来的 PRC 越多、越碎;α 越接近 0,基线越接近前一个极值,相当于相位延迟更大,分解结果更平滑但容易把冲击细节抹掉。我一般固定 α=0.5,只在遇到模态混叠时才微调到 0.4 或 0.6。需要注意的是,α 不是越大越好,实际工程里调参超过 0.7 之后,会出现“伪分量”,把原来的冲击拆成两三个小波峰,反而增加识别难度。
ITD 和 EMD 的对比,在故障信号分解的选型上很关键。EMD 通过包络均值迭代筛选,每个 IMF 要反复迭代十几到几十次,三次样条包络在信号端点附近极易发散;ITD 每条分量只做一次基线和一次减法,极值点之间的插值是线性的,计算量大概只有 EMD 的几分之一。对需要处理长数据、或者打算做在线滚动诊断的场景,ITD 的优势非常明显。
| 对比项 | ITD | EMD |
|---|---|---|
| 插值方式 | 极值间分段线性插值 | 三次样条拟合上下包络 |
| 每次分量筛选次数 | 1 次 | 通常 10 次以上迭代 |
| 计算速度 | 快,适合长序列和在线计算 | 慢,离线分析场景常用 |
| 端点效应 | 可控性强,可用端点控制点抑制 | 容易两端飞散,需延拓处理 |
| 模态混叠 | 较轻,但α设置不当也会出现 | 常见,需要集合平均等改进 |
| 对冲击特征的分辨能力 | 强,线性基线能保留冲击陡峭边沿 | 样条过平滑,冲击边沿容易被圆化 |
2.2 用MATLAB把ITD跑起来的最小代码:一个函数搞定分解
我平时在 MATLAB 里做 ITD 分解,核心函数只有几十行。这里给出一个可直接复制的最小实现,输入一段振动信号,输出 PRC 分量矩阵和残差项。代码里已经加了端点处理,避免基线外扩导致的两端振荡。
function [PRC, residual] = itd_decompose(x, alpha, max_prc) % ITD_DECOMPOSE 固有时间尺度分解 % 输入: % x: 输入信号,行向量或列向量均可 % alpha: 基线控制系数,范围(0,1),默认0.5 % max_prc: 最大分解层数,默认10 % 输出: % PRC: PRC分量矩阵,每行一个分量 % residual: 残余趋势项 if nargin < 2 || isempty(alpha), alpha = 0.5; end if nargin < 3 || isempty(max_prc), max_prc = 10; end x = x(:)'; N = length(x); t = 1:N; PRC = zeros(max_prc, N); residual = x; for k = 1:max_prc % 提取局部极大值和极小值 [pks, locs_p] = findpeaks(residual); [vls, locs_v] = findpeaks(-residual); vls = -vls; % 合并所有极值点并排序 ext_val = [pks, vls]; ext_loc = [locs_p, locs_v]; [ext_loc, idx] = sort(ext_loc); ext_val = ext_val(idx); % 去掉间隔过近的伪极值点 keep = [true, diff(ext_loc) > 1]; ext_loc = ext_loc(keep); ext_val = ext_val(keep); if length(ext_loc) < 3 break; end % 计算基线控制点 L_k = alpha*x_k + (1-alpha)*x_{k-1} Lk = zeros(size(ext_val)); Lk(1) = residual(1); % 首端控制点直接用信号首点 for j = 2:length(ext_val) Lk(j) = alpha * ext_val(j) + (1 - alpha) * ext_val(j-1); end Lk(end) = residual(end); % 末端控制点直接用信号末点 % 在极值点之间做线性插值,生成连续基线 baseline = interp1(ext_loc, Lk, t, 'linear', 'extrap'); % 固有旋转分量 = 当前信号 - 基线 PRC(k, :) = residual - baseline; residual = baseline; end % 去掉未使用的全零行 PRC(~any(PRC, 2), :) = []; end这段代码的逻辑可以拆成五个步骤:先通过findpeaks分别找局部极大值和极小值,合并后按时间排序;然后剔除相邻间隔小于 1 个采样点的伪极值,避免高频噪声干扰基线估计;接下来按公式计算每个极值位置的基线控制点,注意首尾控制点被强制设成信号首尾值,这一步是抑制端点效应的关键;随后用interp1做线性插值得到连续基线;最后用当前信号减基线,得到一个 PRC,并把基线作为下一轮输入。max_prc控制了最多分解多少层,实际能分解出几层,取决于信号里还剩多少极值点。
调用这个函数的方式也很简单:
x = % 你的振动信号 [PRC, residual] = itd_decompose(x, 0.5, 8); figure; for i = 1:size(PRC, 1) subplot(size(PRC,1)+1, 1, i); plot(x); plot(PRC(i,:)); end subplot(size(PRC,1)+1, 1, size(PRC,1)+1); plot(residual);输入信号x建议先做去均值和带通滤波,比如 1 到 5 kHz 的带通在滚动轴承诊断里就常用。alpha 默认 0.5,max_prc 不要一次给太大,从 6 到 10 之间起跳,否则后面几层基本是在分解噪声。如果你手里是一个现成的 ITD 分解工具包,大概率核心算法和我上面写的等价,区别只在于端点约束方式和极值点剔除策略,参数语义是一样的。
3. 故障信号分解的完整流程:从振动数据到故障特征频率
3.1 滚动轴承故障信号分解怎么做:ITD + 包络谱的串联
拿到一段疑似故障的振动信号,直接对它做 FFT 往往看不出什么,因为故障冲击引起的是周期性调制,能量散布在很宽的频带里。把这个信号做 ITD 分解后,冲击成分会被单独分到某个 PRC 里,再对这个分量做 Hilbert 包络解调,得到的包络谱在故障特征频率处会出现明显谱线。
滚动轴承的故障特征频率要先算清楚。以电机驱动端轴承为例,转频 fr = 25 Hz,滚珠数 n = 9,滚珠直径 d = 7.12 mm,节圆直径 D = 33.5 mm,接触角 φ = 0,外圈故障频率 BPFO = n/2 × fr × (1 − d/D × cosφ) ≈ 102.6 Hz,内圈故障频率 BPFI = n/2 × fr × (1 + d/D × cosφ) ≈ 122.4 Hz。实际计算时你用轴承手册里的几何参数代进去就行。下面我给出一段仿真故障信号的完整处理流程,代码里既包含生成仿真信号,也包含 ITD 分解和包络谱计算,可以直接复制去跑通整个链路。
%% 参数设置 fs = 12800; % 采样率 12.8 kHz T = 2; % 信号时长 2 秒 N = fs * T; t = (0:N-1) / fs; fr = 25; % 转频 25 Hz bpfo = 102.6; % 外圈故障特征频率,按轴承参数计算 fc = 3000; % 冲击激励共振频率,假设 3 kHz %% 生成外圈故障冲击信号:指数衰减振荡 impact_amp = 0.8; noise_amp = 0.3; x_impact = zeros(1, N); period = fs / bpfo; % 冲击间隔(采样点) for i = 1:floor(T * bpfo) start_idx = round(i * period); if start_idx + 30 < N idx = start_idx:start_idx+30; x_impact(idx) = x_impact(idx) + ... impact_amp * exp(-20 * (0:30) / fs) .* sin(2 * pi * fc * (0:30) / fs); end end % 加入转频成分和随机噪声 x = x_impact + 0.15 * sin(2 * pi * fr * t) + noise_amp * randn(1, N); %% ITD 分解 [PRC, residual] = itd_decompose(x, 0.5, 8); %% 计算每个 PRC 的峭度,选择冲击特征最明显的分量 kurt_list = zeros(size(PRC, 1), 1); for k = 1:size(PRC, 1) kurt_list(k) = kurtosis(PRC(k, :)); end [~, best_idx] = max(kurt_list); fprintf('峭度最大的分量为 PRC%d,峭度值 %.2f\n', best_idx, kurt_list(best_idx)); %% 对选定分量做 Hilbert 包络与包络谱分析 analytic = hilbert(PRC(best_idx, :)); env = abs(analytic); env = env - mean(env); L = length(env); win = hann(L)'; spec = abs(fft(env .* win)); f_axis = (0:floor(L/2)-1) * fs / L; spec = spec(1:floor(L/2)); % 在 50 Hz 到 300 Hz 范围内搜索峰值,避开转频低倍频干扰 band_mask = f_axis >= 50 & f_axis <= 300; [~, peak_loc] = max(spec(band_mask)); peak_freq = f_axis(band_mask); fprintf('包络谱峰值频率: %.2f Hz,理论BPFO: %.2f Hz\n', ... peak_freq(peak_loc), bpfo); %% 可视化 figure; subplot(3,1,1); plot(t, x); title('原始振动信号'); subplot(3,1,2); plot(t, PRC(best_idx, :)); title(sprintf('PRC%d 时域波形', best_idx)); subplot(3,1,3); plot(f_axis, spec); title('包络谱'); xlim([0, 500]);这个流程里要注意几个关键参数:fs = 12800是多数便携式测振仪常用的采样率,能覆盖轴承故障常见的 1 到 5 kHz 共振带;仿真冲击用 30 个点的指数衰减正弦波模拟故障冲击引起的谐振,衰减系数 20 决定了冲击脉冲的宽度,实际故障信号里这个值由轴承结构和负载决定;Hann窗在包络谱里用于抑制频谱泄漏,如果不加窗,谱线旁边会长出很多旁瓣,现场容易出现误判。包络谱的搜索频带限定在 50 到 300 Hz,是为了避开转频及其低次谐波,如果你的设备转频更高,这个区间要相应调整。
希尔伯特变换在这里的作用是把 PRC 分量从“振荡波形”变成“包络波形”。没有 ITD 预处理时,直接对原信号做希尔伯特,包络里会混入大量与故障无关的高频成分;而 ITD 分解后,冲击所在的分量更干净,包络谱中的故障特征谱线信噪比能提高不少。
3.2 分量筛选:为什么不能无脑拿PRC1做包络
很多第一次用 ITD 做故障诊断的人,默认拿第一个分量 PRC1 去做包络谱,这是最常见的误用。PRC1 是原信号减掉第一条基线得到的,它保留了信号里频率最高、变化最快的成分,但也包含了大部分宽带噪声。尤其是现场采集的振动信号噪声很强时,PRC1 的包络谱里经常是一堆毛刺,故障频率反而不突出。
我常用的筛选策略是先算每个 PRC 的峭度。峭度是四阶中心矩归一化后的统计量,对冲击信号非常敏感,正常振动信号的峭度在 3 附近,轴承早期故障时冲击占比变大,对应分量的峭度会明显升高到 5 以上。代码中的kurtosis(PRC(k,:))就是在做这件事,选出峭度最大的分量再进包络解调。这个策略在仿真信号里基本一选一个准,在实际轴承数据里也大概率没错。
还有一种更稳的补充筛选:计算每个 PRC 和原始信号的互相关系数,筛选相关系数大于 0.3 且峭度大于 4 的分量。因为故障冲击在原始信号里占主导时,包含冲击的分量和原信号的相关性也高;如果某个分量峭度高但和原信号几乎不相关,说明它大概率是端点效应或过分解产生的伪分量。我把两种指标组合起来用,写成下面的判断逻辑:
corr_list = zeros(size(PRC, 1), 1); for k = 1:size(PRC, 1) R = corrcoef(PRC(k, :)', x'); corr_list(k) = R(1, 2); end candidate = find(kurt_list > 4 & corr_list > 0.3); if isempty(candidate) % 没有满足条件的候选,退而求其次选峭度最大 candidate = best_idx; end选出来 candidate 后,可以把这个分量的包络谱和理论故障特征频率做比对。还要说的是,内圈故障和滚动体故障因为存在转频调制,包络谱里除了故障频率本身,还会在故障频率两侧出现转频边带,这时候不要只盯着峰值最高的谱线,要看特征频率附近有没有等间距边带结构。ITD 分解对边带的保留程度比 EMD 好,因为线性插值不会把调制包络的边沿过度平滑,这在早期故障诊断里是个实打实的优势。
4. ITD信号分解的常见问题与避坑:从端点效应到过分解
4.1 端点处理不当,分解出来的分量两端飞掉
现象:ITD 分解出的 PRC 在信号开头和结尾出现幅度很大的振荡,甚至比中间有效信号的幅值还大几倍。把这一段放进包络谱时,频谱低频段会出现一大片不规则隆起,故障特征谱线被淹没。
原因:这是最典型的端点效应。ITD 的基线控制点只定义在极值点上,信号两端往往不是极值点,如果不对端点做约束,线性插值在端部会按照最后一个控制点的斜率继续外推,基线在两端飞出去,PRC 两端也就跟着飞。我把这个写进代码后,用仿真数据一测,前 100 个点和后 100 个点基本没法看。
解决:最省事的方式就是我在 2.2 节代码里写的,强制把信号首点和末点设为基线控制点。这个做法的代价是首末两点附近的分量会轻微失真,但幅度被限制在可控范围内。更讲究的做法是做镜像延拓:把信号前端的一段数据翻转拼接到开头,分解完成后再把延拓部分切掉。我平时处理长数据时直接用强制端点法,处理短数据时会加镜像延拓。
% 镜像延拓示例:前后各扩展 256 个点 ext_len = 256; x_ext = [fliplr(x(1:ext_len)), x, fliplr(x(end-ext_len+1:end))]; [PRC_ext, residual_ext] = itd_decompose(x_ext, 0.5, 8); PRC = PRC_ext(:, ext_len+1:end-ext_len); residual = residual_ext(ext_len+1:end-ext_len);用这段代码时注意fliplr翻转的方向要和你信号是一维行向量匹配。延拓长度一般取信号长度的 1%,或者直接取故障特征频率对应周期的两倍,不要过长,否则计算浪费。
4.2 过分解把噪声当成故障冲击,诊断结果被高倍频误导
现象:把 max_prc 设成 15,分解出来的 PRC8、PRC9 看起来也有周期性冲击,把它们拿去做包络谱,出现了一个峰值,但频率不是故障特征频率的整数倍,也没有边带结构。新手容易把这个结果当故障特征报上去。
原因:这是过分解。信号分解到后面,剩余的残差已经没有明显的极值点结构,ITD 算法会把噪声的极小随机波动当成有效极值,继续进行基线和减法,于是分解出一堆噪声伪分量。伪分量里偶尔会随机排列出类似冲击的形状,峭度也不低,但物理上没有意义。
解决:控制分解层数,不要超过实际需要的分量数。轴承信号里,故障特征通常集中在前 3 到 5 个 PRC,后面的分量基本是噪声和趋势。我一般把 max_prc 限制在 8 以内,并且增加一个极值点数量下限:当当前信号极值点数量少于 6 个时,停止继续分解。在代码里对应的判断就是if length(ext_loc) < 3, break;,但实际诊断场景里我会把这个阈值提高到 6 到 8,防止过分解。还可以用相邻分量的相关性做校验,PRC 和 PRC 之间的相关系数突然超过 0.6,说明这两个分量其实该合并,分解已经过头了。
4.3 选了峭度最大的分量但包络谱里没有峰值,选型逻辑哪里出了问题
现象:按照峭度最大选了 PRC2,包络谱却平平无奇,反而 PRC3 的包络谱里有清晰的故障频率。换一组数据,有时候是 PRC4 效果最好,选择结果不稳定。
原因:峭度最大只说明这个分量“非高斯性最强”,不等于它的“故障特征频率成分最干净”。如果故障冲击激发的高频共振频率落在 PRC3 的频带里,而 PRC2 里主要是随机宽带噪声的尖峰,PRC2 的峭度也可能很高。尤其是噪声源是周期性冲击干扰时(比如其他设备传来的电磁干扰),峭度会骗人。
解决:不要只看峭度,把包络谱分析范围收窄到可能包含故障特征频率的频段,重新计算“带内谱峭度”。比如外圈故障频率 102.6 Hz,转频 25 Hz,我只看包络谱 50 到 300 Hz 这个区间,计算这个区间内谱线峰值的突出程度,作为选择分量的指标。这个方法比纯峭度更稳定。还有一种更工程的做法:把前 5 个 PRC 的包络谱都画出来,肉眼扫一遍,再决定用哪个做自动诊断。现场我们做监测系统时可以自动跑,但离线分析和故障复核时,画出来看一眼永远比纯自动化判断靠谱。
4.4 α参数调了没效果,是因为你忘了信号里还有直流分量
现象:相同的 α,别人分解效果好,自己分解出来的第一个 PRC 总带一个大平台,包络谱低频段有巨大能量。
原因:信号里有直流偏置。加速度传感器输出经常带一个非零均值,如果信号均值不为零,极值点分布会整体偏移,基线控制点的相对关系也被破坏,ITD 分解的第一层可能把直流成分混进 PRC1。包络谱里直流附近的低频泄漏极大,故障频率在 100 Hz 量级时容易被压住。
解决:分解之前先x = x - mean(x)去掉均值,或者用高通滤波器把 5 Hz 以下成分滤掉。如果做完去均值后,PRC1 的平台仍然存在,那就检查 α 是不是设成了 0 或 1,这两个极端值会让基线失去加权意义,把整个信号当作一个 PRC 干出来。
4.5 采样率太高,极值点全被噪声填满,分解结果像白噪声切片
现象:现场把采样率设成 51200 Hz,ITD 分解出的 PRC 每个都像随机噪声,看不到任何规则冲击,包络谱也没有稳定峰值。
原因:采样率过高时,极值点的定义变得很敏感。振动信号里哪怕是非常小的噪声扰动,在相邻几个采样点之间也会形成局部极值,这些伪极值占据了极值点的大多数,真正的故障冲击极值反而被淹没。ITD 的线性插值对这种“极值点过密”的情况特别敏感,基线会被噪声牵着走。
解决:分解前先对信号做带通滤波,把分析频带限制在故障冲击共振集中的区间。滚动轴承一般用 1 kHz 到 5 kHz 带通,如果不知道共振频段,先用原始信号的功率谱密度找一个能量集中的频峰。滤波后再 ITD 分解,极值点数量立刻收敛到正常水平。我在上一节仿真代码里没有加带通滤波,是因为信号本身构造得很干净;现场数据一定要加,让极值点数量保持在每秒钟几十到几百个的量级,而不是几千个。
5. 进阶用法与验证:用仿真信号确认ITD实现没写偏
5.1 给ITD分解函数做一次自检,验证分解完备性
换了别人的 ITD 代码,或者自己改过itd_decompose的端点约束,第一件事不是急着上现场数据,而是先用一组已知成分的仿真信号验证分解结果。ITD 和 EMD 一样有“完备性”特征,也就是所有 PRC 加残差应该能还原出原始信号。这个性质可以用下面这段代码快速检查:
%% 构造一个确定性的混合信号:正弦 + 冲击 + 线性趋势 fs = 2000; t = (0:fs*2-1) / fs; sin_part = 0.5 * sin(2 * pi * 30 * t); impact = zeros(1, fs * 2); impact(200:500) = exp(-(0:300) / 100) .* sin(2 * pi * 130 * (0:300) / fs); x_test = sin_part + impact + 0.2 * t; %% 分解并重构 [PRC, residual] = itd_decompose(x_test, 0.5, 6); x_reconstruct = sum(PRC, 1) + residual; %% 检查重构误差 err = max(abs(x_test - x_reconstruct)); fprintf('最大重构误差: %.3e\n', err);如果err在 1e-12 量级,说明分解是完备的,算法核心没问题。如果误差在 1e-1 以上,大概率是极值点剔除逻辑写错了,比如某次迭代把最后一个极值漏掉,导致残差多了一段。我在拿到一个网上找的 ITD 工具包时,最先跑的就是这段自检代码,前后只花两分钟就能判断这个包能不能用于后续诊断。
5.2 在线监测场景下ITD的性价比:把计算时间压缩到可接受范围
ITD 在设计上就适合滚动计算。以 2 秒长、12800 Hz 采样的信号为例,在我一台普通 i5 笔记本上跑itd_decompose加包络谱,整个过程大概 0.5 秒,而同样数据跑 EMD 加包络谱通常要 3 到 5 秒。这意味着在旋转机械在线监测系统里,ITD 可以做到每两秒更新一次故障特征,基本跟上数据采集的节奏。如果把 max_prc 限制在 5 层、分量筛选只用峭度指标,计算时间还能再压一半。
真正要把 ITD 部署到在线系统,我习惯在算法之前再套一层谱峭度预筛选:先用短时傅里叶变换找出共振频带,只对共振频带附近的子带信号做 ITD,分解效率和诊断准确率都能提升。这个思路其实就是把 ITD 当成包络解调的预处理,而不是把 ITD 当作全能特征提取器。用 ITD 多年下来的体会是:它最大的价值不是比 EMD 多分解出几个漂亮分量,而是让你在故障信号分解这条路上,能用最小的计算代价得到最接近物理本质的冲击分量。判断一个 ITD 实现好不好用,我的标准始终是三件事——重构误差小、端点不飞、冲击分量能被峭度筛出来。如果这三条都满足,你就放心把它写进你的诊断流程里,剩下的就是跟着数据慢慢积累经验了。希望帮到你。
本文还有配套的精品资源,点击获取