论文阅读 - Beat Tracking by Dynamic Programming

文章目录

  • 1 概述
  • 2 总体框架
  • 3. 计算Onset Strength Envelope
  • 4 计算全局的Tempo
  • 5 基于动态规划计算beats
  • 6 参考文献

1 概述

有背景音乐的短视频拼接时,如果两个视频的拼接点刚好在背景音乐的某个节拍点上,那么合成的视频看起来,听起来,都会非常舒服,这是短视频合成的一个加分项,这种视频也就是我们经常说的卡点视频。要做卡点视频的前提是找到背景音乐中可以卡的点,beats是其中一种可以卡的点,本文就是用大白话来讲讲论文Beat Tracking by Dynamic Programming是怎么找beats的。常用的音频信号处理库librosa中的librosa.beat.beat_track用的就是这种方法。这个任务的名字叫做beat tracking,下文中都将这样称呼。

2 总体框架

论文中介绍的beat tracking可以分为三个步骤:

(1)计算Onset Strength Envelope(Onset的能量包络)

(2)计算全局的Tempo

(3)基于动态规划计算beats

我第一次看到这三步,也是一头雾水,如果是专门做信号处理的人,可能已经知道怎么回事了,但这篇文章的的目标受众是像我一样,学过或者了解过信号处理却已经忘的差不多的小伙伴。所以,下面会针对这三个步骤详细解析。

如果只想知道怎么使用的话,librosa已经为我们包装好了一切,三行代码就搞定了。

import librosay, sr = librosa.load(“your/music/file/path”)
tempo, beats = librosa.beat.beat_track(y=y, sr=sr)

3. 计算Onset Strength Envelope

onset指的就是某个音符发出声音的起点,比如按下钢琴键的那个时刻,又比如拨动琴弦的那个时刻,下图示意了onset的位置。在这个环节中,就是要把一段音频中所有的onset的能量包络找出来。给到的音频可能是有鼓的音轨,钢琴的音轨,小提琴的音轨,人声的音轨等混合在一起的。不管哪个音轨的,都会被我们找出来。
onset

图1:onset示意图

找onset能量包络的方法不是这篇论文提出来的,用的是一个已有的常用的方法,叫做crude perceptual model。其步骤如下所示:

(1)用8kHz的采样率读取音频文件。

(2)用32ms的window_size和4ms的step_size,进行短时傅立叶变换(STFT),得到图2中最上方的图。

(3)将频谱图映射到梅尔频谱上,纵轴分为40个bands,得到图2中中间的图。

(4)沿时间轴对每个band做一阶差分,并把所有的负值置0,再把每个时间点的所有band差分后的正值累加。

(5)对(4)中得到的结果进行高通滤波,过滤掉0.4kHz以下的信息,使其局部零均值,再用window_size为20ms高斯窗做平滑处理,得到图2中最下方的图,也就是onset strength envelope,记作O(t)O(t)O(t)
onset_strength_envelope

图2:频谱,梅尔频谱和onset strength envelope的对比图

O(t)O(t)O(t)中的各个局部峰值就是能量突增的地方,为什么会能量变化剧烈?当然是因为有新的音符发声了。注意,这里的波峰我认为不是onsets的精确时间,而是onsets产生的波峰的时间。不过不同的文章中好像都是把这个onsets的候选了。不过我们不关心onsets具体到底在哪里,只要有O(t)O(t)O(t)就够了,所以不纠结定义问题了。

还有一个就是,我比较奇怪论文的作者为什么要用(5)这样的归一化方式,这样得到的O(t)O(t)O(t)就会有很多负值,但是这些负值我们是不要的。Tempo and Beat Tracking中的归一化方式我觉得更合理一些,直接减去local average,就可以了。下图3中的最上方红线就是local average,减去后得到的结果为图3中间的那个图,图3下方的图标出了局部峰值和onsets。
normalization

图3:onset strength envelope归一化处理

4 计算全局的Tempo

Tempo指的是音乐的节拍,通常用bpm(beats per minute)来度量,比如120bpm就表示一分钟有120个beats,拍子的周期为0.5s。本文中用拍子的周期来表示Tempo。一首音乐的Tempo有可能是随时间变化的,但这种情况很少,我们这里只讨论整个音频的tempo都保持一致的情况。变化的Tempo检测可以参见predominant local pulse,大致思想就是分段处理,这里不讨论。

节拍就是一个调子在不断循环,我们要找出这个循环的周期。自相关函数用来找周期是在适合不过的了,我们会计算不同延迟时间下O(t)O(t)O(t)的自相关函数值,值最高的对应的延迟时间就是一个beat的长度。

但是这里有一个问题,如图4的raw autocorrelation所示,周期函数的在他基周期的倍数上的自相关函数值都是很大的。为了解决这个问题,论文引入了一个权重系数,使得周期结果偏向于某个经验值。这个计算自相关系数的计算公式为

TPS(τ)=W(τ)∑tO(t)O(t−τ)TPS(\tau)=W(\tau)\sum_t O(t)O(t-\tau) TPS(τ)=W(τ)tO(t)O(tτ)

其中,τ\tauτ是延迟的时间,TPS为Tempo Period Strength的缩写,是论文作为给这个自相关计算方法取的名字,使得TPS(τ)TPS(\tau)TPS(τ)最大的那个τ\tauτ,就是我们要找的周期。

W(τ)W(\tau)W(τ)是一个高斯权重系数,表示为

W(τ)=exp{−12(log2τ/τ0στ)2}W(\tau) = exp\{-\frac{1}{2}(\frac{log_2 \tau / \tau_0}{\sigma_\tau})^2\} W(τ)=exp{21(στlog2τ/τ0)2}

其中,τ0\tau_0τ0就是默认偏向的周期大小,στ\sigma_\tauστ是表示偏重程度的一个系数。τ0\tau_0τ0στ\sigma_\tauστ都是经验值,论文从MIREX-06 Beat Tracking训练集中统计得来的。统计的方法是填入不同的τ0\tau_0τ0στ\sigma_\tauστ,使得TPS(τ)TPS(\tau)TPS(τ)的得出的最大值和数据中标注的Tempo一致性最高的那组τ0\tau_0τ0στ\sigma_\tauστ就是了。

最终得出的τ0\tau_0τ0为0.5s,也就是120bpm,στ\sigma_\tauστ为1.4。在该组参数下的TPS(τ)TPS(\tau)TPS(τ)如图4中最下方的图所示。图中的Primary Tempo Period就是最终的周期。
tempo

图4:onset strength envelope,原始自相关函数和TPS的对比图

librosa.beat.beat_track中有一个输入参数为start_bpm,指的就是τ0\tau_0τ0,可以人为传入修改,默认为120。

在实际的使用中,会对TPS(τ)TPS(\tau)TPS(τ)做一些优化,变成

TPS2(τ)=TPS(τ)+0.5TPS(2τ)+0.25TPS(2τ−1)+0.25TPS(2τ+1)TPS2(\tau) = TPS(\tau) + 0.5 TPS(2\tau) + 0.25 TPS(2\tau - 1) + 0.25 TPS(2\tau + 1) TPS2(τ)=TPS(τ)+0.5TPS(2τ)+0.25TPS(2τ1)+0.25TPS(2τ+1)

或是

TPS3(τ)=TPS(τ)+0.33TPS(3τ)+0.33TPS(3τ−1)+0.33TPS(3τ+1)TPS3(\tau) = TPS(\tau) + 0.33 TPS(3\tau) + 0.33 TPS(3\tau - 1) + 0.33 TPS(3\tau + 1) TPS3(τ)=TPS(τ)+0.33TPS(3τ)+0.33TPS(3τ1)+0.33TPS(3τ+1)

不管用哪种方法,TPS(τ)TPS(\tau)TPS(τ)的峰值对应的τ\tauτ就是我们要找的周期。

5 基于动态规划计算beats

论文以4ms一个步长(250Hz)将时间分段,利用了第3节和第4节的结果,设计了如下的目标函数:
C({ti})=∑i=1NO(t)+α∑i=2NF(ti−ti−1,τp)C(\{t_i\}) = \sum_{i=1}^N O(t) + \alpha \sum_{i=2}^N F(t_i - t_{i-1}, \tau_p) C({ti})=i=1NO(t)+αi=2NF(titi1,τp)

其中,{ti}\{t_i\}{ti}为找到的NNN个beats;O(t)O(t)O(t)就是第3节中的Onset Strenghth Envelope;α\alphaα为平衡两个目标项的系数;τp\tau_pτp就是第4节中得到的周期;F(△t,τp)F(\triangle t, \tau_p)F(t,τp)是用来衡量每两个相邻的beats的间距和τp\tau_pτp的差距,这个可以自己定义,论文中表示为

F(△t,τp)=−(log△tτp)2F(\triangle t, \tau_p) = -(log \frac{\triangle t}{\tau_p})^2 F(t,τp)=(logτpt)2

可见当△t\triangle ttτp\tau_pτp越接近,F(△t,τp)F(\triangle t, \tau_p)F(t,τp)越大,最大为0,否则越小。除此之外,该式是log对称的,比如F(kτp,τp)=F(τp/k,τp)F(k\tau_p, \tau_p) = F(\tau_p / k, \tau_p)F(kτp,τp)=F(τp/k,τp)

我们的目标是使得C({ti})C(\{t_i\})C({ti})越大越好,分析一下,当α=0\alpha=0α=0时,把所有的时间点全部选上,C({ti})C(\{t_i\})C({ti})就最大了;当α=+∞\alpha=+\inftyα=+时,选择时间间隔为τp\tau_pτp的一组点就可以使得C({ti})C(\{t_i\})C({ti})最大了。不难看出,有了α\alphaα的平衡,最终得到的点列就是间隔在τp\tau_pτp左右微调,且使得点落O(t)O(t)O(t)局部峰值附近的一组点。

论文用动态规划的方法,以线性的时间复杂度,解决了这个问题。首先定义了C∗(t)C^*(t)C(t),表示只考虑时间点不大于时刻ttt时,以时刻ttt为一个beat的C({ti})C(\{t_i\})C({ti})的最大值,这也就是动态规划中的状态转移函数,其表达式为:

C∗(t)=O(t)+max⁡τ=0...t−4ms{αF(t−τ,τp)+C∗(τ)}C^*(t) = O(t) + \max_{\tau=0...t-4ms} \{\alpha F(t-\tau, \tau_p) + C^*(\tau)\} C(t)=O(t)+τ=0...t4msmax{αF(tτ,τp)+C(τ)}

这个和标准的动态规划还是有点区别,要细细品味一下,这个地方我思考了挺久,这种做法可以避免在计算状态转移时,之前的最优beats序列发生变化。这个C∗(t)C^*(t)C(t)还有一个好处,就是可以用来找结尾,当C∗(t)C^*(t)C(t)的增量骤减时,就是音乐转弱,也就是结尾的地方了。

同时,也会记录使得C∗(t)C^*(t)C(t)最大的那个序列在ttt之前的那个beat为

P∗(t)=argmax⁡τ=0...t−4ms{αF(t−τ,τp)+C∗(τ)}P^*(t) = arg\max_{\tau=0...t-4ms} \{\alpha F(t-\tau, \tau_p) + C^*(\tau)\} P(t)=argτ=0...t4msmax{αF(tτ,τp)+C(τ)}

也就是说取的τ\tauτ是多少。通过P∗(t)P^*(t)P(t)可以回溯选择的beats路径,得到最终的beats序列。

在实际搜索τ\tauτ的时候,不会从0到t-4ms去做的,这样会计算很多不必要的情况。有F的惩罚在,我们只要搜索τ=t−2τp...t−τp/2\tau = t - 2\tau_p ... t - \tau_p / 2τ=t2τp...tτp/2

我们把从0到总时长,所有的时刻ttt对应的C∗(t)C^*(t)C(t)都算出来之后,取其中最大的那个C∗(tN)C^*(t_N)C(tN)tNt_NtN就是最后一个beat点,然后tN−1=P∗(tN)t_{N-1} = P^*(t_N)tN1=P(tN),然后由此一直回溯出整条序列{ti}\{t_i\}{ti}。有一点可以确定的是,tNt_NtN必然在[总时长−τp,总时长][总时长-\tau_p, 总时长][τp,]的范围内,不然就还可以再加入一个beat。

说到这里正文已经说完了。这里再简单说一下,为什么不能按标准的动态规划的做法,不然下次自己来看可能又要想半天。按标准的做法,会定义C∗(tj)C^*(t_j)C(tj)为时间点不大于时刻tjt_jtj时,C({ti})C(\{t_i\})C({ti})的最大值。状态转移函数为

C∗(tj)=max⁡{O(tj)+max⁡τ=0...tj−1{αF(tj−P∗(τ),τp)+C∗(τ)},C∗(tj−1)}C^*(t_j) = \max \{ O(t_j) + \max_{\tau=0...t_{j-1}} \{\alpha F(t_j-P^*(\tau), \tau_p) + C^*(\tau)\}, C^*(t_{j-1}) \} C(tj)=max{O(tj)+τ=0...tj1max{αF(tjP(τ),τp)+C(τ)},C(tj1)}

解释一下就是,有两种选择,前一种是把tjt_jtj作为一个beat,另一种是不把tjt_jtj作为一个beat。问题就处在这个P∗(τ)P^*(\tau)P(τ),当我们把tjt_jtj作为一个beat,使得C∗(tj)C^*(t_j)C(tj)尽可能大的前一个beat不一定是P∗(τ)P^*(\tau)P(τ)。换而言之,把tjt_jtj作为一个beat后,前面的最优beats可能会发生变化。如果按标准的做法来,会漏掉许多时间点作为beat的情况。细品,细品。

6 参考文献

[1] Beat Tracking by Dynamic Programming
[2] Tempo and Beat Tracking

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/470590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

十二、PHP框架Laravel学习笔记——构造器的查询表达式

一.select 查询 select()方法可以制定你想要的列,而不是所有列; //设置显示的列,设置列别名 $users DB::table(users)->select(username as name, email)->get(); addSelect()方法,可以在你基础的查询构造器…

python解释器的工作原理_Python GIL全局解释器锁详解(深度剖析)

通过前面的学习,我们了解了 Pyton 并发编程的特性以及什么是多线程编程。其实除此之外,Python 多线程还有一个很重要的知识点,就是本节要讲的 GIL。 GIL,中文译为全局解释器锁。在讲解 GIL 之前,首先通过一个例子来直观…

Chapter7-12_Controllable Chatbot

文章目录1 Chatbot面临的问题2 控制Chatbot的输出2.1 直接Finetune2.2 输入添加控制特征2.3 只有独白3 展望本文为李弘毅老师【Controllable Chatbot】的课程笔记,课程视频youtube地址,点这里👈(需翻墙)。 下文中用到的图片均来自于李宏毅老…

十三、PHP框架Laravel学习笔记——构造器的 where 派生查询

一.where 派生查询 orWhere()方法,可以通过连缀实现两个或以上的 or 条件查询; //where() orWhere 实现 or 条件查询 $users DB::table(users) ->where(price, >, 95) ->orWhere(gender, 女) ->toSql(); 通过闭包&#xff0…

Chapter7-13_Dialogue State Tracking (as Question Answering)

文章目录1 什么是Dialogue State Tracking2 数据集3 两个挑战4 经典模型本文为李弘毅老师【Dialogue State Tracking (as Question Answering)】的课程笔记,课程视频youtube地址,点这里👈(需翻墙)。 下文中用到的图片均来自于李宏毅老师的PP…

Migrate Instance 操作详解 - 每天5分钟玩转 OpenStack(40)

Migrate 操作的作用是将 instance 从当前的计算节点迁移到其他节点上。 Migrate 不要求源和目标节点必须共享存储,当然共享存储也是可以的。 Migrate 前必须满足一个条件:计算节点间需要配置 nova 用户无密码访问。 下面是 Migrate instance 的流程图 …

十四、PHP框架Laravel学习笔记——构造器的排序分组、子查询

一.排序分组 使用 whereColumn()方法实现两个字段相等的查询结果; //判断两个相等的字段,同样支持 orWhereColumn() //支持符号create_time,>, update_time //支持符号支持数组多个字段格式[create_time,>, update_time] $users …

python找不到文件怎么办_python open找不到文件怎么办?

推荐教程:《python视频教程》 python open找不到文件怎么办? python open找不到文件的解决办法: 在python和很多程序语言中"\"转义符号,要想输出\有两种方法,一是多加一个\写成\\ ,一是在字符串前加一个r,提…

css:蓝环章鱼

css&#xff1a;蓝环章鱼 许多海洋生物色彩艳丽&#xff0c;这次用css仿制一下蓝环章鱼的蓝环 <script type"text/javascript" src"http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script> <script type"text/javascript&quo…

论文阅读 - Jukebox: A Generative Model for Music

文章目录1 概述2 什么是VQ-VAE2.1 Auto-encoder(AE)2.2 Variational AutoEncoder(VAE)2.3 Vector-Quantized Variational AutoEncoder(VQ-VAE)2.4 VQ-VAE-23 Music VQ-VAE4 Prior and upsamplers5 Lyrics Conditioning参考文献By learning to produce the data, we can learn t…

十五、PHP框架Laravel学习笔记——构造器的 join 查询

一&#xff0e;join 查询 使用 join 实现内联接的多表查询&#xff0c;比如三张表进行 inner join 查询&#xff1b; $users DB::table(users) ->join(books, users.id, , books.user_id) ->join(profiles, users.id, , profiles.user_id) ->select(users.id, user…

论文阅读 - Group Normalization

文章目录1 概述2 几种normalization的方法2.1 Batch Norm2.2 Layer Norm2.3 Instance Norm2.4 Group Norm3 效果对比参考文献1 概述 Group Nomralization的提出是为了解决一张GPU上能容纳的batch_size很小&#xff0c;导致模型训练效果显著变差的问题。随着深度学习的快速发展…

十六、PHP框架Laravel学习笔记——构造器的增删改

一&#xff0e;增删改操作 使用 insert()方法可以新增一条或多条记录&#xff1b; //新增一条记录 DB::table(users)->insert([ username > 李白, password > 123456, email > libai163.com, details > 123 ]); //新增多条记录 DB::table(users)->insert…

git如何切换分支_拜托,不要再问我Git分支如何使用

今天来讲讲我使用Git分支的一些经验&#xff0c;记录一下&#xff0c;希望对大家有帮助。阐述在平常开发中&#xff0c;一般都会对应三种环境&#xff0c;本地环境、测试环境、线上环境。开发的基本流程都是先在本地环境开发好,再把代码发布到测试环境测试&#xff0c;最后再发…

搞懂HMM

文章目录1 概述2 符号说明3 两点假设4 Evaluation4.1 前向算法&#xff08;forward algorithm&#xff09;4.2 后向算法&#xff08;backward algorithm&#xff09;5 Learning6 Decoding参考资料1 概述 本文是B站上机器学习-白板推导系列(十四)-隐马尔可夫模型HMM的学习笔记&…

书店售书最低价格问题

书店针对《哈利波特》系列书籍进行促销活动&#xff0c;一共5卷&#xff0c;用编号0、1、2、3、4表示&#xff0c;单独一卷售价8元&#xff0c; 具体折扣如下所示&#xff1a;本数 折扣 2 5% 3 10% 4 …

十七、PHP框架Laravel学习笔记——模型的定义

一&#xff0e;默认设置 框架可以使用 Eloquent ORM 进行数据库交互&#xff0c;也就是关系对象模型&#xff1b; 在数据库入门阶段&#xff0c;我们已经创建了一个 User.php 模型&#xff0c;如下&#xff1a; php artisan make:model Http/Models/User //默认在 app 目录 …

centos 启动一个redis_基于prometheus+grafana体系监控redis缓存服务

概述前面已经介绍了怎么用prometheus监控mysql数据库&#xff0c;今天主要分享下怎么去监控redis服务。由于没有redis环境&#xff0c;所以用docker模拟了一下。一、Docker部署1、下载sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.…

十八、PHP框架Laravel学习笔记——模型的增删改

一&#xff0e;增删改操作 新增方法如下&#xff0c;注意&#xff1a;默认模型接管 created_at 和 updated_at&#xff1b; $users new User(); $users->username 辉夜; $users->password 123; $users->email huiye163.com; $users->details 123; $use…

搞懂语音去噪

文章目录1 概述2 传统语音去噪2.1 谱减法2.2 维纳滤波法3 深度语音去噪参考资料1 概述 语音去噪(noise reduction)又被称为语音增强(speech enhancement)&#xff0c;主要是针对于有人声的音频进行处理&#xff0c;目的是去除那些背景噪声&#xff0c;增强音频中人声的可懂性(…