Without any frequency analysis, what can the curve alone tell us?
The last lesson built the pipeline, but the “compute per frame” box is still empty. This lesson fills it with three time-domain answers: the amplitude envelope (the highest value in this frame), the root mean square, or RMS (how strong this frame is overall), and the zero-crossing rate (how many times this frame crosses the centre line). None of them needs any frequency analysis; they look only at the curve itself. They are not three interchangeable names but three different questions, each with its own formula, weaknesses and uses. This lesson works through all three formulas by hand on a small frame of eight numbers, so you can check them with a pen.
导读: 上一课把流水线搭好了,「逐帧计算」那一格还空着。这一课填上时域这条的三个答案:振幅包络(这一帧最高多少)、均方根(这一帧整体多强)、过零率(这一帧穿过中线多少次)。三个都不需要任何频率分析,只看那条曲线本身。它们不是三个可以互换的名字,而是三个不同的问题——每一个都有自己的公式、自己的弱点、自己的用武之地。这一课把三个公式各拿一个八个数的小帧手算一遍,你拿笔就能核对。
三个时域特征
时域特征就是直接从那条曲线算出来、不做频率分析的特征。本课讲三个最常用的,先各用一句话说清它们分别是什么:
- 振幅包络指的是:一帧里最高的那个数有多大。英文缩写 AE。
- 均方根指的是:一帧整体上有多强,帧里每个数都参与。英文缩写 RMS。
- 过零率指的是:一帧里那条曲线穿过中线多少次。英文缩写 ZCR。
图 1:三个特征各问一个不同的问题。三个答案都在手上,才知道这一帧是「短促一下」还是「一直很响」。
下面三节各讲一个。为了能拿笔核对,三个公式都用同一个八个数的小帧算:
这一帧的样本数记作 。
振幅包络:这一帧里最高的那个数
定义:一帧里所有样本中,最大的那个振幅值。
公式看着吓人,逐项拆开只有四样东西:
| 符号 | 是什么 |
|---|---|
| 第 帧的振幅包络——要算的那个结果 | |
| 第 个样本的振幅——原始的那串数 | |
| 帧长,一帧有多少个样本 | |
| 第 帧的第一个样本的编号 | |
| 第 帧的最后一个样本的编号 |
所以这条式子读出来就是:在第 帧那一段里,从头扫到尾,取最大的那个。 对每一帧都这么算一次,把结果按时间连起来,就是一条贴着波形外沿走的线。
图 2:上面是公式各部分对应什么,下面是这八个数上的实际结果。注意最右边那两个数不一样。
照公式写代码会漏掉一半
拿这八个数跑一遍:
照公式直接取最大 0.7
先取绝对值再取最大 0.9
两个数不一样,差 0.2。
原因是 :它是负的,但它离中线的距离比 更远。这一帧真正的峰是 0.9,不是 0.7。
“最大振幅”里的振幅本来就指“离零有多远”,不带符号。所以写成代码时必须补上绝对值:
ae = np.max(np.abs(frame)) # 不能写成 np.max(frame)
漏掉 abs,一帧里最深的那个谷就永远看不见——而对打击类的声音来说,谷往往和峰一样深。
优点、缺点、用在哪
- 能粗略反映响度。 峰越高,通常听着越响。
- 对离群值敏感。 一帧里只要有一个异常大的数,整帧的结果就被它一个人决定了。一次爆音、一次点击噪声,都会让这一帧的 AE 冲到顶。
- 常见用途:起音检测(找出每一下是什么时候开始的)、音乐曲风分类。
均方根:这一帧整体有多强
AE 只看一个数。RMS 让这一帧里的每一个数都参与。
这条式子从里往外读,正好三层:
| 这一层 | 在算什么 |
|---|---|
| 第 个样本的能量 | |
| 这一帧所有样本的能量加起来 | |
| 除以样本数,得到平均能量 | |
| 再开平方 |
名字就是倒着念这三步:Root(根)· Mean(均)· Square(方)。
图 3:从八个原始数走到 0.5256,中间三步。
为什么要平方,又为什么要开回来
拿那八个数直接求平均:
直接求平均 = 0.0625
几乎是零。因为正负互相抵消了——这一帧明明在剧烈振动,直接平均却量不出任何强弱。
平方就解决了这件事:平方之后全是正数,没有抵消。
① 每个数平方
0.09 0.49 0.04 0.81
0.16 0.01 0.25 0.36
② 全部加起来 2.21
③ 除以 K=8 0.2762
④ 开平方 0.5256
那为什么还要开回来?因为平方之后量级变了(0.2762 和原始那些数不在一个尺度上)。开平方把它换回和原始样本一样的量级,这样 RMS 和 AE 才能画在同一张图上直接比。
优点、缺点、用在哪
- 能反映响度,而且比 AE 更贴近「整体感觉」。
- 比 AE 更不怕离群值。 一个孤立的尖峰要和其余 个数一起平均,被稀释掉了。
- 常见用途:音频分段(把一段长录音切成若干有意义的片段)、音乐曲风分类。
过零率:这一帧穿过中线多少次
前两个都在量「有多大」。ZCR 完全不管大小,只数次数。
定义:信号穿过横轴的次数。
图 4:绿点是曲线穿过中线的位置。数它们有几个,就是这一帧的过零次数。
写成公式:
核心是那个 ,叫符号函数,它把一个数压成三种情况之一:
然后看相邻两个样本的符号一样不一样:
- 同号,比如 ,取绝对值除以 2 还是 0——没穿过。
- 异号,比如 ,取绝对值除以 2 得 1——穿过了一次。
那个 就是干这个用的:把「差 2」换算成「1 次」。
手算这八个数
原始 0.3 0.7 -0.2 -0.9
0.4 0.1 -0.5 0.6
符号 +1 +1 -1 -1
+1 +1 -1 +1
相邻异号? 0 1 0 1 0 1 1
加起来:这一帧穿过中线 4 次。
一个必须知道的口径差异
手算到“4 次”就结束了——它给的是一个次数。但实际使用时,还要除以帧长换成比例,好让不同帧长之间能比。除数有两种写法:
除以相邻对的个数 7 = 0.5714
← 本课程 soundlab 的口径
除以帧长 8 = 0.5000
← librosa 的口径
这不是我推的,脚本里真调了一次 librosa 让它自己给:
librosa 实际算出来 0.5000
等于 4/8,确实是除以帧长
zero_crossings(pad=False)
数出 4 次,和上面的手算一致
所以呢? 两个口径差一个 。帧长 1024 时这个差只有 0.1%,可以忽略;但同一个数据集必须自始至终用同一个口径,否则两批数字没法直接比。第 09 课把手写实现和 librosa 对齐时,第一个要对上的就是这个除数。
优点、缺点、用在哪
- 完全不受音量影响。 把整段乘以 10,穿过中线的次数一次也不会变——下面有实测。
- 不能单独用来判断音高。 噪声也可能频繁过零;录音里很小的电气抖动还会制造额外的交点。
- 常见用途:
- 分辨打击乐和有音高的乐音——打击声穿零又多又不规则。
- 单音高估计——只有一个音在响时,过零次数和音高有直接关系。
- 语音的清浊音判断——「s」「f」这类摩擦音的过零率远高于元音。
实验:换成真实录音,三条曲线各画各的
运行 lesson07_three_time_features.py,对 debussy.wav 的前 3 秒逐帧算三个数:
66150 个样本,帧长 1024 帧移 512
切出 128 帧
最小 最大 平均
AE 0.1315 0.2685 0.1808
RMS 0.0426 0.0888 0.0647
ZCR 0.0450 0.0929 0.0654
图 5:四条线共用同一条时间轴。最上面是原始波形,下面三条是逐帧算出的 AE、RMS、ZCR。
三条曲线都是 128 个点、共用同一条时间轴。但它们不是彼此的缩放版——把三条各自除以自己的最大值之后算相关系数:
AE 和 RMS 0.794
AE 和 ZCR -0.322
RMS 和 ZCR -0.552
所以呢? AE 和 RMS 高度同向(0.794),因为两个都在量「这一帧振动得多大」。ZCR 和它们是负相关,而且明显弱一截——它数的是穿过中线的次数,和振动多大根本不是一回事。
要点不是这个负号有多大,而是:ZCR 量的是另一件事,不能拿它替代 AE 或 RMS,反过来也不行。
最直接的证据:把整段乘以 10
原样 ×10 后 倍数
AE 0.1808 1.8081 10.00
RMS 0.0647 0.6465 10.00
ZCR 0.0654 0.0654 1.00
AE 和 RMS 严丝合缝地跟着变了 10 倍。ZCR 一动不动。
所以呢? 乘一个正数不会让曲线多穿过中线一次,也不会少一次。这条性质说明 ZCR 和「录得多响」完全无关——第 09 课比较语音和噪声时要用到它。
这一课得到了什么,下一课接着讲什么
| AE | RMS | ZCR | |
|---|---|---|---|
| 问什么 | 最高多少 | 整体多强 | 穿线多少次 |
| 用几个样本 | 只用最大的那一个 | 全部 个 | 全部相邻对 |
| 怕离群值吗 | 很怕 | 不太怕 | 不怕 |
| 受音量影响吗 | 是 | 是 | 完全不受 |
| 常见用途 | 起音检测、曲风分类 | 音频分段、曲风分类 | 打击乐对乐音、单音高估计、清浊音判断 |
三个公式讲完了,但还没有一行真正能跑在整首曲子上的实现。上面那些数是脚本算的,脚本里的函数还没拆开看过。
下一课从头实现振幅包络:读进三段音乐、画出波形、手写 AE、把帧编号换算成时间、最后把包络叠在波形上——完整走一遍,看看三种曲风的包络长得有多不一样。