音声解析が必要な時があったのでPythonでやってみました.
ちょこちょこ閲覧数も増え, いいねも頂いて有難いのですが, 私は音声解析専門ではありませんのでその辺ご理解ください.
wavデータを作成する
どちらのライブラリを使うにせよ, 基本的にwavデータを使うことになります.
その際, ステレオではなく, モノラルでないといけません. 一応, ターミナルでmp4からwavを作成する方法を載せておきます.
もしffmpegがinstallされていなければ次のコマンドを打ってinstall出来ます(brewが必要です).
$ brew install ffmpeg
変換は次のようにして行います.
$ ffmpeg -y -i file.mp4 -ac 1 -ar 44100 file.wav
-i
は入力ファイルの指定です.
-y
は常に上書きするという指定です.
-ar 44100
はサンプリング周波数の指定です.
wavはモノラル形式でなければなりません. それを -ac 1
で指定しています.
dir構造
次のようにdirectoryを構成しています.
voice_analysis
├── data
│ └── sample.wav
└── src
└── voice_analysis.ipynb
PyWorld
PyWorldとは... ?
C++, matlab用に作られた音声解析ライブラリWorldのPython用のwrapperみたいです.
PyWorldのインストール
githubのREADMEにしたがってinstallすればいいだけです.
と思いきや, installできませんでした.
どうやらPyWorldはanacondaと相性が悪いみたいです...
Does not work in Anaconda #12
自分はpyenvで環境構築しました. 本当はanacondaのpythonで構築しようと思いましたが, なぜか上手く行かなかったので...おそらくはpyenvではなく, systemのpythonでも上手く行くと思います. Anacondaでなければ大丈夫のはずです.
pyenvでのpython3の導入
brewはinstall済みを想定しています.
ただ, これ通りにやると最新のMojaveではzlibエラーが出ます. どうやらxcodeが追いついていないようです.
参考 : MacOS Mojave pyenvでpythonのインストールがzlibエラーで失敗した時の対応
あとは.bashrcなり.zshrcなりにPATHを通せばokです.
# anacondaのPATHは通さないようにコメントアウト
# export PATH="/anaconda/bin:$PATH"
# export PATH=/Users/username/Library/Python/2.7/bin:$PATH
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
ちゃんとpythonはpyenvの方を示すのか確認してみましょう.
$ which python3
/Users/username/.pyenv/shims/python3
$ which pip
/Users/username/.pyenv/shims/pip
大丈夫そうです!
PyWorldのinstall
$ pip install scipy
$ pip install pyworld
scipyを先にinstallしないとinstall出来ない不具合もあるようなので先にinstallしておきます.
PyWorld入門
jupyter notebookで試してみましょう.
今回はsample2として段階的に高さをあげていった音声を自作してみました.
from scipy.io import wavfile
import pyworld as pw
import numpy as np
import matplotlib.pyplot as plt
WAV_FILE = "../data/sample2.wav"
# fs : sampling frequency, 音楽業界では44,100Hz
# data : arrayの音声データが入る
fs, data = wavfile.read(WAV_FILE)
# floatでないとworldは扱えない
data = data.astype(np.float)
_f0, _time = pw.dio(data, fs) # 基本周波数の抽出
f0 = pw.stonemask(data, _f0, _time, fs) # 基本周波数の修正
# --- これは音声の合成に用いる(今回は使わない)
# sp = pw.cheaptrick(data, f0, _time, fs) # スペクトル包絡の抽出
# ap = pw.d4c(data, f0, _time, fs) # 非周期性指標の抽出
# y = pw.synthesize(f0, sp, ap, fs) # 合成
# 可視化
plt.plot(data, label="Raw Data")
plt.legend(fontsize=10)
plt.show();
plt.plot(f0, linewidth=3, color="green", label="F0 contour")
plt.legend(fontsize=10)
plt.show();
これを入力すると... ちゃんと綺麗にピッチが取れました!
- Raw Data
読み込んだデータそのものを可視化したものです.
「あー, あー, あー, あー」と発声しているのがわかります.
- F0 contour
これはピッチです. 綺麗に取得出来ていそうです.
PyWorldの計算時間
果たしてPyWorldの計算時間はどうなっているのでしょうか.
ということでWAVファイルの時間と解析時間について簡単に可視化してみました.
from scipy.io import wavfile
import pyworld as pw
import numpy as np
import matplotlib.pyplot as plt
import time
def calc_time(file_name):
start = time.time()
fs, data = wavfile.read(WAV_FILE)
data = data.astype(np.float)
_f0, _time = pw.dio(data, fs)
f0 = pw.stonemask(data, _f0, _time, fs)
elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
return elapsed_time
def plot_calc_time():
file_names = os.listdir("../data")
plt.figure()
t_list = []
elapsed_time_list = []
for file_name in file_names:
t = int(file_name[:-4])
elapsed_time = calc_time(file_name)
t_list.append(t)
elapsed_time_list.append(elapsed_time)
t_list.sort()
elapsed_time_list.sort()
plt.plot(t_list, elapsed_time_list, marker=".")
plt.title("Time for calculating f0")
plt.xlabel("wav_time")
plt.ylabel("calc_time")
plt.grid()
plt.show();
plot_calc_time()
file_nameに関しては各々のディレクトリ構造に依存するので適宜修正が必要です.
結果はこんな感じでした.
どちらも秒単位. よくわからない挙動を示しているが一応線形でしょうか?
あまり秒数に依らずに高速に解析できています.
pyreaper
pyreaperとは... ?
GoogleがgithubにあげているREAPERのPython用のラッパーです.
A python wrapper for REAPER
pyreaperのinstall
今回もREADMEにしたがってinstallするだけです.
$ pip install pyreaper
これでinstall...と思いましたがinstallできません!
Command "/Users/username/.pyenv/versions/3.7.1/bin/python3.7 -u -c "import setuptools, tokenize;file='/private/var/folders/gw/xyk2qtxd61lg89vs0ckk0wkc0000gn/T/pip-install-ury00gf5/pyreaper/setup.py';f=getattr(tokenize, 'open', open)(file);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, file, 'exec'))" install --record /private/var/folders/gw/xyk2qtxd61lg89vs0ckk0wkc0000gn/T/pip-record-8wj1vx31/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /private/var/folders/gw/xyk2qtxd61lg89vs0ckk0wkc0000gn/T/pip-install-ury00gf5/pyreaper/
よくわからないエラーが出てしまいました.
諸悪の根源anacondaは抹殺したはずなんですが...
どうやらpyreaperの依存関連のエラーのようです.
次を実行して無事installしましょう.
$ git clone https://github.com/r9y9/pyreaper
$ cd pyreaper
$ git submodule update --init --recursive
$ python setup.py develop
次にpysptkのinstallです.
$ pip install pysptk
pyreaper入門
jupyter notebookで試してみましょう.
参考 : How pyreaper works
%pylab inline
rcParams["figure.figsize"] = (16, 4)
%matplotlib inline
import pysptk
import pyreaper
import matplotlib.pyplot as plt
from scipy.io import wavfile
file = "../data/sample2.wav"
fs, data = wavfile.read(file)
plt.plot(data, label="Raw Data")
plt.legend(fontsize=10)
plt.show();
pm_times, pm, f0_times, f0, corr = pyreaper.reaper(x, fs)
plt.plot(pm_times, pm, linewidth=3, color="red", label="Pitch mark")
plt.legend(fontsize=10)
plt.show();
plt.plot(f0_times, f0, linewidth=3, color="green", label="F0 contour")
plt.legend(fontsize=10)
plt.show();
plt.plot(f0_times, corr, linewidth=3, color="blue", label="Correlations")
plt.legend(fontsize=10)
plt.show();
先のwavファイルを入力すると... ちゃんと綺麗にピッチが取れていました!
- Raw Data
読み込んだデータそのものを可視化したものです.
「あー, あー, あー, あー」と発声しているのがわかります.
- Pitch Mark
よく理解出来ていません. 音があるときに値を持っていますし, ピッチが検出できているって印なのでしょうか...?
- F0 contour
これはピッチでしょう. 綺麗に取得出来ていそうです.
- Correlations
これもまだあまり理解出来ていません. 自己相関の値でしょうか.
おそらくピッチの計算として, フーリエ変換した後にピークを検出する方法を用いていて, そのピーク検出に自己相関関数が用いているということでしょうか.
pyreaperの計算時間
果たしてpyreaperの計算時間はどうなっているのでしょう.
ということでWAVファイルの時間と解析時間について簡単に可視化してみました.
import pysptk
import pyreaper
import sys,os
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
def calc_time(file_name):
start = time.time()
fs, data = wavfile.read(file_name)
pm_times, pm, f0_times, f0, corr = pyreaper.reaper(data, fs)
elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
return elapsed_time
def plot_calc_time():
file_names = os.listdir("../data")
plt.figure()
t_list = []
elapsed_time_list = []
for file_name in file_names:
t = int(file_name[:-4])
elapsed_time = calc_time(file_name)
t_list.append(t)
elapsed_time_list.append(elapsed_time)
t_list.sort()
elapsed_time_list.sort()
plt.plot(t_list, elapsed_time_list, marker=".")
plt.title("Time for calculating f0")
plt.xlabel("wav_time")
plt.ylabel("calc_time")
plt.grid()
plt.show();
plot_calc_time()
file_nameに関しては各々のディレクトリ構造に依存するので適宜修正が必要です.
結果はこんな感じでした.
どちらも秒単位. これは...計算時間は二次関数なのか指数関数なのか分かりかねます.
調べてみると, (高速)フーリエ変換は標本数 $N$に対して$O(N^2)$または,$O(N logN)$であるらしく, ピッチ解析の基本はFFTとピーク検出のはずなのでだいたいこれ $+\alpha$と言ったところでしょうか...?