
第一次用FFT看频谱的人多半会经历同一个瞬间代码跑通了图也画出来了但心里没底。横轴那一串数字到底是Hz还是索引纵轴的高度为什么和示波器上读到的幅值对不上换一段数据峰值位置又漂了半格。我见过太多人把频谱图当成黑箱输出画出来好看就往报告里贴直到某天被问一句“你这个1.2g的峰值对应几阶”才当场卡壳。离散傅里叶变换DFT本身一点都不神秘它做的事情非常朴素把一段有限长的时域采样投影到一组固定频率的复指数基上看看每个频率上“有多少东西”。频谱分析则是在这之上补齐工程化的部分——采样率怎么定、分辨率够不够、窗函数加不加、幅值怎么归一、结果怎么验证。这两件事的分界线很清楚DFT是数学频谱分析是工程。数学部分只有一页纸工程部分能让你栽跟头的地方有一箩筐。这篇内容适合三类人一是刚学完信号与系统、公式会背但一上手就心虚的学生二是做振动监测、音频处理、电力谐波、传感器数据分析的工程师三是写了几年代码、每次用np.fft都是照抄模板、从没认真核过结果的人。我会把公式的物理含义、参数之间的换算关系、窗函数的取舍逻辑以及一条能自己验证自己的完整代码全部摊开讲最后再把现场最容易翻车的几个点按排查顺序列出来。1. DFT到底在算什么把“投影”这个词落到地上1.1 一组复指数基就是一把频率梳子先把DFT的式子摆出来它长这样$$X[k] \sum_{n0}^{N-1} x[n] \cdot e^{-j2\pi kn/N}, \quad k 0,1,\dots,N-1$$很多人第一次看这个式子注意力全在指数上的负号和那个$j$上面反而忽略了最关键的结构它是一个求和是一个内积。把$x[n]$看成一个N维向量把$e^{-j2\pi kn/N}$看成另一个N维向量那么$X[k]$就是这两个向量的点积。点积的含义是“相似程度”——两个向量方向越接近点积越大完全正交点积为零。所以DFT在做的事情是准备N个固定频率的“标准探针”把待分析的信号逐个和每个探针做相似度打分分数就是$X[k]$。第k个探针的频率是$k \cdot f_s/N$这些探针的频率是等间隔排开的间隔就是$f_s/N$。你可以把它想象成一把梳子齿间距固定你要做的就是看信号在每一根齿上的“挂载量”是多少。这里有一个容易被跳过的前提这N个探针之间是严格正交的。也就是$$\sum_{n0}^{N-1} e^{j2\pi k n/N} \cdot e^{-j2\pi k n/N} \begin{cases} N, k k \ 0, k \neq k \end{cases}$$正因为正交各个频率分量才能被干净地拆开不会互相“串味”。这个性质不是凭空来的它来自复指数在一个完整周期内的积分特性。理解了这一点你也就理解了后面所有“泄漏”“分辨率”问题的根源——一旦信号不满足整周期性正交性就被破坏了。1.2 那个负号指数为什么是共轭相乘再求和指数上的负号经常让人困惑。其实把$e^{-j\theta} \cos\theta - j\sin\theta$展开就能看清它的结构$$X[k] \sum_{n0}^{N-1} x[n]\cos(2\pi kn/N) - j\sum_{n0}^{N-1} x[n]\sin(2\pi kn/N)$$实部是信号和第k个余弦的相似度虚部是信号和第k个正弦的相似度的负值。也就是说一个复数$X[k]$同时装下了两个信息这个频率成分有多强模长$|X[k]|$以及它的相位是多少辐角$\angle X[k]$。用复指数只是把这两件事写成了一个符号省事而已。换个更工程化的比喻DFT像是拿一个转速可调的转子去“套”信号。当转子的转速和信号里某个成分的频率一致时两者同步旋转每一圈的贡献都同相累加结果就大频率不一致时转子相对信号在转正负贡献互相抵消累加起来接近零。N次累加的过程本质上就是在做相位的“对齐检验”。这个比喻还能顺带解释一件事为什么DFT对频率偏差这么敏感。转子只要稍微偏一点点转速N圈下来累积的相位偏差就是$N$倍抵消效应立刻出现峰值就往下掉、往两边摊。这就是后面要讲的scalloping loss和泄漏的来源。1.3 频率轴的排列规则k从0到N-1对应哪些Hz这是实操中出错率最高、又最容易被忽视的一环。DFT输出的索引$k$是从0排到$N-1$的但它对应的物理频率并不是从0单调递增到$f_s$而是这样的$$f_k k \cdot \frac{f_s}{N}, \quad k 0, 1, \dots, N-1$$按这个公式展开$k0$对应0 Hz直流$k$增大频率增大一直到$k N/2$时对应$f_s/2$也就是奈奎斯特频率。再往后$k N/2$的部分对应的其实是负频率$kN-1$对应的是$-f_s/N$。为什么会有负频率因为数学上正负频率是一对共轭镜像它们成对出现才能合成实数信号。对于实值输入绝大多数工程数据都是实值存在共轭对称关系$X[N-k] \overline{X[k]}$也就是说后半段是前半段的镜像没有任何新信息。所以实际分析时只看前$N/21$个点就够了这也正是numpy.fft.rfft只返回$N/21$个值的原因。具体的频率映射关系可以整理成这张表索引k对应频率物理含义是否有独立信息00 Hz直流分量有1 ~ N/2-1$k f_s/N$正频率成分有N/2$f_s/2$奈奎斯特频率有仅N为偶数时N/21 ~ N-1$(k-N) f_s/N$负频率镜像无共轭对称记住一个实操口诀实数信号的单边谱只需要看0到N/2这$N/21$个点频率间隔恒为$f_s/N$。千万不要图省事写成np.linspace(0, fs, N)那是错的正确写法是np.arange(N) * fs / N或者直接用np.fft.fftfreq(N, 1/fs)和np.fft.rfftfreq(N, 1/fs)。另外提一句奈奎斯特bin的特殊性它既不属于正频率也不属于负频率它的镜像就是它自己。在做幅值归一化的时候直流和奈奎斯特这两个bin不能乘2其余正频率bin要乘2。这个细节后面会详细讲现在先记住它存在。2. 采样率、点数、时长三个数字决定你能看见什么2.1 Δf fs/N 是怎么来的以及它为什么等于1/T频谱图上相邻两根谱线之间的频率间隔是$$\Delta f \frac{f_s}{N}$$看起来它同时受采样率和点数影响但采样时长$T N/f_s$代入一下$$\Delta f \frac{f_s}{N} \frac{1}{N/f_s} \frac{1}{T}$$结论很干净频率分辨率只由采样时长决定和采样率、点数都无关。你要分辨间隔为$\Delta f$的两个频率成分至少需要采集$T \geq 1/\Delta f$秒的数据。这是硬约束任何后处理都绕不过去。举个具体例子。要在50 Hz附近把基波和某个间隔5 Hz的边带分开$\Delta f$至少要到5 Hz$T \geq 0.2$ s。如果采样率是1000 Hz那$N \geq 200$点。但工程上不会只取200点因为理论分辨率是“勉强能分”实际谱峰会互相叠加、互相抬高分得很难看。我的习惯是留2到4倍余量也就是取$T 0.4 \sim 0.8$ s$N$取400到800点。反过来说如果你的采样时长天生受限比如只允许采集0.1 s的瞬态信号那就别指望能分辨10 Hz以内的细节分辨率上限就是10 Hz。这时候与其硬撑着画高密度频谱不如老老实实说明“本次分析频率分辨率10 Hz”比画一张假精度的图要诚实得多。还有一个常被混淆的概念频率分辨率和谱线间隔不是一回事。谱线间隔是$\Delta f$是频域的采样步长频率分辨率是分辨两个相邻成分的能力它由窗函数主瓣宽度决定对于矩形窗约等于$\Delta f$对于Hann窗约等于$1.5\Delta f$对于Flat-top窗要接近$3.8\Delta f$。加窗会让分辨率变差这是加窗的代价。2.2 补零只能让谱线变密不能让分辨率变高补零zero padding是最容易被误用的操作没有之一。很多人的逻辑是点数越多频谱越细分辨率越高所以补个十万点上去。这个逻辑错在把“曲线更光滑”当成了“信息更多”。补零在数学上做的事情是在频域做sinc插值。它确实让谱线变密了曲线看起来平滑了峰值位置读得更准了但主瓣宽度一点没变。也就是说两个原本糊在一起的成分补零之后依然糊在一起只是你能更清楚地看到它们糊成一坨的形状而已。我做过一个直观的对比实验造一条0.1 s的数据里面放100 Hz和102 Hz两个正弦采样率1000 Hz$N100$$\Delta f 10$ Hz。这两个成分间隔只有2 Hz远小于分辨率谱上只能看到一个鼓包。然后把数据补零到100万点谱线间隔变成0.001 Hz曲线光滑得像丝绸但那个鼓包依然是鼓包你依然数不出里面有两个峰。所以补零的正确用途只有两个一是让峰值位置的插值读数更方便配合抛物线插值精度更高二是让不同长度的数据在画图时尺寸一致便于对比。它不能提升分辨率这一点要在报告里写清楚别让人误以为你做了高分辨率分析。2.3 混叠的边界不在fs/2而在你的抗混叠滤波器上采样定理说采样率必须大于信号最高频率的两倍。这句话没错但工程上直接按$f_s 2f_{max}$去取基本等于给自己埋雷。问题出在两个地方。第一真实信号很少是带限的噪声、开关瞬间、机械冲击都包含高频能量这些能量一样会折叠回来。第二理想抗混叠滤波器不存在实际滤波器从通带到阻带有过渡带比如你在$f_s/2$附近刚好需要的那段频率滤波器可能已经衰减了3 dB或者阻带还没完全压下去。工程上的常规做法是取2.56倍到5倍。2.56这个数字来自早期电力谐波分析的标准做法留出约28%的过渡带给滤波器如果对高频噪声比较敏感或者用的是比较廉价的采集卡我会直接取5倍。比如要分析10 kHz以内的振动采样率我不会低于25.6 kHz常规会选51.2 kHz。混叠之后频率会怎么折公式是$$f_{alias} \left| f - \mathrm{round}\left(\frac{f}{f_s}\right) \cdot f_s \right|$$举例$f_s 1000$ Hz信号里混进一个1300 Hz的成分。$1300/1000 1.3$四舍五入到1$|1300 - 1000| 300$ Hz。也就是说这个1300 Hz的东西会伪装成一个300 Hz的峰出现在频谱上而且看起来完全合理你根本分辨不出来。这就是为什么我判断一条频谱是否可信第一件事不是看峰值而是看高频段有没有异常抬升以及采集链路里的抗混叠滤波器是什么型号。如果对方答不上来那这张谱图的高频部分我就只当参考。3. 幅值总对不上先把泄漏和窗函数的账算明白3.1 非整周期截断峰值摊成裙子边的真正原因DFT的隐含假设是你给它的这N个点会无限周期延拓。它默认信号在末尾之后接着从开头重新开始周而复始。如果信号频率刚好是$\Delta f$的整数倍那么截断点首尾自然衔接延拓之后是连续光滑的DFT给出的谱线又尖又准一根谱线对应一个频率这叫整周期采样。但现实里信号频率和$\Delta f$很少有这种缘分截断处会出现一个跳变。这个跳变在频域表现成什么表现成一片拖尾能量从主峰泄漏到邻近的频点上主峰变矮变胖旁边多出一堆“裙子边”。这就是泄漏。用卷积的语言说更准确时域乘窗等于频域卷积窗谱。矩形窗的频谱是Dirichlet核长得像sinc函数主瓣有宽度旁瓣无穷延伸。信号频谱和它一卷原来的冲激就被抹成了sinc形状。旁瓣衰减得慢远端的强信号就能把近端的弱信号淹没。这里必须讲清楚一个后果泄漏带来的不只是“不好看”还有幅值误差。当信号频率落在两根谱线正中间时幅值损失最大这个损失叫扇贝损失scalloping loss。矩形窗最坏情况下幅值只剩真实的63.7%误差超过36%Hann窗最坏情况幅值损失约1.42 dB约15%Flat-top窗专门为了压这个误差设计扇贝损失小到0.01 dB量级。所以如果你测的是一个标准正弦信号读数却比真值低了30%多而且这个信号频率明显不是$\Delta f$整数倍那基本可以确定是扇贝损失加泄漏在作祟换个窗就好。3.2 六种常用窗函数的取舍表窗函数没有最好的只有最合适的。选择的核心是看你要保什么保幅值精度还是保频率分辨还是保动态范围强弱信号共存。下面这张表是我自己常备的速查表窗类型相干增益CG等效噪声带宽ENBWbin最高旁瓣旁瓣滚降典型适用场景矩形窗1.001.00-13 dB20 dB/十倍频整周期采样、瞬态与冲击信号、需要最窄主瓣Hann0.501.50-31 dB60 dB/十倍频通用首选大多数稳态信号Hamming0.541.36-43 dB20 dB/十倍频需要低旁瓣但不想让主瓣太宽Blackman0.421.73-58 dB60 dB/十倍频强弱成分并存动态范围要求高Flat-top0.223.77-93 dB主瓣平坦幅值标定优先不在乎分辨率Kaiser可调可调可调可调需要按参数精细折中读懂这张表的几个要点。相干增益CG是幅值归一化要用的系数它等于窗函数的平均值等效噪声带宽ENBW决定了加窗后噪声底噪会抬高多少ENBW越大底噪抬得越高最高旁瓣决定强信号旁边多远处能看见弱信号主瓣宽度则直接对应频率分辨率的下限。我的实际选择习惯是不确定就先用Hann。它是工程上最稳的默认选项旁瓣低、滚降快、主瓣宽度适中绝大多数振动、音频、通用信号分析都够用。只有在两种极端情况下才换一是要做精确幅值标定比如校准传感器灵敏度换Flat-top二是分析瞬态冲击、脉冲响应这类短事件换矩形窗。矩形窗对冲击信号最友好因为冲击本身频谱是宽的加窗反而会把冲击的宽带特性削掉。还有一个容易忽略的点加窗会降低信号的总能量。矩形窗的$N$个点的平均值是1Hann窗只有0.5相当于把信号“整体压低”了一半。如果你不补偿幅值就少算一半这在后面归一化那一节会讲怎么处理。3.3 归一化三件套单边谱、相干增益、噪声功率带宽这是把DFT输出变成有物理意义的幅值谱的最后一步也是出错最多的一步。我把它拆成三个动作。动作一双边转单边。实数信号的DFT输出是共轭对称的负频率那半边是重复信息。要把它们合并到正频率上正频率的每个bin要乘2。但有三个例外直流bink0不乘2奈奎斯特binkN/2仅N为偶数不乘2因为这两个bin的镜像就是自己乘2就重复计算了。动作二补偿相干增益。除以窗函数的和而不是除以N。对矩形窗窗和等于N等价于除以N对Hann窗窗和等于$0.5N$相当于除以$0.5N$也就是乘2。这一步就是补偿刚才说的“整体压低”。写成公式$$A[k] \frac{2 \left| X[k] \right|}{\sum_{n0}^{N-1} w[n]}, \quad k 1, 2, \dots, \frac{N}{2}-1$$动作三区分幅值谱和功率谱密度。如果你要的是确定性信号正弦、谐波的幅值用上面的公式单位就是信号本身的单位V、g、m/s²。如果你处理的是随机信号噪声、宽带振动单看某个bin的幅值没意义因为bin的幅值本身是随机的应该看功率谱密度$$PSD[k] \frac{2 \left| X[k] \right|^2}{f_s \sum_{n} w[n]^2}$$这里的除数是$f_s \sum w^2$单位和“每Hz”对应最终单位是$V^2/Hz$或者$g^2/Hz$。用scipy.signal.welch的时候scalingspectrum给的是幅值谱scalingdensity给的是PSD选错的话数值能差好几个数量级。还有个验证公式很有用Parseval定理的离散版本$$\sum_{n0}^{N-1} \left| x[n] \right|^2 \frac{1}{N} \sum_{k0}^{N-1} \left| X[k] \right|^2$$时域的总能量等于频域的总能量差一个$1/N$因子。当你怀疑自己的归一化是不是做对了把时域算一遍、频域算一遍对得上就说明整条链路没问题。这个自检我几乎是每次换代码必做一次的。4. 一个能自证的例子从造信号到幅值谱全流程4.1 先造一条“标准答案已知”的信号学FFT最有价值的方法不是拿真实数据试而是先造一条答案已知的信号跑通全流程。真数据的问题是你不确定结果对不对而合成信号你清楚地知道每个频率在哪、幅值多少、相位多少任何偏差都是自己代码的问题。我设计的这条测试信号包含四个要素分别对应四类常见坑一个直流偏置0.05用来检验直流bin的处理是否正确一个100 Hz、幅值1.0的正弦100 Hz是$\Delta f$的整数倍属于整周期采样用来验证“理想情况”下能不能还原到1.0一个253 Hz、幅值0.3、初相0.7的正弦253 Hz不是$\Delta f$的整数倍专门用来制造泄漏和扇贝损失检验窗函数和归一化一点标准差0.01的高斯白噪声用来检验底噪水平和动态范围表现。参数设置采样率$f_s 2000$ Hz时长$T 1$ s所以$N 2000$点$\Delta f 1$ Hz。253除以1不是整数吗等一下$\Delta f 2000/2000 1$ Hz253是整数那它反而是整周期采样了。这就是设计时要小心的地方。为了制造真正的非整周期情况我把$f_2$改成253.4 Hz这样它落在第253和第254根谱线之间扇贝损失和泄漏就都出来了。4.2 完整代码与逐段说明import numpy as np import matplotlib.pyplot as plt # ---------- 1. 造信号 ---------- fs 2000.0 # 采样率 Hz T_total 1.0 # 采样时长 s N int(fs * T_total) # 点数 t np.arange(N) / fs # 时间轴注意用 arange/N 而不是 linspace rng np.random.default_rng(0) A1, f1, ph1 1.00, 100.0, 0.0 # 整周期成分 A2, f2, ph2 0.30, 253.4, 0.7 # 非整周期成分 dc 0.05 # 直流偏置 noise_std 0.01 x (dc A1 * np.sin(2 * np.pi * f1 * t ph1) A2 * np.sin(2 * np.pi * f2 * t ph2) noise_std * rng.standard_normal(N)) # ---------- 2. 预处理去均值 去趋势 ---------- x_detrend x - np.mean(x) # 去掉直流避免DC泄漏抬高低频底噪 # ---------- 3. 加窗 ---------- w np.hanning(N) # Hann窗通用首选 xw x_detrend * w # ---------- 4. FFT ---------- X np.fft.rfft(xw) # 只算正频率返回 N/21 个点 freq np.fft.rfftfreq(N, d1.0/fs) # 频率轴间隔 fs/N # ---------- 5. 幅值归一化 ---------- win_sum np.sum(w) # Hann窗约等于 0.5*N amp np.abs(X) * 2.0 / win_sum # 双边转单边 相干增益补偿 amp[0] / 2.0 # 直流bin不乘2 if N % 2 0: amp[-1] / 2.0 # 奈奎斯特bin不乘2 # ---------- 6. 对数谱 ---------- amp_db 20 * np.log10(np.maximum(amp, 1e-12)) # ---------- 7. 找峰并做抛物线插值 ---------- def refine_peak(mag, k): 用相邻三个bin的幅值做抛物线插值返回更精确的频率和幅值 if k 0 or k len(mag) - 1: return k * fs / N, mag[k] a, b, c mag[k-1], mag[k], mag[k1] denom a - 2*b c if abs(denom) 1e-15: return k * fs / N, b delta 0.5 * (a - c) / denom f_est (k delta) * fs / N a_est b - 0.25 * (a - c) * delta return f_est, a_est half N // 2 k1 np.argmax(amp[1:half]) 1 f1_est, a1_est refine_peak(amp, k1) print(f100Hz分量: 估计 {f1_est:.3f} Hz / {a1_est:.4f} 真值 100.000 Hz / 1.0000) # 在100Hz附近屏蔽后找第二个峰 mask np.ones_like(amp, dtypebool) mask[max(0, k1-5): k16] False k2 np.argmax(np.where(mask, amp, 0)[1:half]) 1 f2_est, a2_est refine_peak(amp, k2) print(f253Hz分量: 估计 {f2_est:.3f} Hz / {a2_est:.4f} 真值 253.400 Hz / 0.3000)代码里有几个地方是刻意这么写的说一下理由。时间轴用np.arange(N) / fs而不是np.linspace(0, T, N)。后者会把最后一个点定在$tT$实际上第$N$个采样点的时刻是$(N-1)/f_s$用linspace相当于把整条时间轴压缩了$1/N$的比例做相位敏感的分析时会有系统性偏差。去均值放在加窗之前。这个顺序不能反。如果先去均值再加窗窗函数会把信号两端压到接近零再减去均值时会引入一个两端为零、中间为负的奇怪形状反而制造了低频成分。正确顺序永远是先去均值去趋势再乘窗。np.fft.rfft而不是np.fft.fft。前者只算正频率输出$N/21$个复数省一半计算量和内存而且频率轴直接用rfftfreq就能对上不容易出错。np.fft.fft的输出是未归一化的绝对值会随着$N$线性增长这是很多人“幅值差了N倍”的直接原因。抛物线插值看着多余其实很实用。当信号频率落在两个bin之间时谱线峰值会偏移直接用argmax读出来的频率误差最大能到半个$\Delta f$。用相邻三个bin做二次插值频率估计精度能提升一到两个数量级我实测过在信噪比20 dB以上时253.4 Hz能读到253.39左右。这段代码就几行加上去很值。4.3 误差对照结果应该对到什么程度跑完这段代码我的机器上得到的结果大概是这样的分量真值频率估计频率真值幅值估计幅值幅值相对误差整周期成分100.000 Hz100.000 Hz1.00001.0000 0.1%非整周期成分253.400 Hz253.39 Hz0.30000.2965约 1.2%底噪水平0.01——约 0.001/根—这张表本身就是一份验货标准。整周期成分能还原到0.1%以内说明归一化和窗函数补偿对了非整周期成分误差在1%到2%之间这是Hann窗扇贝损失加泄漏的固有代价换Flat-top窗能压到0.1%以内但代价是主瓣变宽3倍多频率分辨变差底噪那一行看着奇怪其实是因为Hann窗ENBW是1.5噪声能量被摊到1.5个bin上单根谱线的幅值会被压低这不是bug是正常的。如果你的结果和这张表差得离谱比如整周期成分算出来是2.0而不是1.0那基本可以锁定是单边谱乘2乘错了如果算出来是0.5而不是1.0那就是忘了补偿窗的相干增益如果频率轴整体偏移那就是频率轴公式写错了。这张表的价值就在于它让问题变得可以定点排查。5. 现场最常翻车的几个点以及我的排查顺序5.1 幅值差2倍、差N倍、差√2各是什么原因幅值不对是最常见的症状但不同的倍率关系指向不同的原因可以像查故障码一样定位观测到的现象最可能的原因确认方法幅值正好是N倍比如2000倍忘了归一化直接用了np.fft.fft的原始输出打印np.abs(X).max()看是不是随N线性变化幅值正好是2倍单边谱该乘2的地方乘了两次或DC/Nyquist不该乘2却乘了单独检查k0和kN/2两个bin幅值正好是0.5倍加窗后忘了补偿相干增益把除数从N改成sum(w)再看幅值约1.414倍√2峰值和RMS混用把正弦有效值当成峰值明确信号的标定方式Vpp、Vpk还是Vrms幅值差一个不大不小的比例如0.637扇贝损失信号频率没有落在bin上用Flat-top窗重算一遍对比这张表我基本上可以照着念。印象最深的一次是同事拿着一条频谱来问“为什么这个振动幅值是示波器读数的一半”查了半天才发现他用的采集软件默认输出的是单边谱但没做乘2改成双边谱输出就一致了。这类问题不难难的是不知道往哪个方向查有这张表就能少绕很多弯。5.2 频率轴用了 k*fs/(N-1)这是最隐蔽的错这个错误很隐蔽因为它的影响随$N$增大而减小$N$大的时候几乎看不出来只有点数少的时候才暴露。原因是很多人把时域的“$N$个点分成$N-1$段”这个直觉错误地搬到了频域。正确的频率轴是$f_k k \cdot f_s / N$分母是$N$不是$N-1$。验证方法很简单拿一个已知频率的信号看峰值落在哪里。我用$f_s 1000$ Hz、$N 100$点、信号100 Hz如果频率轴写成$k \cdot 1000/99$峰值的读数会变成100.1 Hz左右如果写成正确的$k \cdot 1000/100$读数就是100.0 Hz。$N100$时误差是0.1%$N10000$时误差就只剩0.001%肉眼看不出来。所以这个小错误常常潜伏很久直到某天处理一批短数据才突然现身。我现在写代码一律用np.fft.rfftfreq(N, d1/fs)不自己手写频率轴从源头上杜绝这个可能。5.3 直流分量、趋势项和加窗前的去均值直流分量在频谱上表现为0 Hz处的一个巨大峰看起来无害但它会带来两个连锁反应。一是通过泄漏直流能量会拖到低频段把本该干净的底噪抬起来让100 Hz以下的弱信号彻底看不见。二是如果信号里有缓慢漂移趋势项它会表现为低频的宽带抬升看起来像是“有低频振动”其实是传感器漂移。处理顺序是先去均值再去趋势最后加窗。去均值用x - x.mean()去趋势用scipy.signal.detrend一般选线性去趋势就够了。别小看这一步我处理过一批加速度数据不去趋势的时候20 Hz以下全是抬升去完趋势之后才发现真正的低频成分在35 Hz之前的分析方向完全错了。还要提醒一句去均值必须在加窗之前做。加窗之后再减均值等于在一个已经变形的信号上做减法结果会更怪。这个顺序没有商量余地。5.4 随机信号别只看单次谱Welch平均与重叠确定性信号正弦、谐波、周期振动单次FFT就够了但随机信号不行。随机信号的每个bin幅值本身就是一个随机变量单次谱的毛刺能大到让你误判。这时候要用Welch方法分段、加窗、算功率、平均。from scipy import signal f_w, Pxx signal.welch( x, fsfs, windowhann, # 窗函数 nperseg512, # 每段点数决定分辨率 noverlap384, # 重叠点数75%重叠 detrendconstant, # 每段各自去均值 scalingdensity, # density给PSDspectrum给幅值 return_onesidedTrue ) amp_rms np.sqrt(Pxx * (f_w[1] - f_w[0])) # 换算成每根谱线的RMS幅值几个参数的选择逻辑nperseg决定分辨率$\Delta f f_s / nperseg$512点对应3.9 Hz想分辨更细就加大但段数会减少、平均效果变差noverlap一般取50%到75%重叠越多段数越多、方差越小但相邻段不独立改善有上限75%是个不错的折中detrendconstant让每段各自去均值避免段间直流差异造成低频抬升。Welch的本质是用方差换分辨率这个取舍要显式做出来。我一般的做法是先用一个较大的nperseg看细节确认没有遗漏后再用较小nperseg加多重叠得到平滑的总体趋势两张图放一起看一张看峰一张看底。5.5 单位与量纲g、m/s²、dB、dBFS别混着用这是最容易在报告里出错的地方因为图看起来都对只是数值含义不同。几个必须理清的关系加速度传感器输出常以g为单位$1g 9.81,\mathrm{m/s^2}$报告里写清楚用的是哪个两种单位的数值差近10倍dB是相对量必须标注参考值。振动领域常见写作“dB re $1,\mu\mathrm{m/s^2}$”声学里是“dB SPL re $20,\mu\mathrm{Pa}$”不提参考值的dB没有意义幅值谱的纵轴是峰值还是RMS要明确。正弦信号的峰值是RMS的$\sqrt{2}$倍写错就差了41%如果数据是从ADC采来的原始码值必须先用LSB换算成电压。ADC的量程和位数决定LSB比如16位、±5 V量程LSB就是$10/65536 \approx 0.153$ mV不换算的话频谱纵轴毫无物理意义对数谱用$20\log_{10}$而不是$10\log_{10}$因为纵轴是幅值不是功率。这个用错整条曲线的斜率都会变形。我的习惯是在绘图代码里把单位直接写进轴标签比如ylabel(加速度幅值 (m/s², 峰值))不给后续解读留歧义空间。图注里再写清楚采样率、点数、窗函数、分辨率这四要素别人拿到图就能复现这才算一份合格的频谱。最后分享一个我坚持了很多年的习惯每换一台采集设备、每改一次分析代码都先用一个已知信号跑一遍全流程。有条件的用信号发生器给一个1 kHz、1 Vpp的正弦没条件的就用上面那段合成信号代码。看频率读数对不对、幅值读数对不对、底噪水平合不合理三项都对上了再去碰真实数据。这个习惯看着笨但它替我省下的返工时间比任何优化技巧都多。频谱分析这件事快不是本事能自证才算数。