
简介这是一份用于演示加窗傅里叶变换的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 16N/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 Hzf0 必须对应到整数个 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/21)); X_hamm_amp abs(X_hamm(1:N/21)); % 幅值恢复中间频点乘 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 HzN 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); % 直接用信号长度做 FFTbin 间隔 4 Hz两个峰正好落在相邻 bin 上 X_close fft(x_close, N); X_close_amp abs(X_close(1:N/21)); 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:10241)); 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 Hz50 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/21)); 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结果冲击旁瓣把旁边一个轻微齿轮啮合频率全部盖掉了整整排查了两天才意识到是泄漏问题。从那以后我每次做频谱分析前都会强制自己先跑一遍这个加窗对比脚本确认窗型和参数选对了再上真实数据。希望帮到你。本文还有配套的精品资源点击获取