The lesson text is only available in Chinese for now. Lesson titles, introductions, menus and buttons are shown in English.

Lesson 2323 lessons in totalAbout 11 min

Spectral centroid and bandwidth: measuring where frequencies are centred and how far they spread

The spectral centroid and bandwidth are often dismissed with two library calls. This lesson goes the other way: first get the time tracks of three music clips with the library’s default settings, then break the two calls down into formulas and recompute them frame by frame yourself. The largest difference is only 1.917e-05 Hz for the centroid and 1.156e-05 Hz for the bandwidth. Lining them up also exposes a definition that is easy to mix up: mean absolute distance is p = 1, but the library computes the root-mean-square distance, p = 2, by default — and on these three clips the two differ by a factor of 1.30 to 1.54.

导读: 频谱质心和带宽经常只用两行库函数带过。本课反过来做:先按库的默认参数得到三段音乐的时间轨迹,再把两行函数拆成公式,自己逐帧重算。质心最大差只有 1.917e-05 Hz,带宽最大差只有 1.156e-05 Hz。对齐过程还会揭开一个容易混用的定义:平均绝对距离是 ,库函数默认算的是均方根距离 ,这三段音乐上两者能差到 1.30—1.54 倍。

二十三课中的第二十三课,实现频谱质心和频谱带宽

完整代码在第 23 课脚本,公共实现仍在frequency_features.py。运行环境沿用课程总纲里的一次性配置。

这一课要完成什么实验

仍然用德彪西、摇滚、艾灵顿公爵三段音乐,各取前 30 秒。这次参数是:

采样率       22050 Hz
帧长         1024 个样本(46.4 毫秒)
帧移          512 个样本(23.2 毫秒)

先调用库函数,确认每段得到 1292 个质心和 1292 个带宽;然后从同一张幅度谱手写两套公式,逐帧比较。最后把 和 都算一遍,看看“定义只差一个参数”在真实音乐上会差多少。

先用耳朵听一遍

和上一课一样,先听:德彪西、摇滚、艾灵顿公爵。

这次留意的是另一件事:声音听起来有多“亮”,以及成分铺得有多杂。 摇滚那段的镲片、失真和人声挤在一起,又亮又满;德彪西那段的钢琴偏暗,同一时刻响着的东西也少;艾灵顿那段的管乐比钢琴亮一些,但远没有摇滚那么铺天盖地。

“亮”和“铺得开”正是这一课要量的两个数。先记住耳朵给出的顺序,等下和曲线对一次。

先让库交出两条时间轨迹

centroid = librosa.feature.spectral_centroid(
    y=y, sr=sr,
    n_fft=1024, hop_length=512
)[0]

bandwidth = librosa.feature.spectral_bandwidth(
    y=y, sr=sr,
    n_fft=1024, hop_length=512
)[0]

三段的 shape 都是 (1292,)。这不是整段录音只有一个质心,而是每帧一个数。用

times = librosa.frames_to_time(
    np.arange(1292), sr=sr, hop_length=512
)

就能把第几帧换成第几秒。

三段真实音乐的频谱质心随时间变化

质心曲线高,表示这一帧的幅度整体更偏向高频。它不是音高轨迹:摇滚里失真、电吉他泛音、鼓和噪声都会把质心拉高,即使没有一个清楚的高音符。

把质心手写出来

先从同一组参数得到幅度谱和真实频率轴:

S = np.abs(librosa.stft(y, n_fft=1024, hop_length=512))
frequencies = np.fft.rfftfreq(1024, d=1 / sr)

S 的形状是“频率格 × 时间帧”。每一列的质心是:

写成向量化代码只有三行:

total = S.sum(axis=0)
weighted = (frequencies[:, None] * S).sum(axis=0)
centroid_manual = weighted / total

这里用的是幅度 S,不是功率 S ** 2。换成平方以后高幅度频率格会被进一步放大,算出的将是另一种质心,不能再拿来和库的默认结果对齐。

带宽先要回答:用哪一把尺

质心确定中心以后,带宽量每一格离中心多远。通用写法是:

当 ,得到平均绝对距离;当 ,远处的频率格先平方,所以受到更大惩罚,最后再开方把单位还原成 Hz。

这一支就是第 21 课说过的加权标准差:把频率当数据、幅度当权重、质心当平均值,算出来的正是标准差。库函数默认使用它:

distance2 = np.abs(frequencies[:, None] - centroid_manual) ** 2
bandwidth_manual = np.sqrt((S * distance2).sum(axis=0) / total)
同一列频谱用 p 等于 1 和 p 等于 2 得到不同带宽

图里同一列频谱的质心是 1000 Hz, 带宽是 166.7 Hz, 是 288.7 Hz。两把尺都合理,但结果不能混用。本课后面的“带宽”如无特别说明,一律指 。

手写与库函数能不能对上

用三段录音全部 3876 帧逐个比较:

手写频谱质心和带宽与库函数的最大绝对差
频谱质心最大绝对差      1.917e-05 Hz
p=2 带宽最大绝对差      1.156e-05 Hz

都不到十万分之二赫兹。剩下的只是浮点数计算顺序不同;公式、幅度权重、真实 Hz 频率轴和矩阵方向已经对齐。

这个核对比“图看着差不多”严格得多。两条曲线即使差几十赫兹,在几千赫兹的纵轴上也可能重合;逐帧最大差会把任何一处偏差抓出来。

p=1 与 p=2 在真实音乐上差多少

录音 带宽中位 带宽中位
德彪西760.5 Hz1173.7 Hz1.54 倍
摇滚1839.1 Hz2390.2 Hz1.30 倍
艾灵顿公爵946.2 Hz1227.6 Hz1.30 倍

这不是小数点后的差异。同一段德彪西,换一把尺就从 760.5 Hz 变成 1173.7 Hz。以后看到“频谱带宽”时,至少要核对三件事:用幅度还是功率作权重、 是多少、静音帧怎么处理。

三段录音的中心和展开

三段真实音乐的 p 等于 2 频谱带宽随时间变化
录音质心中位质心中间一半 带宽中位带宽中间一半
德彪西979.9 Hz861—1311 Hz1173.7 Hz1082—1563 Hz
摇滚2250.4 Hz1917—2846 Hz2390.2 Hz2088—2711 Hz
艾灵顿公爵1075.5 Hz818—1340 Hz1227.6 Hz1128—1326 Hz

这三段里,摇滚的质心和带宽都最高。仍然只能说“这三段录音如此”,不能从每类一首曲子推断整个流派。

两条轨迹确实常一起起伏,但不是同一个量。逐帧相关系数分别是:

德彪西       0.883
摇滚         0.880
艾灵顿公爵   0.767

相关很强,却远没有到完全相同。第 21 课已经造出“质心不动、带宽改变”和“带宽不动、质心改变”的标准答案;这里的相关只说明真实录音中高频增多时,频谱常常也会一起铺开。

两个解释边界

第一,静音帧没有可解释的质心。 当整列幅度都是 0,分子和分母都是 0。公共函数为了和库的数值约定一致返回 0 Hz,但这不代表静音的频谱中心真的在 0 Hz。实际项目里应该同时检查这一帧的能量,低于阈值就标成无效。

第二,录音条件会改变结果。 麦克风的频率响应、压缩、均衡器和环境噪声都会改写频谱。质心和带宽描述的是“这份录音里测到的频率分布”,不是脱离设备后仍然不变的声音身份证。

三个特征放回项目:谁分得开哪一对

第 21 课说过,这三个特征在实际任务里的常见去处就是音乐风格分类,也就是课程项目要做的事。三个数现在都算完了,可以回答那个具体的问题了:哪一个数分得开哪一对风格?

把三个特征的中位数和中间一半并排放:

特征古典(德彪西)爵士(艾灵顿)摇滚
带能量比19.65 dB
14.63—23.56
13.97 dB
9.88—19.66
7.25 dB
5.35—9.32
频谱质心979.9 Hz
861—1311
1075.5 Hz
818—1340
2250.4 Hz
1917—2846
带宽1173.8 Hz
1082—1563
1227.6 Hz
1128—1326
2390.2 Hz
2088—2711

摇滚三个数都分得开。 它的中间一半和另外两段完全不重叠:质心比爵士的上界还高出 577 Hz,带宽高出 762 Hz,BER 比爵士的下界还低 0.55 dB。想从这三段里挑出摇滚,随便用哪一个数都够。

古典和爵士就完全是另一回事了。

  • 用质心:古典的中间一半是 861—1311 Hz,整段落在爵士的 818—1340 Hz 里面。重叠 100%——按这个数,两者根本分不开。
  • 用带宽:反过来,爵士的 1128—1326 Hz 整段落在古典的 1082—1563 Hz 里面,同样重叠 100%。
  • 用带能量比:重叠 5.03 dB,只占古典那段中间一半(8.93 dB)的 56%。仍然重叠得多,但这是三个数里唯一真的把两者拉开了一点的。

所以结论不是省事的“三个特征都有用”,而是:在这三段录音上,质心和带宽基本只是摇滚探测器;古典和爵士之间,只有带能量比动了一下。 这也解释了为什么项目要同时算三个数再交给后面的程序——它们不是三份互相印证的证据,而是各自只在某些对比上说得出话。

再提醒一次边界:每种风格只有一段 30 秒的录音。上面这些话描述的是这三段音频,不是古典、爵士和摇滚这三类音乐。换一段德彪西、换一支爵士乐队,数字都会变。要判断这些特征能不能推广,必须换成更多录音、并且严格分开训练与测试。

这一课得到了什么,下一课接着讲什么

这一课把最后两个频域特征从库函数拆回了可核对的公式:

  1. 质心是用幅度给真实 Hz 频率加权;三段里的手写与库最大差 1.917e-05 Hz;
  2. 库默认带宽是 的均方根距离,手写与库最大差 1.156e-05 Hz;
  3. 与 不是两种写法,而是两把不同的尺,这三段上相差 1.30—1.54 倍;
  4. 质心和带宽相关但不能互换,静音帧的 0 也不能当成有效测量。
课程从统一录音条件、分帧、选择表示走到提取和核对特征的完整流程

二十三课到这里结束,不再虚构一篇“下一课”。文章里的实验是最短、可读的版本;课程代码目录保留了完整输出、参数扫描和边界检查,课程项目则把这些特征放回“三首曲子,一个分类器”的完整流程。接下来真正值得做的,是换成更多录音、严格分开训练与测试,再检验这些数字能否推广到没见过的声音。