音声合成を試す2
8.解説
色々調べたことをまとめました。間違っている箇所があれば気づき次第更新しますのでご容赦を。
(1)メル スペクトログラム(mel-spectrogram)
以下のホームページで丁寧に解説されてます。
以下はメル スペクトログラムの例です。”あ”を出しながら音の高さを上げていきました。縦軸は周波数、横軸は時間軸、色は周波数成分の音の強さです。
メル スペクトログラムはの特徴は次の通りです。
・各周波数成分の音の強さが、どう時間変化していくかを示すもの。なお周波数成分の位相情報は捨てています。
・周波数はメル スケールで抽出する。すなわち低い周波数はより細かく、高い周波数は間引いてサンプルします。これは人が高音は周波数の違いに鈍感で、低温は周波数の違いに敏感なためです。
実装を見てみます。学習のためにメル スペクトグラムを生成する箇所の抜粋です。
layers.py Short-time Fourier transform (STFT)
class TacotronSTFT(torch.nn.Module):
def __init__(self, filter_length=1024, hop_length=256, win_length=1024,
n_mel_channels=80, sampling_rate=22050, mel_fmin=0.0,
mel_fmax=8000.0):
・
mel_basis = librosa_mel_fn(
sampling_rate, filter_length, n_mel_channels, mel_fmin, mel_fmax)
→librosa_mel_fn=librosa.filters.mel、メル周波数分布に変換するためのマトリクス係数を算出
・
・
def mel_spectrogram(self, y):
magnitudes, phases = self.stft_fn.transform(y) fftで週波数成分の大きさ、位相を計算
magnitudes = magnitudes.data 大きさを取り出し
mel_output = torch.matmul(self.mel_basis, magnitudes) メル周波数分布に変換
mel_output = self.spectral_normalize(mel_output)
return mel_output
サンプリング周波数 22.05ksps
1回のFFT数 filter_length 1024(22.05ksps時、46ms分のデータを1回のFFTで使用する)
ホップ数 hop_length 256 256サンプルづつずらしながらFFTする(11.6ms)
周波数範囲(mel_fmin~mel_fmax) 0~8kHz
メルchannel 80 上記8kHzをメルスケールで80分割するという意味
メル スペクトログラム配列の大きさは次のようになります。
=メルchannel × (時間(s)×サンプリング周波数 / hop_length)
(2)発声の仕組み
有声音(声帯から出る音を使って発する声)の場合、声帯から周期的に音源パルスを出して、喉、口で共鳴させて声を作ります。共鳴周波数は口の形、舌の位置等で変わり、メル スペクトログラムに特徴として現れます。
(3)耳での音の周波数成分分解
耳では蝸牛で音を周波数変換して、その時々の音の周波数成分ごとの強さを感じ取っています。蝸牛は実際はかたつむりのように巻いていますが、下の図はイメージをつかむために引き延ばして書いてあります。
(4)声帯の発生音周波数と声道(喉、口)の共鳴スペクトラム
(1)のメル スペクトログラムの明るい線はすべて声帯が出している発生音及びその高調波です。声道の特徴は一見してはこの図からはわかりません。先ほどのメルスペクトログラムから1s、4sを取り出したもの(スペクトラム)が下図です。
細かい山谷は声帯の出す高調波で、なだらかな山谷(赤線)が声道の共鳴の特性です。細かい山谷は声の高さで変わっています(1sは低い声、4秒は高い声)が、赤い特徴はあまり変化しません。
(5)ケプストラム
上記のスペクトラムから、細かい山谷と、なだらかな山谷を分離できれば声帯と声道の共鳴スペクトラムを分離することができます。上記のスペクトラムをもう一回フーリエ変換したものをケプストラムと呼びます。
ケプストラムから高い周波数成分(上記(4)の細かい山谷)をカットして、逆フーリエ変換して戻すと、声道の共鳴スペクトラム(赤線)だけ取り出すことができます。
9.実験
少し実際に音声データを録音して実験してみます。
(1)wav録音、Googleドライブに保存
どんなツールでもかまいませんが、wavファイルで録音します。特に手持ちがなければ、次のソフトは軽量で(インストールの必要がなく、ダウンロード容量も約240kBtyte)、かつパソコンのマイクから録音できてお勧めです。
・ファイル名 test1.wav
・周波数22kHz、チャンネル数モノラル、ビット数16bit、
として録音し、Googleドライブのtacotron2フォルダにアップロードします。
(2)Googleドライブのtacotron2フォルダで右クリックして、その他-Google Colaboratoryで新規ノートブックを立ち上げます。
(3)パッケージインストール
!pip install librosa
!pip install pillow
(4)Googlleドライブマウント
from google.colab import drive
drive.mount('/content/drive')
(5)パッケージのインポート
import matplotlib
import matplotlib.pylab as plt
import numpy as np
import librosa
from librosa import display
(6)Googleドライブのwavデータ読み込み
test1_data ,test1_rate = librosa.load("/content/drive/My Drive/tacotron2/test1.wav")
(7)波形表示
波形を表示させてみます。赤字は拡大したいところの時間です。全体の波形を見て手動で設定します。波形はあいうえおを区切って発音したものです。
#全体
fig = plt.figure(figsize=(16, 20))
test1_time = np.arange(0, test1_data.shape[0]/test1_rate, 1/test1_rate) #x軸の値(データ数から時間を計算)
fig_1 = fig.add_subplot(6,1,1)
fig_1.plot(test1_time,test1_data)
#拡大 あ
fig_2 = fig.add_subplot(6,1,2)
fig_2.plot(test1_time,test1_data)
fig_2.set_xlim(1.1,1.2)
#拡大 い
fig_3 = fig.add_subplot(6,1,3)
fig_3.plot(test1_time,test1_data)
fig_3.set_xlim(1.8,1.9)
#拡大 う
fig_4 = fig.add_subplot(6,1,4)
fig_4.plot(test1_time,test1_data)
fig_4.set_xlim(2.5,2.6)
#拡大 え
fig_5 = fig.add_subplot(6,1,5)
fig_5.plot(test1_time,test1_data)
fig_5.set_xlim(3.2,3.3)
#拡大 お
fig_6 = fig.add_subplot(6,1,6)
fig_6.plot(test1_time,test1_data)
fig_6.set_xlim(3.8,3.9)
fig.show
(8)メル スペクトグラム
メル スペクトログラムを表示します。
#mel spectrogram
S = librosa.feature.melspectrogram(test1_data, sr=test1_rate, n_mels=128)
S_dB = librosa.power_to_db(S, ref=np.max)
plt.figure(figsize=(12, 4))
librosa.display.specshow(S_dB, sr=test1_rate, x_axis='time', y_axis='mel')
plt.title('Mel-frequency spectrogram')
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()
plt.show
#mel スペクトラム
mel_x = np.arange(0, 128, 1) #x軸を生成、単純数列
spec_t=[1.2,1.8,2.5,3.2,3.8]
for t in spec_t:
plt.figure(figsize=(12, 4))
t = int(t/(512/22050)) #1.1s時点でのmelスペクトラム
plt.plot(mel_x,S_dB.T[t])
plt.xlabel("mel")
plt.ylabel("dB")
plt.show
#melに相当する周波数の計算(参考)
print("mel 20=",librosa.mel_to_hz((20.0/128.0*librosa.hz_to_mel(8000))))
print("mel 40=",librosa.mel_to_hz((40.0/128.0*librosa.hz_to_mel(8000))))
print("mel 60=",librosa.mel_to_hz((60.0/128.0*librosa.hz_to_mel(8000))))
print("mel 80=",librosa.mel_to_hz((80.0/128.0*librosa.hz_to_mel(8000))))
print("mel 100=",librosa.mel_to_hz((100.0/128.0*librosa.hz_to_mel(8000))))
あいうえおと発音した時のメル スペクトログラムです。細い線は声帯の周波数成分(高調波)です。わかりやすいように、各発声時のスペクトラムも表示しました。
(9)ケプストラム1
ケプストラムを計算した後、逆変換してメル スペクトグラムに戻します。ほぼ元の形に戻りました。
#mel cepstral
mfccs = librosa.feature.mfcc(test1_data, sr=test1_rate, n_mfcc=128)
plt.figure(figsize=(10, 4))
librosa.display.specshow(mfccs, x_axis='time')
plt.colorbar()
plt.title('MFCC')
plt.tight_layout()
plt.show()
#mfcc to mel
S_dB_from_mfccs = scipy.fftpack.idct(mfccs, axis=0, type=2, norm='ortho', n=128)
plt.figure(figsize=(12, 4))
librosa.display.specshow(S_dB_from_mfccs, sr=test1_rate, x_axis='time', y_axis='mel')
plt.title('Mel-frequency spectrogram')
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()
plt.show
#mel スペクトラム
mel_x = np.arange(0, 128, 1) #x軸を生成、単純数列
spec_t=[1.2,1.8,2.5,3.2,3.8]
for t in spec_t:
plt.figure(figsize=(12, 4))
t = int(t/(512/22050)) #1.1s時点でのmelスペクトラム
plt.plot(mel_x,S_dB_from_mfccs.T[t])
plt.xlabel("mel")
plt.ylabel("dB")
plt.show
(10)ケプルトラム2
ケプストラムのうち高域をカットしてメル スペクトログラムに戻します。声帯の高調波成分がカットされていることがわかります。変更したのは赤字のところ1か所(低域から数えていくつの成分まで返り値とするかの設定)だけです。
#mel cepstral
mfccs = librosa.feature.mfcc(test1_data, sr=test1_rate, n_mfcc=20)
・
・
後は同じ
スペクトラムの細かい山谷が無くなって声道の特性だけを取り出せていることがわかります。ケプストラムの高調波分を無くすということはローパスフィルタを通すのと同様の効果があります。
ここまでのノートブックのpdfを参考に貼っておきます。
https://drive.google.com/file/d/1_Fvk5tWHgBLKrgJSoRTzBCdBKF1D0okf/view?usp=sharing
試すのはいったんおしまいです。
次回以降はtacotron2の中身を調べていきたいと思います。
では!