SPTKの使い方 (7) メルケプストラム分析合成
SPTKの使い方 (6)(2012/8/5)の続き。
今回は、SPTK(2012/7/1)を使って音声合成の実験をしてみました。ここで言う音声合成は、テキストを音声に変換するTTS(Text-to-Speech)ではなく、分析合成と呼ばれるものです。
分析合成では、人間の音声からパラメータを抽出し、ソース・フィルタモデル(Wikipedia)を用いて音声を再合成します。いわゆるヴォコーダーという技術です。ヴォコーダー(Wikipedia)を調べると
本来の意味は通信用の音声圧縮技術で、携帯電話などの多くの機器で使用されている。音声の波形を直接送るのではなくパラメータ化して送り、受信側ではそれらのパラメータから元の音声を合成する。
ヴォコーダー(Wikipedia)
とあります。ん?ということは、携帯電話で聞いているのは実際の肉声ではなく、パラメータから再合成した合成音声なのですかね?これは興味深い。
今まで取り上げてきたSPTKというオープンソースのライブラリを使うと簡単に分析合成の実験をすることができます。SPTKには、
- メルケプストラム
- LPC
- PARCOR
- LSP
- メル一般化ケプストラム
- メル周波数ケプストラム(MFCC)
という6つのパラメータが実装されています。今回は、一番上のメルケプストラムというパラメータで分析合成を試します。残りは次回以降取り上げます。
ソース・フィルタモデル
人間の音声は、ノドの声帯を振動させてブザーのような音を出し、その音が声道、口、唇を通過することで「こんにちは」などの音声が出てくる仕組みになっています。これを数学的にモデル化したのがソース・フィルタモデルです。
http://www.kumikomi.net/archives/2010/08/ep30gose.php から引用
音源で声帯振動にあたるブザー音を作り出し、ブザー音をディジタルフィルタに通すことで合成音を作ります。音源では、声の高さを決めるピッチというパラメータを使い、ディジタルフィルタでは声道特性を表すメルケプストラムなどのパラメータを使います。
http://www.kumikomi.net/archives/2010/08/ep30gose.php から引用
音声ファイルの準備
前(2012/7/1)にも使いましたが、SPTKに付属しているサンプル音声を例に試してみます。data.shortというRAWファイルです。こんな音声です。
音声フォーマットは、16000Hz、16bit、モノラル、リトルエンディアンです。もし、WAVEファイルしかないなら、WAVEファイルをSPTKで使えるRAWファイルに変換します。SPTKのwave2rawを使うと簡単です。
# WAVEファイルをRAWファイルに変換する # 出力はdata.raw wav2raw data.wav
逆にRAWファイルをWAVEファイルに戻すにはsoxを使います。RAWファイルには、ファイルフォーマットの情報がないので引数で指定します。
# RAWファイルをWAVEファイルに変換 sox -e signed-integer -c 1 -b 16 -r 16000 data.raw data.wav
ピッチ抽出
まず、音源に関するパラメータであるピッチをpitchコマンドで抽出します。ピッチは声の高さを表すパラメータです。これはピッチ抽出(2012/7/7)ですでに取り上げました。
x2x +sf data.raw | pitch -a 1 > data.pitch
dmp +f data.pitch > pitch.txt
data.pitchはバイナリファイルなのでテキストファイルにダンプしています。gnuplotやmatplotlibで描画できます(後述のスクリプト参照)。
縦軸のpitchは、はじめHzだと思い込んでいたのですが違いました。サンプリング周波数をHzで割った値のようです。なので高い音ほど値が小さくなります。
音源の生成
exciteコマンドでピッチから音源のブザー音を作成します。soprというコマンドで振幅を1000倍し、x2xコマンドでfloatをsigned integerに変換することでRAWファイルを作成しています。
excite -p 80 data.pitch | sopr -m 1000 | x2x +fs > source.raw sox -e signed-integer -c 1 -b 16 -r 16000 source.raw source.wav
こんな音です。
あー、何となく青い植木鉢って言いたそうなのは伝わります(笑)ついでにソースの波形も描画してみます。
ピッチがある区間(有声音)はパルスが立っていて、ピッチがない区間(無声音)はつぶれて見えませんがホワイトノイズになっていることがわかります。パルスはブザーみたいな音、ホワイトノイズはシャーって音に聞こえます。
pitchの値が小さい部分(高い声)はパルス間隔が短く、pitchの値が大きい部分(低い声)はパルス間隔が広いようです。うーん、音源の振幅がピッチの形と似ているのはなぜなんだろう?音の高さを表すピッチと音の大きさを表す振幅は関係無いような気がするんだけど・・・まだまだ勉強不足かも。
この音源を声道パラメータを指定したフィルタに通すと合成音になります。ほんとかな?
メルケプストラムの抽出
メルケプストラムは、声道特性を表すパラメータです。mcepコマンドで抽出できます。音声をframeコマンドでフレームに分割し、windowsコマンドで窓をかけてから、mcepで抽出します。
x2x +sf < data.raw | frame -l 400 -p 80 | window -l 400 -L 512 | mcep -l 512 -m 20 -a 0.42 > data.mcep # -m : 20次元のメルケプストラム # -a : 16000Hzの音声は0.42で固定
data.mcepがメルケプストラムパラメータです。メルケプストラムは、時間ごとに変化していくパラメータです。あとで時間変化に従って、スペクトルとメルケプストラムがどのように変化するか動画で確認できるスクリプトを作る予定ですが、ここでは、音声の65フレーム目の瞬間のスペクトルとメルスペクトラムだけを切り取って図示してみます。
x2x +sf < data.raw | frame -l 400 -p 80 | bcut +f -l 400 -s 65 -e 65 | window -l 400 -L 512 | spec -l 512 | dmp +f > spec.txt bcut +f -n 20 -s 65 -e 65 < data.mcep | mgc2sp -m 20 -a 0.42 -g 0 -l 512 | dmp +f > mcep.txt
specは音声のスペクトルを求めるコマンド、mgc2spはメルケプストラムパラメータをスペクトルに変換するコマンドです。dmpでテキストファイルにダンプしてからgnuplotやmatplotlibでグラフ描画できます。たとえば、gnuplotなら
gnuplot> plot "spec.txt" w l gnuplot> replot "mcep.txt" w l
でグラフが描けます。
青がスペクトル、赤がメルケプストラムです。メルケプストラムは、スペクトルの形状(スペクトル包絡)を表すパラメータだということがわかります。スペクトル形状の違いによって音韻(あいうえおなど)が区別されます。
メルケプストラム分析合成
最後に音源とメルケプストラムパラメータから音声を合成します。mlsadfがフィルタのコマンドです。MLSAフィルタというものらしいんですが詳細は勉強中です。data.mcep.rawが分析合成音です。
excite -p 80 data.pitch | mlsadf -m 20 -a 0.42 -p 80 data.mcep | x2x +fs > data.mcep.raw sox -e signed-integer -c 1 -b 16 -r 16000 data.mcep.raw data.mcep.wav
ではオリジナルの音声と分析合成音を比べてみます。
オリジナルの音声
分析合成音
かなりよく再合成できているのではないでしょうか?
HMM音声合成というのは、ピッチやメルケプストラムのパラメータを確率モデル(HMM)を使って生成する音声合成法のようです。確率モデルの学習や発声内容の時系列に沿ったパラメータ生成が必要なため、分析合成より難易度が高そうです。
Pythonスクリプト
今までのをすべてまとめたPythonスクリプトです。
python mcep.py data.raw 20
のようにRAWファイル(16000Hz、16bit、モノラル、リトルエンディアン)とメルケプストラムの次数(20)を入れて使います。
#coding:utf-8 import os import sys import subprocess import pylab # メルケプストラム分析合成の手順 # 実行にはsox、SPTK、matplotlibが必要 # 使い方: python mcep.py [raw_file] [order of mel cepstrum] def execute(cmd): """コマンドを実行""" subprocess.call(cmd, shell=True) print cmd def draw_figure(dat_file, xlabel="", ylabel="", style="b-", lw=1): """グラフ描画""" fp = open(dat_file, "r") x = [] y = [] for line in fp: line = line.rstrip() dat = line.split() x.append(float(dat[0])) y.append(float(dat[1])) pylab.plot(x, y, style, linewidth=lw) pylab.xlabel(xlabel) pylab.ylabel(ylabel) pylab.xlim(min(x), max(x)) fp.close() if __name__ == "__main__": if len(sys.argv) != 3: print "usage: python mcep.py [raw_file] [order of mel cepstrum]" exit() raw_file = sys.argv[1] # RAWファイル order = int(sys.argv[2]) # メルケプストラムの次数 # 16000Hzの場合は0.42で固定 alpha = 0.42 prefix = raw_file.replace(".raw", "") # RAWファイルをWAVEファイルに変換 # 音声を確認できるように cmd = "sox -e signed-integer -c 1 -b 16 -r 16000 %s.raw %s.wav" % (prefix, prefix) execute(cmd) # ピッチ抽出 cmd = "x2x +sf %s.raw | pitch -a 1 > %s.pitch" % (prefix, prefix) execute(cmd) # ピッチの描画 cmd = "dmp +f %s.pitch > pitch.txt" % prefix execute(cmd) draw_figure("pitch.txt", "frame", "pitch") pylab.savefig("pitch.png") pylab.clf() os.remove("pitch.txt") # 音源の生成 cmd = "excite -p 80 data.pitch | sopr -m 1000 | x2x +fs > source.raw" execute(cmd) cmd = "sox -e signed-integer -c 1 -b 16 -r 16000 source.raw source.wav" execute(cmd) cmd = "dmp +s source.raw > source.txt" execute(cmd) # 音源の描画 draw_figure("source.txt", "sample", "amplitude") pylab.savefig("source.png") pylab.clf() os.remove("source.txt") # メルケプストラム分析 cmd = "x2x +sf < %s.raw | frame -l 400 -p 80 | window -l 400 -L 512 | mcep -l 512 -m %d -a %f > %s.mcep" % (prefix, order, alpha, prefix) execute(cmd) # 65フレームのの対数スペクトルを描画 cmd = "x2x +sf < %s.raw | frame -l 400 -p 80 | bcut +f -l 400 -s 65 -e 65 | window -l 400 -L 512 | spec -l 512 | dmp +f > spec.txt" % prefix execute(cmd) draw_figure("spec.txt", style="b-") # フレーム65のメルケプストラムのスペクトル包絡を描画 cmd = "bcut +f -n %d -s 65 -e 65 < %s.mcep | mgc2sp -m %d -a %f -g 0 -l 512 | dmp +f > mcep.txt" % (order, prefix, order, alpha) execute(cmd) draw_figure("mcep.txt", "frequency bin", "log magnitude [dB]", style="r-", lw=2) pylab.savefig("mcep.png") os.remove("spec.txt") os.remove("mcep.txt") # 分析合成音の作成 cmd = "excite -p 80 %s.pitch | mlsadf -m %d -a %f -p 80 %s.mcep | x2x +fs > %s.mcep.raw" % (prefix, order, alpha, prefix, prefix) execute(cmd) # WAVEファイルに変換 cmd = "sox -e signed-integer -c 1 -b 16 -r 16000 %s.mcep.raw %s.mcep.wav" % (prefix, prefix) execute(cmd)
次回はメルケプストラムの次数を変えると音声がどう変わるか実験してみようと思います。
関連リンク
- ケプストラム分析(2012/2/11)
- SPTKの使い方 (3) ピッチ抽出(2012/7/7)
- 音声分析合成はじめの一歩 - SPTKを用いた分析合成の解説
- 音声の分析・合成処理の基本 - 携帯電話の仕組み
- 初音ミクと音声合成のしくみ - 音声合成のわかりやすい解説
- 小林隆夫, 音声のケプストラム分析、メルケプストラム分析 - 音声分析のサーベイ