一个纯音是怎么糊成一片的
这一章处理第 2 章埋的那个伏笔:拿 3.5 圈/秒去缠一段根本没有 3.5 Hz 的信号,机器报了 0.2315。这个数不是误差,也不是噪声——它是你「只看一段」这个动作本身的产物。而它能有多大,是这一卷最难看的一笔账。
1000 个采样点,采样率 1000 Hz(也就是记录长度正好 1 秒)。信号是一个绝对纯净的余弦,没有噪声,没有其他成分,双精度浮点。
做一次变换,然后问:装下 99% 的能量,需要多少根谱线?
两种情况:
① 余弦的频率是 100 Hz(在这一秒里正好转 100 整圈,正好落在谱线上)
② 余弦的频率是 100.5 Hz(正好落在两根谱线的正中间)
先看两张单子
情况 ① f = 100 Hz(正好整数圈) 最高的那根谱线 500.0 其余 499 根里最大的 8.9 × 10⁻¹² ← 浮点意义上的零 装下 99% 能量需要 1 根 情况 ② f = 100.5 Hz(挪了半格) 最高的那根谱线 319.0 ← 峰值本身矮了 ⟨−3.90 dB⟩ 装下 99% 能量需要 ⟨41 根⟩ 峰两侧各一格之外还剩 ⟨9.9%⟩ 的能量
频率只挪了 0.5 Hz(相对量 0.5%),99% 能量的落点从 1 根谱线摊到了 41 根,峰值本身还矮了 36%。
这里最要命的一点是:信号本身一点都没变脏。它还是那个绝对纯净的余弦。多出来的四十根谱线,全部是分析过程造出来的。
上面那张是情况 ①:一根线,旁边什么都没有(那些 10⁻¹² 量级的东西根本画不出来)。下面这张图是情况 ② 的绕圈视角:
为什么:你不是在分析信号,你在分析「被剪断的信号」
上一章末尾已经点出了机制。你手里那一秒钟的数据,等于
你分析的东西 = 真信号 × 一个矩形窗
┌────────────┐
矩形窗: │ │ 窗内是 1,窗外是 0
───────────┘ └───────────────
0 1 秒
而时域相乘,在频域里是卷积(第 14 章的定理,这里先用)。所以:
你看到的谱 = 真谱 ✻ 矩形窗的谱 真谱是一根无限细的线(一个纯音)。 一根线和任何东西卷积,得到的就是**那个东西本身**,只不过挪到了线的位置上。 于是:**你看到的,就是矩形窗的频谱,被搬到 100.5 Hz 那儿去了。**
那么矩形窗的频谱长什么样?它是一个 sinc 形状(准确说是狄利克雷核):中间一个胖主瓣,两侧一串越来越矮但衰减很慢的旁瓣。本机把它精细采样出来量了一下:
矩形窗的频谱(64 点窗,64 倍过采样) 主瓣全宽 2.0 个格子 最高旁瓣 ⟨−13.26 dB⟩ ← 只比主峰低 4.6 倍 旁瓣衰减速度 每倍频程 6 dB ← 非常慢
−13.26 dB 是个很糟糕的数:紧挨着主峰的那个假峰,高度是真峰的 22%。而且旁瓣衰减得极慢,一路拖到频谱另一头去。
那为什么情况 ① 一点事都没有?因为那些旁瓣正好落在谱线的缝隙里。狄利克雷核的零点间隔恰好是一个格子,而 100 Hz 正好在格子上——于是每一根其他谱线都精确地采在了一个零点上。
情况 ① (频率落在格子上):
谱线位置: | | | | | |
窗的形状: ─────────╱▔▔▔╲─────────────────
↑ 只有这一根采到了主峰
其余每一根都采在零点上 → 8.9e−12
情况 ② (频率挪半格):
谱线位置: | | | | | |
窗的形状: ────╱▔▔▔▔▔╲──────────────────
↑ 峰落在两根中间,两边各采到一半
而所有零点都错开了 → 每一根都采到一点东西
这就是全部机制。情况 ① 的干净是一个巧合,情况 ② 才是常态——你不可能事先知道那个音是 100.0 还是 100.5 Hz,然后据此选一个整除的窗口长度。
频谱泄漏不是噪声,是你「只取一段」这个动作的频域投影。它有三个后果,每一个都会独立地坑人:
- 能量摊开:本该在一根线上的东西,摊到几十根上(1 → 41)。
- 峰值变矮(扇贝损失):读出来的振幅偏小,最坏时 −3.90 dB,也就是只有真值的 64%。
- 掩盖弱信号:一个强音的旁瓣可以整个盖住一个真实存在的弱音。这是三条里最贵的一条,下一章会算出它有多贵。
而这三条都是同一个东西造成的:矩形窗的频谱形状。那么解决办法自然也只有一个:换一个形状更好的窗。这就是下一章。
顺手把第 2 章那个 0.2315 结清
第 2 章里,我们拿 3.5 圈/秒去缠一段只有 3 Hz 和 5 Hz 的信号,得到质心 0.2315。现在可以说清楚了:那不是「3 Hz 蹭过来的」,也不是「5 Hz 蹭过来的」,而是
0.2315 = 3 Hz 那根线的泄漏 + 5 Hz 那根线的泄漏
= 狄利克雷核在偏离 0.5 格处的值 × 1.0
+ 狄利克雷核在偏离 1.5 格处的值 × 0.5
(两者还带符号,会部分抵消)
换句话说,第 2 章那台机器在非整数速度上给出的那些「不该有的数」,全部是这一章的泄漏。它不是机器的缺陷,是「一秒钟」这个边界的代价。
扇贝损失(scalloping loss)这个奇怪的名字,来自频率响应曲线的形状:把峰值随「频率偏离格子多少」画出来,得到一串扇贝壳一样的弧。矩形窗最深处是 −3.92 dB(本机在离散情形下量到 −3.90 dB)。
它的实际含义很吓人:如果你用 FFT 直接读峰值来测振幅,误差可以达到 36%,而且这个误差完全取决于被测频率碰巧落在哪儿。同一台设备,测 100.0 Hz 读出 500,测 100.5 Hz 读出 319——很多「设备不稳定」的报告,根子在这里。
下一章的平顶窗(flat-top)就是专门为这件事发明的:它的扇贝损失只有 −0.01 dB,代价是主瓣宽到 10 个格子。校准用平顶窗,分辨用汉宁窗,是频谱分析仪上那个下拉框存在的全部理由。
把两张单子跑出来,十几行:
import numpy as np
N = fs = 1000
def report(f):
n = np.arange(N)
m = np.abs(np.fft.fft(np.cos(2*np.pi*f*n/fs)))[:N//2]
e = m**2
srt = np.sort(e)[::-1]
need = int(np.searchsorted(np.cumsum(srt), 0.99*e.sum()) + 1)
print(f'f={f} 峰值 {m.max():.1f} 99% 能量需要 {need} 根谱线')
report(100) # f=100 峰值 500.0 99% 能量需要 1 根谱线
report(100.5) # f=100.5 峰值 319.0 99% 能量需要 41 根谱线
值得多试几个:100.1(99% 只要 5 根)、100.25(21 根)、100.5(41 根,最坏)。泄漏的严重程度是「离格子有多远」的连续函数,半格处最坏。
再看看扇贝损失那条曲线:
for d in (0, 0.1, 0.2, 0.3, 0.4, 0.5):
n = np.arange(N)
m = np.abs(np.fft.fft(np.cos(2*np.pi*(100+d)*n/fs)))[:N//2].max()
print(f'偏离 {d:.1f} 格 峰值 {m:6.1f} {20*np.log10(m/500):6.2f} dB')
python3 -c "import numpy as np;N=1000;n=np.arange(N);print(np.abs(np.fft.fft(np.cos(2*np.pi*100.5*n/N))).max())"
在线跑:Google Colab。想直观看:任何一款频谱分析软件(Audacity 的「绘制频谱」)里切换窗函数,同一段音频的谱底会整个抬起或压下——那就是这一章。
- 振动监测里的轴承故障。一台电机的振动谱上,转频那根线极高,而轴承早期故障的特征频率是一根很小的线,可能低 40 dB。用矩形窗的话,转频的旁瓣(−13 dB 起步)会把它整个埋掉——设备已经在报警,而你的分析工具把警报盖住了。这是工业上换窗函数最主要的动机。
- 音频里的「假谐波」。拿一段人声做频谱,会看到基频周围一堆小尖尖。新手会以为那是嗓音的细节,其实大部分是泄漏。换成汉宁窗,它们会塌下去一大截。
- 射电天文的谱线搜索。要在宽带噪声里找一条极窄的谱线(比如 21 cm 氢线),最怕的就是某个强源的泄漏在那个位置上堆出一个假峰。这个领域对窗函数的讲究程度,远超过一般工程。
- 你的性能火焰图。这不是频谱,但同一个道理:采样式性能分析器每隔 10 ms 采一次栈,如果你的程序里有一个周期恰好接近 10 ms 的循环,采样结果会严重偏斜——那是第 6 章的混叠;而如果你只采了 100 个样本就下结论,那是第 7 章的分辨率。这两章加上这一章,是「用有限的观测推断一个系统」这件事的三张账单。
「频谱上主峰旁边那些小尖尖是噪声。信号采得干净一点、平均几次,它们就会降下去。」
平均不会让它们降。噪声是随机的,多次平均会按 √n 变小;泄漏是确定性的,你平均一万次它一模一样。这是区分二者最可靠的判据:再采一段,看那些小尖尖会不会出现在完全相同的位置、相同的高度。会的话,那是泄漏。
上面那张单子是最干净的证据:那个信号没有任何噪声,是双精度浮点算出来的纯余弦,而 99% 能量摊到了 41 根谱线上。这些能量一个比特都不是噪声,全部来自「只取了一秒」这个动作。
还有一个连带的错误值得一起纠正:「补零之后频谱上冒出更多小峰,说明分辨出了更多成分」。上一章已经说过补零不增加信息——那些新冒出来的小峰,正是本来藏在谱线缝隙里的泄漏旁瓣,补零只是把它们画了出来。判据是:真实成分的位置不随窗口长度改变,泄漏旁瓣的位置会随窗口长度成比例移动。
正确答案是 C:1 根 / 41 根。峰值还从 500.0 掉到了 319.0(−3.90 dB)。
A 「信号是纯的,谱当然也是纯的」——这个直觉的漏洞在于「信号」到底指什么。你分析的不是那个纯余弦,而是「纯余弦 × 矩形窗」。后者一点也不纯:它在 0 秒和 1 秒处各有一个突然的跳变,而跳变是宽频的东西。你看到的所有多余频率,都是那两个跳变的谱。 B 「只会分给旁边两根」——这是最接近的一个错,也是很多人的默认模型。它错在低估了旁瓣衰减有多慢:矩形窗的旁瓣按 1/k 衰减(每倍频程只降 6 dB),所以离峰 20 根谱线的地方还有可观的能量。要想真的「只分给旁边两三根」,需要一个旁瓣衰减快得多的窗——那正是下一章要买的东西,而它有明码标价。 D 「均匀撒满整个频谱」——撒得很开,但绝不均匀。狄利克雷核是有结构的:主瓣、第一旁瓣、第二旁瓣,一路衰减。如果它真是均匀的,反倒好办了(那就是白噪声底,可以减掉);难办的正是它有形状——那些旁瓣看起来非常像真实的谱峰。这个换域是这本书的一个转折点。在此之前,我们说「时域和频域是同一份数据的两套坐标」;从这里开始,你会一次次看到时域里的一个「无操作」,在频域里是一个很有分量的操作——剪断、补零、重采样、量化、丢帧,全都如此。
凡是你在时域里做过的手脚,频域都记着账。下一章就是去看这笔账能不能少付一点。答案是能,但要拿别的东西去换。
这一章的一句话
频谱上那些多余的东西,多数不是噪声,是你「只取了一段」这个动作在频域里的样子;它会摊开能量、压低峰值、并且用旁瓣盖住真实存在的弱信号。
卷 II 到此结束:一眼一眼地看,代价是混叠;只看一段,代价是分辨率和泄漏。下一卷讲怎么讨价还价——而这一卷的三笔账里,只有泄漏是可以谈的。下一章会把五种窗的价目表全部列出来,包括那个把最高旁瓣从 −13.3 dB 压到 −93.0 dB 的选项,以及它要你付出的东西。