简介:这是一份用于演示加窗傅里叶变换的MATLAB程序,面向数字信号处理初学者与相关课程实验人员,通过对方窗和海明窗下的正弦波信号进行傅里叶变换,直观展示窗函数对频谱泄漏的抑制作用。压缩包为zip格式,仅含1个m文件,大小759B,代码简洁,便于直接运行与参数修改。已有456人学习/下载,属于轻量级教学示例。程序内部包含正弦波生成、方窗与海明窗加窗处理、FFT计算及频谱对比等环节,学习者可调整窗长参数,观察窗口长度与频率分辨率、时间分辨率之间的权衡关系,从而深入理解加窗傅里叶变换的核心概念。海明窗的平滑过渡特性能够有效降低旁瓣幅度,与方窗形成鲜明对比,非常适合课堂教学辅助或自学入门,帮助快速建立窗函数应用的直观认识。1. 加窗傅里叶变换的演示程序:频谱泄漏怎么从看不见变成看得见做信号处理的几乎都遇到过这种困惑:明明输入是干净的正弦波,FFT 之后谱图上却出现一条宽宽的“山包”,主峰旁边还挂着长短不一的尾巴。傅里叶变换本身没有问题,问题出在计算机只能处理有限长数据——截断即加窗,而默认加的窗是矩形窗。这份 MATLAB 演示程序把方窗和海明窗对同一个正弦波的处理并排放在一个脚本里,直接展示两种窗在做傅里叶变换时主瓣宽度、旁瓣高度的差异,让“频谱泄漏”从抽象概念变成两条能对比的曲线。适合正在学数字信号处理,或者做振动测试、音频分析时需要判断频谱是否失真的从业者。2. 矩形窗与海明窗的原理边界:主瓣宽度和旁瓣衰减的数学对比2.1 直接截断等于加了矩形窗:DFT 的隐含假设先用一句话把泄漏的根源说清楚:DFT 处理的不是无限长的连续信号,而是从某个时刻开始截出来的 N 个样本。截断这个动作本身,等价于把原始信号乘上了一个矩形窗函数。矩形窗的时域表达式是:w_rect(n) = 1, 0 <= n <= N-1它对信号没有任何加权,所以很多人会忽略它的存在。但频域里矩形窗的频谱是 Dirichlet 核,形状是 sin(Nω/2)/sin(ω/2),主瓣两侧有大量旁瓣,第一旁瓣只比主瓣低约 13 dB。当信号频率不是频谱 bin 的整数倍时,主瓣功率会“漏”到旁瓣里,造成谱峰变宽、幅值偏低,甚至出现原本不存在的频率成分。这个机制的通俗理解是:截断造成数据两端的不连续跳变,跳变在频域里就是宽频能量。跳变越陡,频谱尾巴拖得越长。矩形窗的跳变是瞬间发生的,所以旁瓣衰减非常慢。常见的做法是直接调用 fft(x) 而不做任何处理,这在整周期采样时问题不大,一旦采样点数不满足周期条件,泄漏立刻变得明显。数据越长,泄漏看起来越不明显,但这不是因为泄漏消失了,而是主瓣变窄掩盖了旁瓣的存在。2.2 主瓣宽度与旁瓣衰减:两个决定谱分析质量的指标评价窗函数只需要看两个指标:主瓣宽度和旁瓣衰减。这两个指标是矛盾的,主瓣窄则旁瓣高,旁瓣低则主瓣宽,不存在两个都好的窗。主瓣宽度决定频率分辨能力。两个频率很接近的正弦波,如果它们的间隔小于主瓣宽度,在频谱上就会叠成一个峰,无法区分。矩形窗主瓣两个零点之间的宽度约为 2fs/N(模拟频率),这是所有常用窗里最窄的,所以矩形窗的频率分辨能力最好。旁瓣衰减决定泄漏抑制能力。矩形窗第一旁瓣约为 -13 dB,海明窗第一旁瓣约为 -41 dB,这意味着海明窗能把远端泄漏压低约 28 dB。代价是主瓣宽度变成矩形窗的两倍左右,约为 4fs/N。工程上做设备振动测试时,我一般先用矩形窗快速看全貌,确认有多少频率成分;确定目标频率后,再切到海明窗或者平顶窗去精确读幅值。矩形窗负责“找得见”,海明窗负责“测得准”,两者配合而不是二选一。2.3 海明窗为什么比矩形窗“干净”:从时域表达式看频域效果海明窗的时域定义是:w_hamm(n) = 0.54 - 0.46 * cos(2*pi*n/(N-1)), 0 <= n <= N-1这个式子的本质是在矩形窗基础上叠加了一个余弦分量。余弦在频域对应两个冲激,位置在 ±2π/(N-1),幅度为 -0.46/0.54 ≈ -0.85 倍。它们正好落在矩形窗第一个旁瓣的位置上,用反相分量把旁瓣压掉一大截,这就是海明窗旁瓣低的直接原因。代价也从这个式子看得出来:0.54 的直流分量把主瓣展宽了,相当于用分辨率换动态范围。指标矩形窗海明窗时域表达式w(n)=1w(n)=0.54-0.46cos(2πn/(N-1))主瓣零点间宽度(模拟频率)约 2fs/N约 4fs/N第一旁瓣衰减约 -13 dB约 -41 dB频率分辨能力好中幅值测量精度(非整周期)差较好适用场景整周期采样、瞬态信号连续振动、音频频谱分析这两行数字记在心里,后面调试频谱的时候能少走很多弯路。选窗不是看惯不习惯,而是看你到底要分辨相近的频率,还是要读准单一频率的幅值。3. MATLAB 加窗 FFT 的实现:从正弦波生成到单边谱绘制的完整脚本3.1 信号生成部分:采样率、频率与采样点数的配合先搭一个能直接跑起来的最小框架。采样率 fs 设为 1024 Hz,采样点数 N 设为 256,信号频率 f0 设为 64 Hz。这个搭配的用意是:fs/f0 = 16,N/f0 周期 = 16,正好是整数周期采样,矩形窗下不会出现额外的泄漏干扰,便于和海明窗做干净的对比。fs = 1024; % 采样率,单位 Hz N = 256; % 采样点数 n = 0:N-1; % 离散时间索引 f0 = 64; % 正弦波频率,单位 Hz % 生成单频正弦波 x = sin(2*pi*f0*n/fs); % 加窗:矩形窗等于不加窗 win_rect = ones(1, N); x_rect = x .* win_rect; % 加窗:海明窗,注意用 periodic 模式并转成行向量 win_hamm = hamming(N, 'periodic')'; x_hamm = x .* win_hamm;这段代码里有三个参数值得解释。fs 决定频率轴的跨度,FFT 能显示的最高频率是 fs/2,也就是 512 Hz,信号频率必须低于这个值;N 决定频率分辨率,fs/N = 4 Hz,也就是说频谱上每个 bin 间隔 4 Hz;f0 必须对应到整数个 bin 上,64/4 = 16 个 bin,这样才能让矩形窗的泄漏最小化。hamming(N, 'periodic') 返回的是列向量,需要用单引号转置成行向量,才能和 x 这个行向量逐元素相乘。periodic 和默认的 symmetric 模式的区别在于:periodic 模式让窗函数首尾在周期延拓时保持连续,适合频谱分析;symmetric 模式更常用于 FIR 滤波器设计。做加窗 FFT 时建议明确写成 periodic。3.2 加窗操作:方窗与海明窗的代码写法方窗在 MATLAB 里没有独立函数,ones(1, N) 就是它的全部代码。很多教材会把“不加窗”和“加方窗”分成两件事讲,其实在 DFT 语境里它们是同一个操作。fft(x) 内部的第一件事就是把 x 当作已加窗序列处理。海明窗直接用内置函数 hamming,但新手最容易犯的错不是选窗,而是忘了处理维度。x 是行向量,hamming(N, 'periodic') 默认输出列向量,两者直接相乘会报维度不匹配的错误。我习惯在写信号处理脚本时统一用行向量,所有窗函数生成后立即加转置,避免后续 plot、fft、findpeaks 这些函数在行列方向上出幺蛾子。如果需要更大动态范围,可以把 hamming 换成 hann(N, 'periodic')',代码结构完全不变。hann 窗第一旁瓣约 -31 dB,比 hamming 差一点,但主瓣略微窄一点。两者选哪个不影响流程,影响的是最终频谱读数的取舍。3.3 FFT 与频谱绘制:单边谱的实现方式加窗之后的下一步是 FFT 和幅值谱绘制。这里要处理两个细节:一是只画单边谱,二是幅值恢复。% 对加窗后的信号做 N 点 FFT X_rect = fft(x_rect, N); X_hamm = fft(x_hamm, N); % 取单边谱的前半段 X_rect_amp = abs(X_rect(1:N/2+1)); X_hamm_amp = abs(X_hamm(1:N/2+1)); % 幅值恢复:中间频点乘 2,直流点不乘 X_rect_amp(2:end-1) = 2 * X_rect_amp(2:end-1) / N; X_rect_amp(1) = X_rect_amp(1) / N; % 海明窗的恢复系数按窗函数能量折算 win_sum = sum(win_hamm); X_hamm_amp(2:end-1) = 2 * X_hamm_amp(2:end-1) / win_sum; X_hamm_amp(1) = X_hamm_amp(1) / win_sum; % 频率轴 f_axis = (0:N/2) * fs / N; figure; plot(f_axis, X_rect_amp, 'LineWidth', 1); hold on; plot(f_axis, X_hamm_amp, 'LineWidth', 1); xlabel('频率 (Hz)'); ylabel('幅值'); legend('矩形窗', '海明窗'); grid on; xlim([0 150]);代码里的除以 N 和乘以 2 是 DFT 幅值恢复的标准做法。fft 输出的模值等于原始信号幅度乘以 N(整周期采样时),所以要除以 N 还原真实幅值。单边谱只取了正频率部分,能量对半分成正负两侧,所以除了直流和奈奎斯特频率点外要乘 2。海明窗的恢复系数直接用 sum(win_hamm) 来折算,因为加窗相当于对信号幅度做了一个平均加权,海明窗的 sum/N 约为 0.54,因此原始幅度约为读取值的 1/0.54 倍。手写代码时不要把这个系数当成固定值,程序里用 sum(win) 动态计算更稳妥。跑完这段代码,矩形窗谱峰在 64 Hz 处高度接近 1,海明窗谱峰也回到接近 1,但海明窗主瓣明显比矩形窗宽。这就是目视对比最直接的结论。3.4 三个直接影响结果的参数:窗长、FFT 点数、幅值归一化第一个参数是窗长 N。N 越大,频率分辨率越高,时域观察窗口越长。但 N 不是越大越好,长窗会让瞬态事件在时间轴上被抹平,分析非平稳信号时反而有害。第二个参数是 FFT 点数 NFFT。fft(x, NFFT) 中 NFFT 可以大于 N,等于在时域末尾补零。补零让频谱曲线更平滑,但不会提升真实分辨率,这一点放到下一章详细展开。第三个参数是幅值归一化方式。有人用 abs(fft(x))/N2,有人用 20log10(abs(fft(x))),还有人直接画归一化到最大值的 dB 谱。三种方式都对,但读出的数值含义完全不同。做振动测试要读物理幅值,用第一种;做音频和噪声分析要对比动态范围,用 dB 谱更直观。程序里把两种都画出来,矩形窗和海明窗的旁瓣差异在 dB 谱上更震撼:一个在 -13 dB 处就出现第一旁瓣,另一个要到 -41 dB 才出现,视觉对比非常明显。% dB 归一化谱:便于观察旁瓣高度 X_rect_db = 20 * log10(X_rect_amp / max(X_rect_amp) + eps); X_hamm_db = 20 * log10(X_hamm_amp / max(X_hamm_amp) + eps); figure; plot(f_axis, X_rect_db, 'LineWidth', 1); hold on; plot(f_axis, X_hamm_db, 'LineWidth', 1); xlabel('频率 (Hz)'); ylabel('归一化幅值 (dB)'); legend('矩形窗', '海明窗'); grid on; ylim([-80 5]); xlim([0 150]);这里的 eps 是防止 log10 参数为零导致结果为负无穷。加上这个细节,程序在信号存在全零频点时不会报 warning。4. 频率分辨率与补零的真实关系:窗长说了算,FFT 点数只是显示密度4.1 频率分辨率等于 fs/N,加窗后有效分辨率进一步变差频率分辨率这个说法在工程里有两种含义,必须先分清。第一种是 DFT 的 bin 间隔,等于 fs/N,它决定频谱上相邻两条谱线之间的频率差值。第二种是实际能分辨两个相近频率的最小间隔,它由窗函数的主瓣宽度决定。bin 间隔只反映“显示粒度”,不反映“分辨能力”。fs = 1024 Hz,N = 256 时,bin 间隔是 4 Hz。这不代表你能分辨相差 4 Hz 的两个信号。矩形窗主瓣零点间宽度约为 8 Hz,海明窗约为 16 Hz,所以两个频率间隔 4 Hz 的信号,矩形窗勉强能看出两个峰,海明窗只能看到一个鼓包。加窗会进一步拉低有效分辨率。这是因为窗函数在频域里和信号频谱做卷积,主瓣越宽,卷积结果越平。实际工程里我判断能否分辨两个峰,第一反应不是算 fs/N,而是先看所选窗的主瓣宽度够不够窄。4.2 补零不是万能药:为什么 NFFT 大于 N 不能提升真实分辨率很多人加窗后发现谱峰不够尖锐,第一反应是把 FFT 点数改大,比如 N = 256 却做 2048 点 FFT,结果频谱曲线确实变平滑了,但峰和峰之间还是分不开。这是典型的“把显示密度当分辨率”的误解。补零的本质是在频域做 sinc 插值,它不增加任何新信息,只是把已有的频谱曲线描得更细腻。判断这一点很简单:补零前分开的两个峰,补零后仍然分开;补零前叠在一起的峰,补零后依然是叠在一起的整体。% 两个频率相差很近的信号 f1 = 50; f2 = 54; x_close = sin(2*pi*f1*n/fs) + sin(2*pi*f2*n/fs); % 直接用信号长度做 FFT:bin 间隔 4 Hz,两个峰正好落在相邻 bin 上 X_close = fft(x_close, N); X_close_amp = abs(X_close(1:N/2+1)); X_close_amp(2:end-1) = 2 * X_close_amp(2:end-1) / N; % 补零到 2048 点:曲线变平滑,但分辨能力没有变 X_pad = fft(x_close, 2048); X_pad_amp = abs(X_pad(1:1024+1)); X_pad_amp(2:end-1) = 2 * X_pad_amp(2:end-1) / N;注意补零后的幅值归一化仍然除以 N,不能除以 2048,因为能量来自原来的 256 个样本,补的零不携带任何信号能量。这是我见过的最常见的补零翻车点之一,归一化分母一错,整个谱的幅值全部失真。绘制这两条曲线后可以看到:N 点 FFT 的谱在 50 Hz 和 54 Hz 处各有独立峰值,幅值可能略有偏差;2048 点 FFT 的谱在这两个位置附近变成一条带波纹的宽峰,看起来“更连续”,但两个峰之间的谷没有变深,反而因为插值点更多而显得更饱满。4.3 两个频率靠得很近时:海明窗的极限与判定方法在 4.1 节的基础上继续推:fs/N = 4 Hz,海明窗主瓣零点间宽度约 16 Hz,50 Hz 和 54 Hz 间隔只有 4 Hz,远小于主瓣宽度,海明窗会把两个峰完全压成一个。验证方法很直接:对 x_close 加海明窗后再做 FFT,谱图上只有一个中心约在 52 Hz 的宽峰。这就是选窗时的实际约束。如果你要分辨 50 Hz 和 54 Hz 两个分量,海明窗就不合适,要么把 N 加大到 1024 甚至 2048,要么换回矩形窗。增大 N 的本质是压缩主瓣宽度:N = 1024 时,bin 间隔变窄,海明窗主瓣宽度变成约 4 fs/N = 4 Hz,刚好够把这两个峰分出来,但依然处于临界状态。工程上我一般留出 1.5 到 2 倍的余量,也就是要求频率间隔至少是主瓣宽度的 1.5 倍以上,再谈分辨可靠性。临界状态下得到的“两个峰”可能是噪声或者旁瓣造成的假象,不值得相信。5. 加窗 FFT 的五个常见问题与排查:现象、原因和解决步骤5.1 加窗后幅值变小了:忘了做幅值恢复现象:同样的正弦波,矩形窗谱峰读数是 1.0,海明窗谱峰读数只有 0.54 左右,感觉像是信号衰减了一半。原因:海明窗的时域平均幅度只有 0.54,逐点相乘后信号能量整体降低,直接取 FFT 模值当然偏小。这不是窗吃掉了信号,而是加权本来就有的效果。解决:幅值恢复系数取 N / sum(win)。在代码里不要写死 0.54,用 sum(win_hamm) 动态计算,因为不同窗长下这个值有细微变化。整周期采样时这个方法能准确恢复峰值,非整周期时恢复后的幅值依然有偏差,这是加窗本身导致的,属于正常现象。5.2 谱线宽度异常:窗长与 FFT 点数不匹配现象:N = 256,做了 1024 点 FFT,谱峰看起来还是宽宽的一个三角形,旁边有振荡拖尾,比预期的锐利谱线差很多。原因:FFT 点数只是把频域曲线插值变密,主瓣和旁瓣形状完全由实际加窗长度决定。256 点矩形窗的主瓣宽度无论做多少点 FFT 都不会变窄。解决:先确认 fft 的第一个参数长度和窗函数长度一致。如果确实需要更高分辨率,增加采样点数 N,而不是增加 NFFT。检查方法是在代码里打印 length(win_hamm) 和 length(x),两者不一致时优先修正数据长度。5.3 直流分量附近出现“裙边”:低频泄漏淹没了附近小信号现象:频谱左侧从 0 Hz 到 20 Hz 区域有一条快速衰减的斜坡,像裙子一样铺开,某个实际存在的小信号被完全淹没在裙边里。原因:矩形窗旁瓣以 1/f 的速度衰减,直流分量或极低频分量的旁瓣会覆盖邻近频段。低频信号波长长,截断造成的跳变更严重,泄漏范围更大。解决:换用海明窗或 Hann 窗压低旁瓣,或者先做去直流处理再分析。如果分析对象本身就在低频段,可以考虑增加采样点数,让目标频率离直流更远,或者用更高阶的窗做更深的旁瓣抑制。5.4 海明窗主瓣变宽后两个峰分不开:选窗时没算主瓣宽度现象:之前用矩形窗能看到两个相邻峰,切到海明窗后变成一个宽包,感觉“灵敏度变差了”。原因:海明窗主瓣宽度约是矩形窗的两倍,原来勉强能分辨的频率间隔现在落在主瓣内部,两个峰被卷积成一个。解决:回到 4.3 节的判定方法,先算目标频率间隔和主瓣宽度,再决定窗型。频率间隔足够大时才用海明窗;间隔不足时要么延长采样时间,要么切回矩形窗。选窗没有对错之分,只有匹配不匹配。5.5 相位信息乱了:加窗对相位的影响现象:加窗后用 angle(fft(x)) 看相位谱,非信号频率处的相位像噪声一样随机跳动,信号频率处的相位也和预期不符。原因:加窗对时域样本做了不均匀加权,边缘样本被压低,等效改变了信号的时域包络,这会影响 DFT 相位。非信号频率处幅值接近零,相位对噪声和数值误差极度敏感,读出来没有意义。解决:需要精确相位信息时,只读幅值最大的几个频点的相位,而且要保证该频点远离旁瓣干扰区域。更深一层的办法是直接提取整周期采样段,不加窗或者用矩形窗做相位测量。加窗后的相位谱适合看相对相位关系,不适合读取绝对相位值。6. 进阶验证:用自动峰值搜索量化两种窗的旁瓣抑制指标前面几章都是目视对比。目视能看出海明窗干净,但说不出干净多少。把这个过程自动化,让程序自己打印出两种窗的第一旁瓣衰减值,以后再做谱分析时就能直接引用数值。可以封装一个小函数,把加窗、FFT、幅值恢复、峰值搜索全部放进去:function [main_db, side_db] = window_analysis(x, win, fs) % x: 信号行向量 % win: 窗函数行向量 % fs: 采样率 N = length(x); xw = x .* win; X = fft(xw, N); Xmag = abs(X(1:N/2+1)); Xmag(2:end-1) = 2 * Xmag(2:end-1) / sum(win); Xdb = 20 * log10(Xmag / max(Xmag) + eps); % 用 findpeaks 找主峰和旁瓣(需要 Signal Processing Toolbox) pks = findpeaks(Xdb, 'SortStr', 'descend', 'NPeaks', 3); main_db = pks(1); side_db = pks(2) - pks(1); % 旁瓣相对主峰的 dB 差 fprintf('主瓣峰值: %.1f dB, 第一旁瓣: %.1f dB\n', main_db, side_db); end没有 Signal Processing Toolbox 时,找一个局部极大值也很容易:先在 Xdb 里定位最大值的索引,然后往右侧扫描,找到第一个“比左边大且比右边也大”的点,那个点就是主瓣外第一旁瓣。两种方式选一种即可。% 组合调用 fs = 1024; N = 256; n = 0:N-1; x = sin(2*pi*64*n/fs); win_rect = ones(1, N); win_hamm = hamming(N, 'periodic')'; [~, side_rect] = window_analysis(x, win_rect, fs); [~, side_hamm] = window_analysis(x, win_hamm, fs); fprintf('矩形窗第一旁瓣: %.1f dB\n', side_rect); fprintf('海明窗第一旁瓣: %.1f dB\n', side_hamm);跑完这个脚本,你会看到矩形窗旁边约 -13 dB 处的旁瓣被自动抓出来,海明窗则要到 -41 dB 附近才出现第一个可检测的局部峰值。这个 -28 dB 的差距,就是为什么振动测试里读轴频附近的边带时,大家宁可牺牲一点分辨率也要用海明窗的原因。这个函数还能顺手验证一件事:窗长改变时,第一旁瓣是否保持不变。矩形窗的第一旁瓣无论 N 是多少都在 -13 dB 附近,海明窗同理。旁瓣高度由窗的形状决定,不由窗长决定,窗长只影响主瓣宽窄。把这个数值验证跑熟之后,再选窗就不是拍脑袋了。我自己经历过的教训是某次给一个现场振动数据做分析,波形里明显有冲击成分,时间窗又短,我当时图省事直接用了矩形窗做整段 FFT,结果冲击旁瓣把旁边一个轻微齿轮啮合频率全部盖掉了,整整排查了两天才意识到是泄漏问题。从那以后我每次做频谱分析前,都会强制自己先跑一遍这个加窗对比脚本,确认窗型和参数选对了再上真实数据。希望帮到你。本文还有配套的精品资源,点击获取
简介:这是一份用于演示加窗傅里叶变换的MATLAB程序,面向数字信号处理初学者与相关课程实验人员,通过对方窗和海明窗下的正弦波信号进行傅里叶变换,直观展示窗函数对频谱泄漏的抑制作用。压缩包为zip格式,仅含1个m文件,大小759B,代码简洁,便于直接运行与参数修改。已有456人学习/下载,属于轻量级教学示例。程序内部包含正弦波生成、方窗与海明窗加窗处理、FFT计算及频谱对比等环节,学习者可调整窗长参数,观察窗口长度与频率分辨率、时间分辨率之间的权衡关系,从而深入理解加窗傅里叶变换的核心概念。海明窗的平滑过渡特性能够有效降低旁瓣幅度,与方窗形成鲜明对比,非常适合课堂教学辅助或自学入门,帮助快速建立窗函数应用的直观认识。1. 加窗傅里叶变换的演示程序:频谱泄漏怎么从看不见变成看得见做信号处理的几乎都遇到过这种困惑:明明输入是干净的正弦波,FFT 之后谱图上却出现一条宽宽的“山包”,主峰旁边还挂着长短不一的尾巴。傅里叶变换本身没有问题,问题出在计算机只能处理有限长数据——截断即加窗,而默认加的窗是矩形窗。这份 MATLAB 演示程序把方窗和海明窗对同一个正弦波的处理并排放在一个脚本里,直接展示两种窗在做傅里叶变换时主瓣宽度、旁瓣高度的差异,让“频谱泄漏”从抽象概念变成两条能对比的曲线。适合正在学数字信号处理,或者做振动测试、音频分析时需要判断频谱是否失真的从业者。2. 矩形窗与海明窗的原理边界:主瓣宽度和旁瓣衰减的数学对比2.1 直接截断等于加了矩形窗:DFT 的隐含假设先用一句话把泄漏的根源说清楚:DFT 处理的不是无限长的连续信号,而是从某个时刻开始截出来的 N 个样本。截断这个动作本身,等价于把原始信号乘上了一个矩形窗函数。矩形窗的时域表达式是:w_rect(n) = 1, 0 <= n <= N-1它对信号没有任何加权,所以很多人会忽略它的存在。但频域里矩形窗的频谱是 Dirichlet 核,形状是 sin(Nω/2)/sin(ω/2),主瓣两侧有大量旁瓣,第一旁瓣只比主瓣低约 13 dB。当信号频率不是频谱 bin 的整数倍时,主瓣功率会“漏”到旁瓣里,造成谱峰变宽、幅值偏低,甚至出现原本不存在的频率成分。这个机制的通俗理解是:截断造成数据两端的不连续跳变,跳变在频域里就是宽频能量。跳变越陡,频谱尾巴拖得越长。矩形窗的跳变是瞬间发生的,所以旁瓣衰减非常慢。常见的做法是直接调用 fft(x) 而不做任何处理,这在整周期采样时问题不大,一旦采样点数不满足周期条件,泄漏立刻变得明显。数据越长,泄漏看起来越不明显,但这不是因为泄漏消失了,而是主瓣变窄掩盖了旁瓣的存在。2.2 主瓣宽度与旁瓣衰减:两个决定谱分析质量的指标评价窗函数只需要看两个指标:主瓣宽度和旁瓣衰减。这两个指标是矛盾的,主瓣窄则旁瓣高,旁瓣低则主瓣宽,不存在两个都好的窗。主瓣宽度决定频率分辨能力。两个频率很接近的正弦波,如果它们的间隔小于主瓣宽度,在频谱上就会叠成一个峰,无法区分。矩形窗主瓣两个零点之间的宽度约为 2fs/N(模拟频率),这是所有常用窗里最窄的,所以矩形窗的频率分辨能力最好。旁瓣衰减决定泄漏抑制能力。矩形窗第一旁瓣约为 -13 dB,海明窗第一旁瓣约为 -41 dB,这意味着海明窗能把远端泄漏压低约 28 dB。代价是主瓣宽度变成矩形窗的两倍左右,约为 4fs/N。工程上做设备振动测试时,我一般先用矩形窗快速看全貌,确认有多少频率成分;确定目标频率后,再切到海明窗或者平顶窗去精确读幅值。矩形窗负责“找得见”,海明窗负责“测得准”,两者配合而不是二选一。2.3 海明窗为什么比矩形窗“干净”:从时域表达式看频域效果海明窗的时域定义是:w_hamm(n) = 0.54 - 0.46 * cos(2*pi*n/(N-1)), 0 <= n <= N-1这个式子的本质是在矩形窗基础上叠加了一个余弦分量。余弦在频域对应两个冲激,位置在 ±2π/(N-1),幅度为 -0.46/0.54 ≈ -0.85 倍。它们正好落在矩形窗第一个旁瓣的位置上,用反相分量把旁瓣压掉一大截,这就是海明窗旁瓣低的直接原因。代价也从这个式子看得出来:0.54 的直流分量把主瓣展宽了,相当于用分辨率换动态范围。指标矩形窗海明窗时域表达式w(n)=1w(n)=0.54-0.46cos(2πn/(N-1))主瓣零点间宽度(模拟频率)约 2fs/N约 4fs/N第一旁瓣衰减约 -13 dB约 -41 dB频率分辨能力好中幅值测量精度(非整周期)差较好适用场景整周期采样、瞬态信号连续振动、音频频谱分析这两行数字记在心里,后面调试频谱的时候能少走很多弯路。选窗不是看惯不习惯,而是看你到底要分辨相近的频率,还是要读准单一频率的幅值。3. MATLAB 加窗 FFT 的实现:从正弦波生成到单边谱绘制的完整脚本3.1 信号生成部分:采样率、频率与采样点数的配合先搭一个能直接跑起来的最小框架。采样率 fs 设为 1024 Hz,采样点数 N 设为 256,信号频率 f0 设为 64 Hz。这个搭配的用意是:fs/f0 = 16,N/f0 周期 = 16,正好是整数周期采样,矩形窗下不会出现额外的泄漏干扰,便于和海明窗做干净的对比。fs = 1024; % 采样率,单位 Hz N = 256; % 采样点数 n = 0:N-1; % 离散时间索引 f0 = 64; % 正弦波频率,单位 Hz % 生成单频正弦波 x = sin(2*pi*f0*n/fs); % 加窗:矩形窗等于不加窗 win_rect = ones(1, N); x_rect = x .* win_rect; % 加窗:海明窗,注意用 periodic 模式并转成行向量 win_hamm = hamming(N, 'periodic')'; x_hamm = x .* win_hamm;这段代码里有三个参数值得解释。fs 决定频率轴的跨度,FFT 能显示的最高频率是 fs/2,也就是 512 Hz,信号频率必须低于这个值;N 决定频率分辨率,fs/N = 4 Hz,也就是说频谱上每个 bin 间隔 4 Hz;f0 必须对应到整数个 bin 上,64/4 = 16 个 bin,这样才能让矩形窗的泄漏最小化。hamming(N, 'periodic') 返回的是列向量,需要用单引号转置成行向量,才能和 x 这个行向量逐元素相乘。periodic 和默认的 symmetric 模式的区别在于:periodic 模式让窗函数首尾在周期延拓时保持连续,适合频谱分析;symmetric 模式更常用于 FIR 滤波器设计。做加窗 FFT 时建议明确写成 periodic。3.2 加窗操作:方窗与海明窗的代码写法方窗在 MATLAB 里没有独立函数,ones(1, N) 就是它的全部代码。很多教材会把“不加窗”和“加方窗”分成两件事讲,其实在 DFT 语境里它们是同一个操作。fft(x) 内部的第一件事就是把 x 当作已加窗序列处理。海明窗直接用内置函数 hamming,但新手最容易犯的错不是选窗,而是忘了处理维度。x 是行向量,hamming(N, 'periodic') 默认输出列向量,两者直接相乘会报维度不匹配的错误。我习惯在写信号处理脚本时统一用行向量,所有窗函数生成后立即加转置,避免后续 plot、fft、findpeaks 这些函数在行列方向上出幺蛾子。如果需要更大动态范围,可以把 hamming 换成 hann(N, 'periodic')',代码结构完全不变。hann 窗第一旁瓣约 -31 dB,比 hamming 差一点,但主瓣略微窄一点。两者选哪个不影响流程,影响的是最终频谱读数的取舍。3.3 FFT 与频谱绘制:单边谱的实现方式加窗之后的下一步是 FFT 和幅值谱绘制。这里要处理两个细节:一是只画单边谱,二是幅值恢复。% 对加窗后的信号做 N 点 FFT X_rect = fft(x_rect, N); X_hamm = fft(x_hamm, N); % 取单边谱的前半段 X_rect_amp = abs(X_rect(1:N/2+1)); X_hamm_amp = abs(X_hamm(1:N/2+1)); % 幅值恢复:中间频点乘 2,直流点不乘 X_rect_amp(2:end-1) = 2 * X_rect_amp(2:end-1) / N; X_rect_amp(1) = X_rect_amp(1) / N; % 海明窗的恢复系数按窗函数能量折算 win_sum = sum(win_hamm); X_hamm_amp(2:end-1) = 2 * X_hamm_amp(2:end-1) / win_sum; X_hamm_amp(1) = X_hamm_amp(1) / win_sum; % 频率轴 f_axis = (0:N/2) * fs / N; figure; plot(f_axis, X_rect_amp, 'LineWidth', 1); hold on; plot(f_axis, X_hamm_amp, 'LineWidth', 1); xlabel('频率 (Hz)'); ylabel('幅值'); legend('矩形窗', '海明窗'); grid on; xlim([0 150]);代码里的除以 N 和乘以 2 是 DFT 幅值恢复的标准做法。fft 输出的模值等于原始信号幅度乘以 N(整周期采样时),所以要除以 N 还原真实幅值。单边谱只取了正频率部分,能量对半分成正负两侧,所以除了直流和奈奎斯特频率点外要乘 2。海明窗的恢复系数直接用 sum(win_hamm) 来折算,因为加窗相当于对信号幅度做了一个平均加权,海明窗的 sum/N 约为 0.54,因此原始幅度约为读取值的 1/0.54 倍。手写代码时不要把这个系数当成固定值,程序里用 sum(win) 动态计算更稳妥。跑完这段代码,矩形窗谱峰在 64 Hz 处高度接近 1,海明窗谱峰也回到接近 1,但海明窗主瓣明显比矩形窗宽。这就是目视对比最直接的结论。3.4 三个直接影响结果的参数:窗长、FFT 点数、幅值归一化第一个参数是窗长 N。N 越大,频率分辨率越高,时域观察窗口越长。但 N 不是越大越好,长窗会让瞬态事件在时间轴上被抹平,分析非平稳信号时反而有害。第二个参数是 FFT 点数 NFFT。fft(x, NFFT) 中 NFFT 可以大于 N,等于在时域末尾补零。补零让频谱曲线更平滑,但不会提升真实分辨率,这一点放到下一章详细展开。第三个参数是幅值归一化方式。有人用 abs(fft(x))/N2,有人用 20log10(abs(fft(x))),还有人直接画归一化到最大值的 dB 谱。三种方式都对,但读出的数值含义完全不同。做振动测试要读物理幅值,用第一种;做音频和噪声分析要对比动态范围,用 dB 谱更直观。程序里把两种都画出来,矩形窗和海明窗的旁瓣差异在 dB 谱上更震撼:一个在 -13 dB 处就出现第一旁瓣,另一个要到 -41 dB 才出现,视觉对比非常明显。% dB 归一化谱:便于观察旁瓣高度 X_rect_db = 20 * log10(X_rect_amp / max(X_rect_amp) + eps); X_hamm_db = 20 * log10(X_hamm_amp / max(X_hamm_amp) + eps); figure; plot(f_axis, X_rect_db, 'LineWidth', 1); hold on; plot(f_axis, X_hamm_db, 'LineWidth', 1); xlabel('频率 (Hz)'); ylabel('归一化幅值 (dB)'); legend('矩形窗', '海明窗'); grid on; ylim([-80 5]); xlim([0 150]);这里的 eps 是防止 log10 参数为零导致结果为负无穷。加上这个细节,程序在信号存在全零频点时不会报 warning。4. 频率分辨率与补零的真实关系:窗长说了算,FFT 点数只是显示密度4.1 频率分辨率等于 fs/N,加窗后有效分辨率进一步变差频率分辨率这个说法在工程里有两种含义,必须先分清。第一种是 DFT 的 bin 间隔,等于 fs/N,它决定频谱上相邻两条谱线之间的频率差值。第二种是实际能分辨两个相近频率的最小间隔,它由窗函数的主瓣宽度决定。bin 间隔只反映“显示粒度”,不反映“分辨能力”。fs = 1024 Hz,N = 256 时,bin 间隔是 4 Hz。这不代表你能分辨相差 4 Hz 的两个信号。矩形窗主瓣零点间宽度约为 8 Hz,海明窗约为 16 Hz,所以两个频率间隔 4 Hz 的信号,矩形窗勉强能看出两个峰,海明窗只能看到一个鼓包。加窗会进一步拉低有效分辨率。这是因为窗函数在频域里和信号频谱做卷积,主瓣越宽,卷积结果越平。实际工程里我判断能否分辨两个峰,第一反应不是算 fs/N,而是先看所选窗的主瓣宽度够不够窄。4.2 补零不是万能药:为什么 NFFT 大于 N 不能提升真实分辨率很多人加窗后发现谱峰不够尖锐,第一反应是把 FFT 点数改大,比如 N = 256 却做 2048 点 FFT,结果频谱曲线确实变平滑了,但峰和峰之间还是分不开。这是典型的“把显示密度当分辨率”的误解。补零的本质是在频域做 sinc 插值,它不增加任何新信息,只是把已有的频谱曲线描得更细腻。判断这一点很简单:补零前分开的两个峰,补零后仍然分开;补零前叠在一起的峰,补零后依然是叠在一起的整体。% 两个频率相差很近的信号 f1 = 50; f2 = 54; x_close = sin(2*pi*f1*n/fs) + sin(2*pi*f2*n/fs); % 直接用信号长度做 FFT:bin 间隔 4 Hz,两个峰正好落在相邻 bin 上 X_close = fft(x_close, N); X_close_amp = abs(X_close(1:N/2+1)); X_close_amp(2:end-1) = 2 * X_close_amp(2:end-1) / N; % 补零到 2048 点:曲线变平滑,但分辨能力没有变 X_pad = fft(x_close, 2048); X_pad_amp = abs(X_pad(1:1024+1)); X_pad_amp(2:end-1) = 2 * X_pad_amp(2:end-1) / N;注意补零后的幅值归一化仍然除以 N,不能除以 2048,因为能量来自原来的 256 个样本,补的零不携带任何信号能量。这是我见过的最常见的补零翻车点之一,归一化分母一错,整个谱的幅值全部失真。绘制这两条曲线后可以看到:N 点 FFT 的谱在 50 Hz 和 54 Hz 处各有独立峰值,幅值可能略有偏差;2048 点 FFT 的谱在这两个位置附近变成一条带波纹的宽峰,看起来“更连续”,但两个峰之间的谷没有变深,反而因为插值点更多而显得更饱满。4.3 两个频率靠得很近时:海明窗的极限与判定方法在 4.1 节的基础上继续推:fs/N = 4 Hz,海明窗主瓣零点间宽度约 16 Hz,50 Hz 和 54 Hz 间隔只有 4 Hz,远小于主瓣宽度,海明窗会把两个峰完全压成一个。验证方法很直接:对 x_close 加海明窗后再做 FFT,谱图上只有一个中心约在 52 Hz 的宽峰。这就是选窗时的实际约束。如果你要分辨 50 Hz 和 54 Hz 两个分量,海明窗就不合适,要么把 N 加大到 1024 甚至 2048,要么换回矩形窗。增大 N 的本质是压缩主瓣宽度:N = 1024 时,bin 间隔变窄,海明窗主瓣宽度变成约 4 fs/N = 4 Hz,刚好够把这两个峰分出来,但依然处于临界状态。工程上我一般留出 1.5 到 2 倍的余量,也就是要求频率间隔至少是主瓣宽度的 1.5 倍以上,再谈分辨可靠性。临界状态下得到的“两个峰”可能是噪声或者旁瓣造成的假象,不值得相信。5. 加窗 FFT 的五个常见问题与排查:现象、原因和解决步骤5.1 加窗后幅值变小了:忘了做幅值恢复现象:同样的正弦波,矩形窗谱峰读数是 1.0,海明窗谱峰读数只有 0.54 左右,感觉像是信号衰减了一半。原因:海明窗的时域平均幅度只有 0.54,逐点相乘后信号能量整体降低,直接取 FFT 模值当然偏小。这不是窗吃掉了信号,而是加权本来就有的效果。解决:幅值恢复系数取 N / sum(win)。在代码里不要写死 0.54,用 sum(win_hamm) 动态计算,因为不同窗长下这个值有细微变化。整周期采样时这个方法能准确恢复峰值,非整周期时恢复后的幅值依然有偏差,这是加窗本身导致的,属于正常现象。5.2 谱线宽度异常:窗长与 FFT 点数不匹配现象:N = 256,做了 1024 点 FFT,谱峰看起来还是宽宽的一个三角形,旁边有振荡拖尾,比预期的锐利谱线差很多。原因:FFT 点数只是把频域曲线插值变密,主瓣和旁瓣形状完全由实际加窗长度决定。256 点矩形窗的主瓣宽度无论做多少点 FFT 都不会变窄。解决:先确认 fft 的第一个参数长度和窗函数长度一致。如果确实需要更高分辨率,增加采样点数 N,而不是增加 NFFT。检查方法是在代码里打印 length(win_hamm) 和 length(x),两者不一致时优先修正数据长度。5.3 直流分量附近出现“裙边”:低频泄漏淹没了附近小信号现象:频谱左侧从 0 Hz 到 20 Hz 区域有一条快速衰减的斜坡,像裙子一样铺开,某个实际存在的小信号被完全淹没在裙边里。原因:矩形窗旁瓣以 1/f 的速度衰减,直流分量或极低频分量的旁瓣会覆盖邻近频段。低频信号波长长,截断造成的跳变更严重,泄漏范围更大。解决:换用海明窗或 Hann 窗压低旁瓣,或者先做去直流处理再分析。如果分析对象本身就在低频段,可以考虑增加采样点数,让目标频率离直流更远,或者用更高阶的窗做更深的旁瓣抑制。5.4 海明窗主瓣变宽后两个峰分不开:选窗时没算主瓣宽度现象:之前用矩形窗能看到两个相邻峰,切到海明窗后变成一个宽包,感觉“灵敏度变差了”。原因:海明窗主瓣宽度约是矩形窗的两倍,原来勉强能分辨的频率间隔现在落在主瓣内部,两个峰被卷积成一个。解决:回到 4.3 节的判定方法,先算目标频率间隔和主瓣宽度,再决定窗型。频率间隔足够大时才用海明窗;间隔不足时要么延长采样时间,要么切回矩形窗。选窗没有对错之分,只有匹配不匹配。5.5 相位信息乱了:加窗对相位的影响现象:加窗后用 angle(fft(x)) 看相位谱,非信号频率处的相位像噪声一样随机跳动,信号频率处的相位也和预期不符。原因:加窗对时域样本做了不均匀加权,边缘样本被压低,等效改变了信号的时域包络,这会影响 DFT 相位。非信号频率处幅值接近零,相位对噪声和数值误差极度敏感,读出来没有意义。解决:需要精确相位信息时,只读幅值最大的几个频点的相位,而且要保证该频点远离旁瓣干扰区域。更深一层的办法是直接提取整周期采样段,不加窗或者用矩形窗做相位测量。加窗后的相位谱适合看相对相位关系,不适合读取绝对相位值。6. 进阶验证:用自动峰值搜索量化两种窗的旁瓣抑制指标前面几章都是目视对比。目视能看出海明窗干净,但说不出干净多少。把这个过程自动化,让程序自己打印出两种窗的第一旁瓣衰减值,以后再做谱分析时就能直接引用数值。可以封装一个小函数,把加窗、FFT、幅值恢复、峰值搜索全部放进去:function [main_db, side_db] = window_analysis(x, win, fs) % x: 信号行向量 % win: 窗函数行向量 % fs: 采样率 N = length(x); xw = x .* win; X = fft(xw, N); Xmag = abs(X(1:N/2+1)); Xmag(2:end-1) = 2 * Xmag(2:end-1) / sum(win); Xdb = 20 * log10(Xmag / max(Xmag) + eps); % 用 findpeaks 找主峰和旁瓣(需要 Signal Processing Toolbox) pks = findpeaks(Xdb, 'SortStr', 'descend', 'NPeaks', 3); main_db = pks(1); side_db = pks(2) - pks(1); % 旁瓣相对主峰的 dB 差 fprintf('主瓣峰值: %.1f dB, 第一旁瓣: %.1f dB\n', main_db, side_db); end没有 Signal Processing Toolbox 时,找一个局部极大值也很容易:先在 Xdb 里定位最大值的索引,然后往右侧扫描,找到第一个“比左边大且比右边也大”的点,那个点就是主瓣外第一旁瓣。两种方式选一种即可。% 组合调用 fs = 1024; N = 256; n = 0:N-1; x = sin(2*pi*64*n/fs); win_rect = ones(1, N); win_hamm = hamming(N, 'periodic')'; [~, side_rect] = window_analysis(x, win_rect, fs); [~, side_hamm] = window_analysis(x, win_hamm, fs); fprintf('矩形窗第一旁瓣: %.1f dB\n', side_rect); fprintf('海明窗第一旁瓣: %.1f dB\n', side_hamm);跑完这个脚本,你会看到矩形窗旁边约 -13 dB 处的旁瓣被自动抓出来,海明窗则要到 -41 dB 附近才出现第一个可检测的局部峰值。这个 -28 dB 的差距,就是为什么振动测试里读轴频附近的边带时,大家宁可牺牲一点分辨率也要用海明窗的原因。这个函数还能顺手验证一件事:窗长改变时,第一旁瓣是否保持不变。矩形窗的第一旁瓣无论 N 是多少都在 -13 dB 附近,海明窗同理。旁瓣高度由窗的形状决定,不由窗长决定,窗长只影响主瓣宽窄。把这个数值验证跑熟之后,再选窗就不是拍脑袋了。我自己经历过的教训是某次给一个现场振动数据做分析,波形里明显有冲击成分,时间窗又短,我当时图省事直接用了矩形窗做整段 FFT,结果冲击旁瓣把旁边一个轻微齿轮啮合频率全部盖掉了,整整排查了两天才意识到是泄漏问题。从那以后我每次做频谱分析前,都会强制自己先跑一遍这个加窗对比脚本,确认窗型和参数选对了再上真实数据。希望帮到你。本文还有配套的精品资源,点击获取