Help us understand the problem. What is going on with this article?

Pythonで音声解析入門 [PyWorld, pyreaper]

音声解析が必要な時があったのでPythonでやってみました.

ちょこちょこ閲覧数も増え, いいねも頂いて有難いのですが, 私は音声解析専門ではありませんのでその辺ご理解ください.

wavデータを作成する

どちらのライブラリを使うにせよ, 基本的にwavデータを使うことになります.
その際, ステレオではなく, モノラルでないといけません. 一応, ターミナルでmp4からwavを作成する方法を載せておきます.
もしffmpegがinstallされていなければ次のコマンドを打ってinstall出来ます(brewが必要です).

Installの方法
$ 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済みを想定しています.

参考 : Mac環境へのPython3系インストール

ただ, これ通りにやると最新のMojaveではzlibエラーが出ます. どうやらxcodeが追いついていないようです.
参考 : MacOS Mojave pyenvでpythonのインストールがzlibエラーで失敗した時の対応

あとは.bashrcなり.zshrcなりにPATHを通せばokです.

.zshrc
# 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の方を示すのか確認してみましょう.

pythonの環境の確認
$ which python3
/Users/username/.pyenv/shims/python3

$ which pip
/Users/username/.pyenv/shims/pip

大丈夫そうです!

PyWorldのinstall

scipyとpyworldのinstall
$ pip install scipy
$ pip install pyworld

scipyを先にinstallしないとinstall出来ない不具合もあるようなので先にinstallしておきます.

PyWorld入門

jupyter notebookで試してみましょう.

参考 : 音声合成システム WORLD に触れてみる

今回はsample2として段階的に高さをあげていった音声を自作してみました.

hello_pyworld.ipynb
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

読み込んだデータそのものを可視化したものです.
「あー, あー, あー, あー」と発声しているのがわかります.

pyworld_rpw.png


  • F0 contour

これはピッチです. 綺麗に取得出来ていそうです.

pyworld_f0.png

PyWorldの計算時間

果たしてPyWorldの計算時間はどうなっているのでしょうか.
ということでWAVファイルの時間と解析時間について簡単に可視化してみました.

calc_pyworld.ipynb
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に関しては各々のディレクトリ構造に依存するので適宜修正が必要です.
結果はこんな感じでした.

pyworld_time.png

どちらも秒単位. よくわからない挙動を示しているが一応線形でしょうか?
あまり秒数に依らずに高速に解析できています.

pyreaper

pyreaperとは... ?

GoogleがgithubにあげているREAPERのPython用のラッパーです.
A python wrapper for REAPER

pyreaperのinstall

今回もREADMEにしたがってinstallするだけです.

pyreaperの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しましょう.

pyreaperのinstall
$ git clone https://github.com/r9y9/pyreaper
$ cd pyreaper
$ git submodule update --init --recursive
$ python setup.py develop

次にpysptkのinstallです.

pysptkのinstall
$ pip install pysptk

pyreaper入門

jupyter notebookで試してみましょう.

参考 : How pyreaper works

hello_pyreaper.ipynb
%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

読み込んだデータそのものを可視化したものです.
「あー, あー, あー, あー」と発声しているのがわかります.

reaper_1.png


  • Pitch Mark

よく理解出来ていません. 音があるときに値を持っていますし, ピッチが検出できているって印なのでしょうか...?

reaper_2.png


  • F0 contour

これはピッチでしょう. 綺麗に取得出来ていそうです.

reaper_3.png


  • Correlations

これもまだあまり理解出来ていません. 自己相関の値でしょうか.

おそらくピッチの計算として, フーリエ変換した後にピークを検出する方法を用いていて, そのピーク検出に自己相関関数が用いているということでしょうか.

reaper_4.png

pyreaperの計算時間

果たしてpyreaperの計算時間はどうなっているのでしょう.
ということでWAVファイルの時間と解析時間について簡単に可視化してみました.

calc_pyreaper.ipynb
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に関しては各々のディレクトリ構造に依存するので適宜修正が必要です.
結果はこんな感じでした.

calc_pyreaper.png

どちらも秒単位. これは...計算時間は二次関数なのか指数関数なのか分かりかねます.
調べてみると, (高速)フーリエ変換は標本数 $N$に対して$O(N^2)$または,$O(N logN)$であるらしく, ピッチ解析の基本はFFTとピーク検出のはずなのでだいたいこれ $+\alpha$と言ったところでしょうか...?

参考 : 高速フーリエ変換 by Wikipedia

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away