原文链接:Onset Detection Part 6: Onsets & Spectral Flux

在之前的一篇文章中,我从科学的角度讨论了起点/节拍检测。有很多不同的方案可以不同程度的做好这项工作。然而,有一种方法与其他更复杂的算法相比非常简单,性能也非常好,因此我选择使用它来实现我的目的。它被称为光谱通量或光谱差异分析,在这篇文章中,我会试着让你对它的工作原理有一个深入的了解。

关于起点检测的最好的论文之一是 Bello 等人 。你们可以在这里看到。我将回顾这里提到的一些概念,并偷取一些图片。

首先什么是起点?瞧:

(译注:此处并没看到有图,尴尬。。。html里的链接也失效了,就不贴了)

在顶部我们可以看到单个音符的振幅或波形样本。起点(onset)是演奏音符时发生的第一件事,接着是攻击(attack),直至达到最大振幅,然后是衰减阶段。我避免定义瞬态,因为它与我们无关。

我们想处理的音频信号当然比一个音符要复杂一点。我们想要检测的复调音乐中包含很多音调乐器,如吉他或小提琴、人声和非音调鼓。正如我们在频图中看到的,后面几篇文章中提到的,大多数音高仪器的频率范围都是重叠的(大部分在70Hz到4000Hz之间)。因此,完美的开始检测是不可能的,但我们可以尝试得到一个近似的估计。

从我读到的关于起点检测的论文中,我可以发现几乎总是遵循相同的模式。首先读入音频信号,然后将其转换为一个起点检测函数(只是与原始音频样本以某种方式对应的另一组浮点数),最后在这个检测函数中选择onsets/beats作为峰值。第一个阶段我们已经讲过了,结果很简单。这个过程的第二阶段通常是相当复杂的,做一些魔术把音频信号转换成另一个更简单的,压缩的一维函数。最后一个阶段在大多数文献中都没有详细介绍。然而,当我在做一个原型的时候,我发现这个阶段可能是最重要的,甚至比开始检测函数的计算更重要。在以后的文章中,我们将集中讨论峰值检测,现在让我们继续讨论起点检测函数。

这是一个很好的关于起点检测的一般过程的小图表,同样取自Bello等人。

图片来自原文,谁能告诉我怎么去掉水印?

例如,预处理阶段可以进行动态范围压缩,这使得所有东西都同样大声,这是一个糟糕的习惯,几乎在所有现代音乐混合中都使用。不过糟糕的 DRC 对某些起点检测函数还是有益的。我们不会使用从中受益的函数,所以我们不关心预处理。

还原阶段(reduction phase)相当于构造起点检测函数。这一阶段的目的是得到音乐信号结构的压缩视图。起点检测函数应该能够捕捉音乐事件,使检测onsets比在音频信号的普通波形上更容易。起始检测函数的输出值表示连续时间跨度内是否存在音乐事件。还记得我们如何读取样本以进行播放吗? 我们在每次迭代中读取1024个样本。对于这样一个样本窗口,起点检测函数将输出一个值。对音乐的一秒钟,使用44100Hz的采样率,使用1024个样本作为起点检测函数的时间跨度,我们将得到大约43个值(44100/1024),这些值模拟了音频信号的结构。同时,我们的FFT类可以处理1024的样本窗口大小。总而言之:生成起点检测函数的值意味着为每个连续的样本窗口计算一个值,这给了我们一个很好的曲线,以供我们对其进行峰值检测。

现在,我在上一篇文章中介绍了FFT,所以我们将使用它来生成起始检测函数。这样做的动机是什么?听一听“A Perfect Circle”的《Judith》,看看下面的图片:

[audio:http://apistudios.com/hosted/marzec/badlogic/wordpress/wp-content/uploads/2010/02/judith.mp3]

图片来自原文,谁能告诉我怎么去掉水印?

图像描述了音频信号连续1024个采样窗口的频谱。每个垂直像素柱对应一个频谱。列中的每个像素代表一个频率箱。颜色告诉我们这个频率箱对采样窗口中音频信号的贡献有多大,频谱就是从采样窗口中提取的。值的范围从蓝色到红色到白色,依次递增。为了便于参考,y轴用频率标记,x轴用时间标记。天啊,蝙蝠侠,(。。。)我们能看到节奏了!我用可怕的箭头标记了它们。节拍只能在大约5000Hz到16000Hz的高频范围内看到。它们对应于正在演奏的hi-hat。当你敲击 hi-hat,它会产生一种叫做白噪声的东西,这种声音跨越了全部频谱。与其他乐器相比,它被击中时具有很高的能量。我们可以在频谱图上清楚地看到。

现在,处理这个频谱图不会比处理波形简单很多。为了比较,下面是同一首歌的波形:

图片来自原文,谁能告诉我怎么去掉水印?

我们也可以在波形中看到 hi-hat 的敲击。那么傅里叶变换有什么好处呢?它让我们专注于特定的频率。举个例子,如果我们只对探测 hi-hat 的节拍感兴趣,或者对 crash 感兴趣,或者对叮叮镲感兴趣,我们会选择非常高的频率范围,因为这些乐器会留下它们存在的痕迹。我们不能单独用振幅来做。同样的道理也适用于其他乐器,比如踢腿鼓或低音,我们只在非常低的频率下寻找。如果我们分别检查频谱的多个频率范围,我们称之为多波段起点检测(multi-band onset detection)。给你一些谷歌先生的关键词。

所以我们知道了为什么我们要在频域中进行起点检测,但是我们并不知道如何将频谱图的所有信息压缩到一个可管理的一维浮点数组中,以便我们可以很容易地进行峰值检测。让我给你们介绍我们最好的新朋友,光谱差异 也叫 光谱通量。这个概念非常简单:对于每个频谱,我们都计算当前样本窗口的频谱 与上一个样本窗口的频谱 的差。就是这样。我们得到的是一个数字,它告诉我们当前频谱的bin值和上一个频谱bin值之间的绝对差。这里有一个很好的小公式(所以我可以使用latex wordpress插件):

[latex size=”2″]SF(k) =\displaystyle \sum_{i=0}^{n-1} s(k,i) – s(k-1,i)[/latex] (这插件明显没好使嘛。。。)

SF(k)给出了第k个光谱的光谱通量。s(k, i) 给出第 k 个频谱里第 i 个箱的值,s(k-1, i) 同理,是第 k-1 个频谱里第 i 个箱的值。然后用上一个频谱里每个箱的值,与当前频谱里对应的每个箱的值 做差,之后把这些差求和得到一个最终的值,这就是频谱的光谱通量 k !

让我们写一个小程序,计算和可视化一个 mp3 的频谱通量:

public class SpectralFlux
{public static final String FILE = "samples/judith.mp3"; public static void main( String[] argv ) throws Exception{MP3Decoder decoder = new MP3Decoder( new FileInputStream( FILE  ) );                         FFT fft = new FFT( 1024, 44100 );float[] samples = new float[1024];float[] spectrum = new float[1024 / 2 + 1];float[] lastSpectrum = new float[1024 / 2 + 1];List<Float> spectralFlux = new ArrayList<Float>( );while( decoder.readSamples( samples ) > 0 ){          fft.forward( samples );System.arraycopy( spectrum, 0, lastSpectrum, 0, spectrum.length ); System.arraycopy( fft.getSpectrum(), 0, spectrum, 0, spectrum.length );float flux = 0;for( int i = 0; i < spectrum.length; i++ )           flux += (spectrum[i] - lastSpectrum[i]);          spectralFlux.add( flux );                   }       Plot plot = new Plot( "Spectral Flux", 1024, 512 );plot.plot( spectralFlux, 1, Color.red );      new PlaybackVisualizer( plot, 1024, new MP3Decoder( new FileInputStream( FILE ) ) );}
}

我们首先实例化一个解码器,它将从mp3文件中读取样本。我们还实例化了一个FFT对象,参数是1024个样本窗口和44100Hz的采样率。接下来,我们为从解码器读取的样本创建一个浮点数组。我们使用的窗口大小为1024个样本。接下来的两行实例化将包含当前频谱和最后频谱的辅助浮点数组。最后,我们还需要一个地方为每个样本窗口写光谱通量,一个ArrayList将在这里完成这项工作。

下面的循环做了计算光谱通量的讨厌工作。首先,我们从解码器中读取接下来的1024个示例。如果成功了,我们通过调用fft.forward(samples)计算这个样本窗口的傅里叶变换和频谱。然后将上一个频谱的数据复制到 lastSpectrum 中,然后将刚刚计算的频谱复制到 spectrum 中。下面是上述公式的实现。对于每个箱,我们从当前的频谱箱中减去上一个频谱箱的值,并将其添加到称为flux的求和变量中。当循环结束,flux 包含... 当前频谱的频谱通量。这个值被添加到spectralFlux ArrayList中,我们继续解码下一个样本窗口。对于可以从解码器读入的每个样本窗口,我们都重复此操作。循环完成后,spectralFlux包含了我们读取的每个样本窗口的光谱通量。最后三行只是创建一个图并可视化结果。注意:我创建了一个名为PlaybackVisualizer的小助手类,它的参数是一个 plot 对象、每个像素的采样数量和一个解码器,解码器将在播放音频的同时相应地设置图中的标记。没有什么复杂的,我们在前面的一篇文章中看到了如何做到这一点。下面是我们之前用过的歌曲“Judith”的输出:

图片来自原文,谁能告诉我怎么去掉水印?

欢呼!我们可以再次看到高峰。这一次,他们不仅对应的 hi-hat ,但也有小军鼓和踢鼓。得到这个结果的原因是,使用完整的光谱,所以光谱通量的值由能量最大的乐器控制,在这种情况下是hi-hat、snare和kick drum。现在让我们回顾一下刚才做的。取原始光谱图像,从上面我标记的热门的hi-hat。代码的作用本质上是对两个连续光谱的桶的差异进行求和。当电流谱的总能量大于前一个谱时,光谱通量函数会上升。如果当前光谱的能量小于以前的光谱,光谱通量函数就会下降。因此,我们成功地将二维谱图映射为一维函数,可以对其进行峰值检测。嗯,有点……

在我们做任何峰值检测之前,我们必须清理一下光谱通量函数。我们先忽略负谱通量值。我们对光谱通量的下降不感兴趣,而只对光谱通量的上升感兴趣。忽略负谱通量值的过程称为校正。下面是计算光谱通量的修正循环:

   float flux = 0;for( int i = 0; i < spectrum.length; i++ )          {float value = (spectrum[i] - lastSpectrum[i]);            flux += value < 0? 0: value;}spectralFlux.add( flux );

以及相应的输出:

图片来自原文,谁能告诉我怎么去掉水印?

更好一点。当我们计算峰值检测的阈值函数时,对光谱通量进行校正将有助于我们的研究。

另一个清理过程是使用所谓的汉宁窗(Hamming window)。在计算傅里叶变换之前,将这个汉宁窗应用于样本。它基本上是一个平滑函数,让我们的样本看起来更漂亮。FFT类有一个功能,我们可以通过它启用汉宁窗平滑:

fft.window(FFT.HAMMING);

这就是所有要做的,结果如下:

图片来自原文,谁能告诉我怎么去掉水印?

差别不大,但我们可以看到它是如何平衡前4个点的,还去掉了样本末尾的一个点。在其他歌曲中,汉宁窗对产生的光谱通量有更大的影响,但这真的取决于你是否使用它。它不会花费很多性能。

我想以此结束本文。我们已经了解了如何从样本到光谱再到计算一维函数,我们稍后将使用一维函数来检测音频信号中的峰值,从而检测音频信号中的onsets。从上面的图像我们已经可以看到很多峰值,似乎很容易检测。在下一篇文章中,我们将进一步计算不同频段的频谱通量。通过这种方法,我们希望能够提取出各种不同的仪器,以选择聚焦于整个光谱中声音最大的东西。我们还将使用一种称为跳变的技术,在这种技术中,我们计算的光谱通量不是在重叠的连续样本窗口上,而是在非重叠的样本窗口上。这将进一步平滑得到的光谱通量函数。然后,我们准备创建一个简单但有效的峰值检测器,它将给出最终的起点时间。

与往常一样,您可以在http://code.google.com/p/audio-analysis/找到所有源代码。

音符起始点检测(音频节奏检测)(6)相关推荐

  1. 音符起始点检测(音频节奏检测)(1)

    原文链接:Onset Detection Part 1: The Basics 在这篇文章中,我想开始一个小的循序渐进的系列,它将允许您为您的音乐游戏需求构建自己的起始点检测器(onset detec ...

  2. 音频节奏检测(Onset Detection)

    项目代码下载 https://download.csdn.net/download/weixin_43865690/39129840 1. 前言 最近市场上出现一些多个视频拼接而成MV,其原理是根据音 ...

  3. 音符起始点检测(音频节奏检测)(4.5)

    原文链接:Onset Detection Part 4.5: What to expect (这篇文章没大翻译明白,建议阅读原文.大概内容就是在展示自制检测器对各种类型音乐的检测效果,以及和 audi ...

  4. 音符起始点检测(音频节奏检测)(2)

    原文链接:Onset Detection Part 2: A simple framework 好了,我刚刚为我们的起始点检测入门教程组合了一个简单的框架.它位于 http://code.google ...

  5. 音符起始点检测(音频节奏检测)(5)

    原文链接:Onset Detection Part 5: The (Discrete) Fourier Transform 警告:这是我理解离散傅里叶变换的方法,在一些地方可能是错误的.对于傅里叶变换 ...

  6. 音符起始点检测(音频节奏检测)(7)

    原文链接:Onset Detection Part 7: Thresholding & Peak picking 在上一篇文章中,我们看到了如何将一个随着时间的推移而演化为一个简单一维函数的复 ...

  7. python 声音强度检测_python检测音频中的静音

    #-*- coding: utf-8 -*- importosimportwavefrom time importsleepimportnumpy as np SUCCESS=0 FAIL= 1 #需 ...

  8. 音频算法检测发言者方位

    音频算法检测发言者方位:

  9. java mp3静音检测,音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

最新文章

  1. vs2005sp1安装加速
  2. 学习MSCKF笔记——真实状态、标称状态、误差状态
  3. linux——文件权限的更改与管理
  4. 架构师已死(转自UML软件工程组织)
  5. 【机器学习基础】Softmax与Sigmoid你还不知道存在这些联系?
  6. K-Means算法的10个有趣用例
  7. Java多线程(3)--线程的生命周期
  8. 行号 设置vim_在VSCode里面配置Vim正确姿势(细节解析)
  9. aliyun centos6 安装mysql_阿里云CentOS6.8安装MySQL5.6
  10. 调查了 17,000 多位程序员,当前的云原生开发现状究竟如何?
  11. jlist动态添加元素后刷新_小米电视5再曝光:MEMC动态画质补偿技术
  12. android工控软件,基于Android的工业控制监控软件的设计和开发
  13. NoSQL数据库的分布式算法
  14. 在Android平台上发现新的恶意程序伪装成杀毒软件挟持设备
  15. 实现Mybatis接口模式下的数据库调用分离
  16. 剑指offer——面试题21:包含min函数的栈
  17. hdu 1254 推箱子(嵌套搜索,bfs中有dfs)
  18. 你们知道我们山东考生是怎么过来的么!山大校长写给你!
  19. 【计算机网络】物理层 : 编码 ( 数字数据 编码 数字信号 | 非归零编码 | 归零编码 | 反向不归零编码 | 曼彻斯特编码 | 差分曼彻斯特编码 | 4B/5B 编码 )
  20. css 设置背景颜色失效?

热门文章

  1. 数据分析与预测(二)——pandas 函数read_csv解析
  2. css实现旋转的小箭头
  3. Could not retrieve mirrorlist
  4. 计算机怎么化成10的次方,10的n次方换算关系 10^N 计算机存储单位的换算关系
  5. 跨专业考浙大计算机考研难度,0基础跨专业计算机考研经验-2013浙大
  6. 字节跳动裁员不发年终奖致员工与HR薅头发互殴?字节回应来了
  7. 用Python做一个猜数游戏(入门)
  8. 帧同步游戏开发基础指南
  9. 《流浪星球》作者:区块链让虚拟世界代替现实世界
  10. Pulling is not possible because you have unmerged files