卷 II · 只看几眼CH 06深度 6/20

折回来的

采样率不够的时候,大部分人预期的结果是「高频变模糊」或者「高频没了」。都不是。高频既不模糊也不消失,它换一个身份走进低频区,然后住下来——伪装得完美到,事后再高明的算法也认不出它是外来户。这一章给出这本书里最不讲道理的一组数。

★★ 两个声音,一份数据2.39e−12事后无救

▷ 先猜一下

两段完全不同的声音:一段是 1400 Hz 的纯正弦,一段是 400 Hz 的纯正弦。放给人听,一个尖,一个闷,谁都分得出来。

现在用 1000 Hz 采样率去采它们(注意:1000 Hz 对 1400 Hz 来说远远不够,奈奎斯特只到 500 Hz)。各采一千个数。

问:这两串数,最大差多少?

A 差不多是 2。两个完全不同的信号,采出来当然完全不同 B 差 0.5 上下。有点像但明显不一样,用统计方法还是分得开 C 最大差 10⁻¹² 量级——数学上就是同一串数,浮点误差而已 D 取决于两个正弦的起始相位怎么配。配得巧才会一样

先看这组数

fs = 1000 Hz,各采 1000 个点

  a[n] = sin(2π · 1400 · n/1000)
  b[n] = sin(2π ·  400 · n/1000)

  ✓ 逐点最大差              ⟨2.39 × 10⁻¹²⟩
    严格逐位相等的点数       1 / 1000
    (其余 999 个点差在 10⁻¹² 量级,是 sin 的舍入,不是信号的差别)

  而这两个信号在连续时间上呢:
  ✗ 最大差                  1.9768        ← 峰值只有 1,这已经是最大可能的差别

连续时间上,它们几乎处处不同,差到不能再差。在采样点上,它们是同一串数。

为什么:又是整数圈

这件事在纸上只有一行:

在采样时刻 t = n/fs 上,把频率加上 fs:

  sin( 2π(f + fs) · n/fs )
    = sin( 2πf·n/fs + 2π·n )
    = sin( 2πf·n/fs )              ← 因为 n 是整数,多转的正好是 n 整圈

于是:频率相差 fs 的整数倍的两个正弦,
      在采样点上给出**完全相同**的一串数。

1400 = 400 + 1000 → 采出来和 400 Hz 一模一样。

又是「整数圈」。第 3 章那条恒等式的三个前提里,第二条(m 不是 N 的倍数)在这里被破坏了:1400 Hz 的圈和 400 Hz 的圈,在这个采样格点上根本不是两个圈,是同一个圈。

这也是「别名」(alias)这个词的来历——它不是一个近似值,是同一个东西的另一个名字

◆ 主线

采样不足的时候,高频不会消失,它会以一个低频的身份出现在你的数据里,并且和真的那个低频完全不可区分。

「不可区分」这四个字是字面意思:不是难分辨,是信息论意义上没有区别。你手里那串数对应无穷多个原信号,采样这个动作已经把它们的差别抹平了。任何声称能事后分开它们的算法,都只是在偷偷引入额外假设。

折回来的落点:一张表

高频会折到哪儿?规则是:先对 fs 取余,如果超过一半就用 fs 减掉它。形象地说,频率轴在 0 和 fs/2 这两处装了镜子,超出去的部分被来回反射回来:

fs = 1000 Hz,奈奎斯特 = 500 Hz

  真实频率        采出来长什么样
  ──────────────────────────────
   100 Hz    →      100 Hz        ✓ 本来就在范围内
   400 Hz    →      400 Hz        ✓
   600 Hz    →     ⟨400 Hz⟩       ← 折了
   900 Hz    →     ⟨100 Hz⟩
  1100 Hz    →     ⟨100 Hz⟩
  1400 Hz    →     ⟨400 Hz⟩
  1600 Hz    →     ⟨400 Hz⟩
  1900 Hz    →     ⟨100 Hz⟩
  2400 Hz    →     ⟨400 Hz⟩
  ──────────────────────────────
  注意 400 / 600 / 1400 / 1600 / 2400 全都折到了同一个地方。
  它们在你的数据里已经加成了一个数,加法不可逆。

换成 CD 的采样率 44100 Hz,奈奎斯特是 22050 Hz:
  一个 30000 Hz 的成分  →  ⟨14100 Hz⟩      正好落在人耳最敏感的区段附近

这张表解释了一个常见的困惑:为什么混叠产生的假频率听起来那么难听。因为它和真实的谐波结构完全没有关系——一个 3000 Hz 的谐波折回来可能落在 1100 Hz 上,而原声里 1100 Hz 上什么都没有。它是一个纯粹外来的音,和其他音之间没有任何和谐关系。这就是低码率数字音频那种「金属味」的一部分来源。

谱上看得更直接。以 1000 Hz 采 1400 Hz,做一次变换:

1000 点的变换,找最高的那根谱线:

  峰落在  bin 400  =  400.0 Hz
  而 1400 Hz 那个位置上:什么都没有(它根本不在这台机器能表示的范围里)

银幕上那些倒转的车轮

这本书里最容易亲眼看见的一次混叠。电影每秒 24 帧,也就是以 24 Hz 采样这个世界。一个有 12 根辐条的轮子,转过 1/12 圈就和没转一样——于是「辐条经过某个位置」这件事的频率是转速的 12 倍。

12 根辐条,24 帧/秒
隐形周期 = 24 / 12 = 2.0 转/秒  ← 每转到这个速度的整数倍,轮子看起来就是静止的

  真实转速        银幕上看起来
  ─────────────────────────────
  1.6 转/秒   →   ⟨−0.40⟩ 转/秒     倒着慢慢转
  1.8 转/秒   →   ⟨−0.20⟩ 转/秒     倒得更慢
  2.0 转/秒   →     0.00  转/秒     完全静止
  2.2 转/秒   →     0.20  转/秒     正着慢慢转
  3.9 转/秒   →   ⟨−0.10⟩ 转/秒     又开始倒
  4.0 转/秒   →     0.00  转/秒     又静止了

请注意 1.6 → −0.40 这一行:轮子明明在正着转,银幕上却在倒转。这不是错觉,也不是快门的问题——那是数据本身在说谎,而且说得毫无破绽。如果你只有这段视频,没有任何别的信息,你无法判断那个轮子到底往哪边转。

顺带一提,日光灯下用肉眼也能看到这个效果:日光灯每秒闪 100 次(50 Hz 交流,一周期亮两次),于是它对高速旋转的东西做了一次 100 Hz 的采样。老式车间要求机床附近用白炽灯或者错相位的多路日光灯,防的就是这个——一个高速旋转的刀盘在日光灯下可能看起来是静止的。

✎ 术语正名

抗混叠滤波器(anti-aliasing filter)这个名字容易让人以为它是「用来消除混叠的」。不是。它是用来防止混叠发生的,而且必须装在采样之前,是一个模拟电路。

已经混进去的东西,没有任何数字滤波器能拿出来——数据里那个 400 Hz 的成分,是「真 400 Hz」和「折回来的 1400 Hz」的,而你不知道各占多少。这一条在图形学里同样成立:一张已经出现了摩尔纹的照片,事后做任何处理都不可能恢复出原来的布料纹理。抗混叠必须在采样的那一刻完成,事后是不可逆的。

唯一的例外是你能重新采一次——比如换一个采样率再采一遍,两次的折叠位置不同,联立就能解出来。这正是医学成像和射电天文里「多速率采样」的思路。但它需要重新访问那个物理现象,不是对旧数据做处理。

⌨ 自己跑一遍

三行看见这一章的全部内容:

import math
fs = 1000
a = [math.sin(2*math.pi*1400*n/fs) for n in range(1000)]
b = [math.sin(2*math.pi* 400*n/fs) for n in range(1000)]
print(max(abs(x-y) for x, y in zip(a, b)))       # 2.39e-12

折叠规则也是三行:

def fold(f, fs):
    m = f % fs
    return m if m <= fs/2 else fs - m

for f in [100, 400, 600, 900, 1100, 1400, 1600, 1900, 2400]:
    print(f, '→', fold(f, fs))

如果你有耳朵可用,这个实验值得真的听一次:用 Audacity 生成一个从 100 Hz 扫到 20000 Hz 的扫频音,把工程采样率设成 8000 Hz(不要勾选任何重采样质量选项),你会听见音调升到 4000 Hz 之后掉头往下走,来回反弹。那正是上面那张表在耳朵里的样子。

python3 -c "import math;fs=1000;print(max(abs(math.sin(2*math.pi*1400*n/fs)-math.sin(2*math.pi*400*n/fs)) for n in range(1000)))"

在线跑:python.org/shell。想看图:Desmos 上画 sin(2π·1400x)sin(2π·400x),再把 x 限制在 n/1000 这些点上。

▸ 在现实里
  • 摩尔纹。拍屏幕、拍格子衬衫时出现的那些彩色波纹,就是二维的混叠:布料的高空间频率被传感器的像素网格折回成了一个低频图案。相机上那块「光学低通滤波器」(OLPF)就是抗混叠滤波器的光学版——它故意把画面弄糊一点点。近年不少相机把它去掉了,换取锐度,代价就是偶尔出摩尔纹。
  • 游戏里的 MSAA / FXAA。「抗锯齿」这个名字翻译得不太好,它英文就是 anti-aliasing。锯齿的成因和摩尔纹完全一样:几何边缘是无限高频的,而屏幕像素是一次采样。MSAA 的做法是「采样前先多采几次再平均」,本质上是在采样点上做局部积分。
  • 数字示波器上的假波形。用 100 MSa/s 的示波器看一个 60 MHz 的方波,屏幕上会显示一个漂亮的、频率完全不对的低频波。示波器不会报警,因为它没有办法知道。这是入门工程师最容易栽的一个跟头。
  • 业务数据的周期陷阱。「每周一取一次数」对一个有周内波动的指标来说是 1/7 Hz 采样;如果再叠上一个双周发版节奏,两个周期一混叠,就会在报表上长出一条根本不存在的「季度趋势」。周期性数据的采样间隔,永远要先问一句它和已知周期是什么关系。
✗ 这个直觉是错的

「采样率低一点没关系,先采下来,事后用数字滤波器把那些假的高频滤掉就行。」

滤不掉。折回来的东西不在高频区了——它就低频区,和真信号并排坐着,长得一模一样。你的数字滤波器只能看到「400 Hz 处有多少能量」,看不到这些能量里有多少是外来的。

上面那组数就是证据:两串数最大差 2.39 × 10⁻¹²。任何算法拿到的输入都是同一份,输出当然只能是同一个。这不是算法水平问题,是信息已经不存在了。

正确的顺序永远是:先在模拟域把高频砍掉,再采样。这也解释了为什么高端 ADC 常用「过采样 + 数字抽取」——先用远高于需要的采样率采(这时模拟滤波器可以做得很温和,因为过渡带极宽),采完在数字域用一个陡峭的滤波器把高频砍干净,再降采样。数字滤波器可以很陡,代价是延迟;模拟滤波器要陡就很贵。过采样是拿存储和算力去换模拟电路的钱。

判据是:问「这个高频成分,在它被记下来之前有没有被处理过」——没有的话,你后面做什么都晚了。

◇ 揭晓

正确答案是 C:最大差 2.39 × 10⁻¹²。数学上是同一串数,那点差别是 sin 在做大角度求值时的舍入。

A 「完全不同的信号采出来当然不同」——这个直觉背后是一个隐含假设:采样保留了信号的「身份」。它不保留。采样只保留了信号在那些时刻的取值,而无穷多条不同的曲线可以在那些时刻取相同的值。这一章和上一章其实是同一件事的两半:上一章说「带限时这样的曲线只有一条」,这一章说「不带限时有无穷多条,而且你选不出来」。 B 「用统计方法还是分得开」——只要输入的比特完全相同,任何确定性算法的输出就必然相同,统计方法也不例外。能帮忙的只有额外信息:比如你事先知道「这段录音里不可能有 1400 Hz」(那就等于你已经知道答案了),或者你能换一个采样率再采一次(那是重新访问物理世界,不是处理旧数据)。 D 「取决于起始相位」——不取决于。上面那个恒等式对任何相位都成立,因为多转的永远是整数圈。事实上还有一个稍微微妙的情形:如果频率高出的部分不是 fs 的整数倍而是「过了一半再折回来」(比如 600 Hz 折到 400 Hz),那么折回来的信号相位会取共轭,也就是时间上镜像。幅度谱上看不出来,相位谱上看得出来——第 4 章那半张脸在这里又有用了。
⟳ 换个域看
1400 Hz 和 400 Hz 采出来是同一串数——这在时域里是一个需要死记的怪事,除了「代进去算一下」没有别的理解方式。 同一件事,是上一章那张频谱周期化的图里,两个副本压到了一起。1400 Hz 那根线属于「往左平移了 1000 Hz 的那个副本」,落点正好是 400。

这一次换域给出的不只是解释,是整张地图:为什么折叠点是 fs/2(那是两个副本的交界)、为什么会来回反射(副本是左右对称的,因为实信号的谱共轭对称)、为什么折回来的分量相位取共轭(那正是共轭对称的意思)、为什么抗混叠必须在采样之前(副本一旦相加就再也拆不开)。

四个结论,一张图。如果你只在时域里想这件事,这四条是四条互不相干的规则;换到频域,它们是同一张图看四眼。

这一章的一句话

采样不足的时候,高频不会消失也不会模糊,它会顶着一个低频的名字住进你的数据里,和真的那个低频逐位相同——所以抗混叠必须发生在采样之前,事后没有任何算法能补救。

前两章处理的是「一眼一眼地看」。下一章处理另一条限制:你只能看有限长的一段。这条限制的代价用一个公式就能说完:分辨率 = 1/T。它会导出一个让很多人意外的结论——想把 1000 Hz 和 1002 Hz 分开,提高采样率一点用都没有,把 1024 个点补零到 8192 个也一点用都没有。唯一的办法是多听 0.5 秒。