2016年3月7日月曜日

自己相関関数で音声の繰り返し部分を抽出する[Python]

フーリエ変換でミクを描いたものの,フーリエ変換をちゃんと勉強したことがなかったので『メカトロニクス入門シリーズ 信号処理入門』を読んでます.

その中で「自己相関関数」というものが紹介されており,非常に面白そうだったので実装してみることにします.


1.自己相関関数とは

「あー」というような定常な音の場合,同じような音の波形が繰り返されています.
すなわち,f(t)を時間tの時の音の大きさだとすると,f(t)には次のような性質を持つことがわかります. \[ f(t) = f(t + nT) \] もっとも,実際の音声では完全にイコールにはならないと思いますが,概ねこのような性質を持っています.こういった関数を周期Tの周期関数と呼びます.例えばサイン関数は周期2πの周期関数です.

この周期Tを求める手法として上記の本では「自己相関関数」を用いています.関数fに対する自己相関関数R_ffを,今回次のように定義します.
\[ R_{ff}(\tau) = \frac{(f(t),f(t+\tau))}{||f(t)||\,\,||f(t+\tau)||}\\ \] ただし,実装するときにどうするかをはっきりしたいので,関数の内積とノルムは次のように定義します. \[ (f(t),f(t+\tau)) = \int_{t_0}^{t_1} f(t)f(t+\tau)dt \\ ||f(t)|| = \sqrt{\int_{t_0}^{t_1}f(t)^2 dt }\\ ||f(t+\tau)|| =\sqrt{ \int_{t_0}^{t_1}f(t+\tau)^2 dt} \\ \]
関数f(t)とf(t+τ)の相関係数みたいなものなので,関数のノルムで割るかどうかや,関数の平均値を減じるかどうかは人それぞれだと思います.

さてこの自己相関関数R_ffを実際に計算していくとこのようになります.
τを変化していき,τ=nT ( n = 0,1,2,... )となる点で似たパターンとなるため,R_ffが極大値を取ることがわかります.

2.抽出アルゴリズム

ソースコードを貼る前に,実際にプログラムを組んで「あー」という音を読み込ませて,その音声に対する自己相関関数を求めるとこんな感じになります.
とまぁ現実の音をやってみるとそんなうまくいかないようです.結構ギザギザしてて嫌な感じです.

恐らくフラクタル図形のように「あー」という音の中にも似た部分があるようです.それはそれで面白いのですが…….

まぁ自分の目で見ればどれが目的の周期なのか一目瞭然なのですが,できれば自動的に判定したいところです.

とりあえずFFTで高周波成分を除去するハイパスフィルタを実装するとこんな感じになります.緑色の部分がハイパスフィルタで高周波成分を除去したもの.移動平均でも良いと思います.
そんでもってscipy.signalの極大値の要素番号をsignal.argrelmaxで求めて,しきい値0.2よりも大きい相関以上かつ極大となっている要素番号を取得すると,{5,  646, 1291, 1938, 2582, 3228, 3872, 4520} みたいな等差数列になっているので差を求めてやればそれっぽい周期が求められる.

その周期で音声の波形を10個ほど取り出すとこんな感じになった.
繰り返されている部分がいい感じに取り出せました.とは言うものの周期Tをint型に型キャストしているのでズレていってしまいますがまぁこんなもんでしょう.

この繰り返されている波形データだけで音声認識とかできないか考え中なところです.

3.プログラム

コメントは多めにしたつもりではありますがうまくいかない場合はコメントを参考にしつつ頑張って下さい.