论文阅读 - Beat Tracking by Dynamic Programming
文章目錄
- 1 概述
- 2 總體框架
- 3. 計算Onset Strength Envelope
- 4 計算全局的Tempo
- 5 基于動態(tài)規(guī)劃計算beats
- 6 參考文獻
1 概述
有背景音樂的短視頻拼接時,如果兩個視頻的拼接點剛好在背景音樂的某個節(jié)拍點上,那么合成的視頻看起來,聽起來,都會非常舒服,這是短視頻合成的一個加分項,這種視頻也就是我們經(jīng)常說的卡點視頻。要做卡點視頻的前提是找到背景音樂中可以卡的點,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)基于動態(tài)規(guī)劃計算beats
我第一次看到這三步,也是一頭霧水,如果是專門做信號處理的人,可能已經(jīng)知道怎么回事了,但這篇文章的的目標受眾是像我一樣,學過或者了解過信號處理卻已經(jīng)忘的差不多的小伙伴。所以,下面會針對這三個步驟詳細解析。
如果只想知道怎么使用的話,librosa已經(jīng)為我們包裝好了一切,三行代碼就搞定了。
import librosay, sr = librosa.load(“your/music/file/path”) tempo, beats = librosa.beat.beat_track(y=y, sr=sr)3. 計算Onset Strength Envelope
onset指的就是某個音符發(fā)出聲音的起點,比如按下鋼琴鍵的那個時刻,又比如撥動琴弦的那個時刻,下圖示意了onset的位置。在這個環(huán)節(jié)中,就是要把一段音頻中所有的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)中得到的結(jié)果進行高通濾波,過濾掉0.4kHz以下的信息,使其局部零均值,再用window_size為20ms高斯窗做平滑處理,得到圖2中最下方的圖,也就是onset strength envelope,記作O(t)O(t)O(t)。
O(t)O(t)O(t)中的各個局部峰值就是能量突增的地方,為什么會能量變化劇烈?當然是因為有新的音符發(fā)聲了。注意,這里的波峰我認為不是onsets的精確時間,而是onsets產(chǎn)生的波峰的時間。不過不同的文章中好像都是把這個onsets的候選了。不過我們不關(guān)心onsets具體到底在哪里,只要有O(t)O(t)O(t)就夠了,所以不糾結(jié)定義問題了。
還有一個就是,我比較奇怪論文的作者為什么要用(5)這樣的歸一化方式,這樣得到的O(t)O(t)O(t)就會有很多負值,但是這些負值我們是不要的。Tempo and Beat Tracking中的歸一化方式我覺得更合理一些,直接減去local average,就可以了。下圖3中的最上方紅線就是local average,減去后得到的結(jié)果為圖3中間的那個圖,圖3下方的圖標出了局部峰值和onsets。
4 計算全局的Tempo
Tempo指的是音樂的節(jié)拍,通常用bpm(beats per minute)來度量,比如120bpm就表示一分鐘有120個beats,拍子的周期為0.5s。本文中用拍子的周期來表示Tempo。一首音樂的Tempo有可能是隨時間變化的,但這種情況很少,我們這里只討論整個音頻的tempo都保持一致的情況。變化的Tempo檢測可以參見predominant local pulse,大致思想就是分段處理,這里不討論。
節(jié)拍就是一個調(diào)子在不斷循環(huán),我們要找出這個循環(huán)的周期。自相關(guān)函數(shù)用來找周期是在適合不過的了,我們會計算不同延遲時間下O(t)O(t)O(t)的自相關(guān)函數(shù)值,值最高的對應的延遲時間就是一個beat的長度。
但是這里有一個問題,如圖4的raw autocorrelation所示,周期函數(shù)的在他基周期的倍數(shù)上的自相關(guān)函數(shù)值都是很大的。為了解決這個問題,論文引入了一個權(quán)重系數(shù),使得周期結(jié)果偏向于某個經(jīng)驗值。這個計算自相關(guān)系數(shù)的計算公式為
TPS(τ)=W(τ)∑tO(t)O(t?τ)TPS(\tau)=W(\tau)\sum_t O(t)O(t-\tau) TPS(τ)=W(τ)t∑?O(t)O(t?τ)
其中,τ\tauτ是延遲的時間,TPS為Tempo Period Strength的縮寫,是論文作為給這個自相關(guān)計算方法取的名字,使得TPS(τ)TPS(\tau)TPS(τ)最大的那個τ\tauτ,就是我們要找的周期。
W(τ)W(\tau)W(τ)是一個高斯權(quán)重系數(shù),表示為
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στ?是表示偏重程度的一個系數(shù)。τ0\tau_0τ0?和στ\sigma_\tauστ?都是經(jīng)驗值,論文從MIREX-06 Beat Tracking訓練集中統(tǒng)計得來的。統(tǒng)計的方法是填入不同的τ0\tau_0τ0?和στ\sigma_\tauστ?,使得TPS(τ)TPS(\tau)TPS(τ)的得出的最大值和數(shù)據(jù)中標注的Tempo一致性最高的那組τ0\tau_0τ0?和στ\sigma_\tauστ?就是了。
最終得出的τ0\tau_0τ0?為0.5s,也就是120bpm,στ\sigma_\tauστ?為1.4。在該組參數(shù)下的TPS(τ)TPS(\tau)TPS(τ)如圖4中最下方的圖所示。圖中的Primary Tempo Period就是最終的周期。
librosa.beat.beat_track中有一個輸入?yún)?shù)為start_bpm,指的就是τ0\tau_0τ0?,可以人為傳入修改,默認為120。
在實際的使用中,會對TPS(τ)TPS(\tau)TPS(τ)做一些優(yōu)化,變成
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 基于動態(tài)規(guī)劃計算beats
論文以4ms一個步長(250Hz)將時間分段,利用了第3節(jié)和第4節(jié)的結(jié)果,設計了如下的目標函數(shù):
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=1∑N?O(t)+αi=2∑N?F(ti??ti?1?,τp?)
其中,{ti}\{t_i\}{ti?}為找到的NNN個beats;O(t)O(t)O(t)就是第3節(jié)中的Onset Strenghth Envelope;α\alphaα為平衡兩個目標項的系數(shù);τp\tau_pτp?就是第4節(jié)中得到的周期;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τp?△t?)2
可見當△t\triangle t△t和τ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?左右微調(diào),且使得點落O(t)O(t)O(t)局部峰值附近的一組點。
論文用動態(tài)規(guī)劃的方法,以線性的時間復雜度,解決了這個問題。首先定義了C?(t)C^*(t)C?(t),表示只考慮時間點不大于時刻ttt時,以時刻ttt為一個beat的C({ti})C(\{t_i\})C({ti?})的最大值,這也就是動態(tài)規(guī)劃中的狀態(tài)轉(zhuǎn)移函數(shù),其表達式為:
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...t?4msmax?{αF(t?τ,τp?)+C?(τ)}
這個和標準的動態(tài)規(guī)劃還是有點區(qū)別,要細細品味一下,這個地方我思考了挺久,這種做法可以避免在計算狀態(tài)轉(zhuǎn)移時,之前的最優(yōu)beats序列發(fā)生變化。這個C?(t)C^*(t)C?(t)還有一個好處,就是可以用來找結(jié)尾,當C?(t)C^*(t)C?(t)的增量驟減時,就是音樂轉(zhuǎn)弱,也就是結(jié)尾的地方了。
同時,也會記錄使得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...t?4msmax?{α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τ=t?2τ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)tN?1?=P?(tN?),然后由此一直回溯出整條序列{ti}\{t_i\}{ti?}。有一點可以確定的是,tNt_NtN?必然在[總時長?τp,總時長][總時長-\tau_p, 總時長][總時長?τp?,總時長]的范圍內(nèi),不然就還可以再加入一個beat。
說到這里正文已經(jīng)說完了。這里再簡單說一下,為什么不能按標準的動態(tài)規(guī)劃的做法,不然下次自己來看可能又要想半天。按標準的做法,會定義C?(tj)C^*(t_j)C?(tj?)為時間點不大于時刻tjt_jtj?時,C({ti})C(\{t_i\})C({ti?})的最大值。狀態(tài)轉(zhuǎn)移函數(shù)為
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...tj?1?max?{αF(tj??P?(τ),τp?)+C?(τ)},C?(tj?1?)}
解釋一下就是,有兩種選擇,前一種是把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后,前面的最優(yōu)beats可能會發(fā)生變化。如果按標準的做法來,會漏掉許多時間點作為beat的情況。細品,細品。
6 參考文獻
[1] Beat Tracking by Dynamic Programming
[2] Tempo and Beat Tracking
總結(jié)
以上是生活随笔為你收集整理的论文阅读 - Beat Tracking by Dynamic Programming的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: es6 数组合并_JavaScript学
- 下一篇: python网络爬虫系列(五)——数据提