ディジタル音声処理特論レポート¶

課題3「フォルマント周波数を変化させると'a'の音声を他の母音の音声に変化させられるのか」¶

学籍番号:G2XTX0XX, 氏名:○○ ○○¶

授業では音声について学習し,母音や子音の特徴を学んだ. 母音はフォルマント周波数が違うことによって音声に違いが生まれる. そこでフォルマント周波数をスペクトル操作によって変えることで,母音の'a'の音声を'i'などの他の母音に聞こえるように変化させられるかどうかを試した.

授業で用いたあいうえおのサンプルボイスを用いる. ここでは教科書第7章のコードを用いて aiueoの音声をa/i/u/e/oに分離し,'a'の音声について処理する. Fig.1はサンプル音声の波形である.

In [26]:
fs, wave_data = scipy.io.wavfile.read ('sample/voice[aiueo]fs16kHz.wav') # 標本化周波数と音声データの読み込み
print("Sampling frequency [Hz] :", fs)
sampling_interval = 1.0 / fs                          # 標本化周期は,標本化周波数の逆数
times = np.arange ( len ( wave_data )) * sampling_interval # 時間軸データの配列

plt.title("Fig.1")
plot_wave ( times , wave_data )

audio = Audio(wave_data, rate = fs)
audio
Sampling frequency [Hz] : 16000
Out[26]:
Your browser does not support the audio element.

Fig.2は横軸をサンプル点数に変えた図である.

In [27]:
plt.title("Fig.2")
plot_wave([], wave_data)

aiueoそれぞれの音声を区別してスペクトルを確認するために以下のプログラムを用いる.Fig.3は切り出した1024点の'a'の音声波形である.

In [28]:
nfft = 1024      # FFTの点数は1024
start = np.array([2800, 4800, 6000, 9000, 11000])
# 各母音の開始ポイントをまとめて宣言

target = 0      # 対象として 0番目の音,すなわち /a/ を選択
voice_interval = (start[target], start[target] + nfft)
# 対象音の切り出し区間を設定します。

voice_data = wave_data[ voice_interval[0] : voice_interval[1] ]
# 対象となるデータをスライスで切り出し表示
plt.title("Fig.3")
plot_wave([], voice_data)
audio = Audio(voice_data, rate = fs)
audio
Out[28]:
Your browser does not support the audio element.

Fig.3の'a'の音声波形をFFTしてスペクトルを確認する.このスペクトルをFig.4に示す.

In [29]:
voice_data = hanning( len(voice_data) ) * voice_data
# FFT する前にハニング窓かけ
sp = np.fft.fft(voice_data)
print("Fig.4")
draw_FFT_spectrum(sp, fs, phase_spectrum = False, stem = False)
# (phase_spectrum = False) 振幅スペクトルのみ表示します。
# (stem = False) 点数が多いので,折れ線グラフで表示します
Fig.4

わかりやすくするために,縦軸をデシベル表記に変更する.

In [30]:
print("Fig.5")
draw_FFT_spectrum(sp, fs = 16000, phase_spectrum = False, real_wave = True, \
                  stem = False, level = True, draw_range = 90,)
# (real_wav  = True) ナイキスト周波数(標本化周波数の1/2)までの描画に限定
#                     実数波形ならば,振幅スペクトルは偶関数なのでナイキスト周波数以上は省略可能
# (level = True) 縦軸を相対レベルで表示し,-90 dB まで描画
Fig.5

まずはaiueo音声の'a'の音声のフォルマント周波数を調べた. 第一フォルマント周波数をF1,第二フォルマント周波数をF2とすると, 音声サンプルのF1は500 Hz付近,F2は1 kHz付近である.

次にこの'a'の音声を'i'に変換する. 授業資料に示されている成人男性の'i'の発音は おおよそF1=300 Hz,F2=2.2 kHzとなっている.

これらの周波数の成分を増幅させ,それ以外を減衰させれば'i'の音声に近づくことが予想される.

そこで,0~600Hz間,特に250~350Hzを増幅,600~1200Hzを減衰,2000から2200Hzを増幅,2200Hz以上を減衰するようなスペクトルの操作を行った.

In [31]:
# スペクトルを変形する処理を実装
def apply_frequency_mask(sp, fs, lower_cutoff, upper_cutoff, attenuation_factor):
    # FFTの周波数軸を取得
    freq_axis = np.fft.fftfreq(len(sp), d=1/fs)

    # マスクを作成
    mask = np.ones_like(sp)

    # 指定された周波数帯域での減衰・増幅
    mask[(freq_axis >= lower_cutoff) & (freq_axis <= upper_cutoff)] *= attenuation_factor

    # FFTにマスクを適用
    sp *= mask
    # 負の周波数成分の処理
    sp_reverse = sp[::-1].conj() # 複素共役をとって,逆順に並べた
    sp[len(sp) // 2:] = sp_reverse[0: len(sp) // 2]
In [32]:
# マスクを適用
apply_frequency_mask(sp, fs, 0, 600, 10.0)
apply_frequency_mask(sp, fs, 250, 350, 10.0)
apply_frequency_mask(sp, fs, 600, 1200, 0.5)
apply_frequency_mask(sp, fs, 2000, 2200, 10.0)


print("Fig.6")
draw_FFT_spectrum(sp, fs = 16000, phase_spectrum = False, real_wave = True, \
                  stem = False, level = True, draw_range = 90)
 # 逆FFTで波形に変換
waveform = np.fft.ifft(sp).real
 # 波形を再生
Audio(waveform, rate=fs)
Fig.6
Out[32]:
Your browser does not support the audio element.

Fig.6はスペクトル操作後の'a'のスペクトルである.このスペクトルを時間波形に戻し音声を聞くと音声に変化があり,'i'に聞こえる.

つまり,第一フォルマント周波数と第二フォルマント周波数を変えるだけでもある程度母音の発音を変えられることが確認できる.どうしたらより'i'の音声に近づくかを調べるために,サンプル音声の'i'についてもスペクトルを確認する.

In [33]:
nfft = 1024      # FFTの点数は1024
start = np.array([2800, 4800, 6000, 9000, 11000])
# 各母音の開始ポイントをまとめて宣言

target = 1       # 対象として 0番目の音,すなわち /a/ を選択
voice_interval = (start[target], start[target] + nfft)

voice_data = wave_data[ voice_interval[0] : voice_interval[1] ]
plt.title("Fig.7")
plot_wave([], voice_data)
audio = Audio(voice_data, rate = fs)
audio

voice_data = hanning( len(voice_data) ) * voice_data
# FFT する前にハニング窓かけ
sp = np.fft.fft(voice_data)
# (phase_spectrum = False) 振幅スペクトルのみ表示します。
# (stem = False) 点数が多いので,折れ線グラフで表示します
print("Fig.8")
draw_FFT_spectrum(sp, fs = 16000, phase_spectrum = False, real_wave = True, \
                  stem = False, level = True, draw_range = 90,)
# (real_wav  = True) ナイキスト周波数(標本化周波数の1/2)までの描画に限定
#                     実数波形ならば,振幅スペクトルは偶関数なのでナイキスト周波数以上は省略可能
# (level = True) 縦軸を相対レベルで表示し,-90 dB まで描画
Fig.8

Fig.7は'i'の音声波形,Fig.8は'i'の音声スペクトルである. 'a'の音声スペクトルを操作したFig.6と比較すると,'i'の音声では高い周波数範囲において比較的強いスペクトル成分を持つ.

一方で'a'のスペクトルであるFig.6では約5500Hz付近からスペクトル強度が小さくなっている.よってこの成分を増幅させればより'i'の発音に近くなることが予想される.

In [34]:
nfft = 1024      # FFTの点数は1024
start = np.array([2800, 4800, 6000, 9000, 11000])
# 各母音の開始ポイントをまとめて宣言

target = 0       # 対象として 0番目の音,すなわち /a/ を選択
voice_interval = (start[target], start[target] + nfft)
# 対象音の切り出し区間を設定します。

voice_data = wave_data[ voice_interval[0] : voice_interval[1] ]
voice_data = hanning( len(voice_data) ) * voice_data

sp = np.fft.fft(voice_data)

# マスクを適用
apply_frequency_mask(sp, fs, 0, 600, 10.0)
apply_frequency_mask(sp, fs, 250, 350, 10.0)
apply_frequency_mask(sp, fs, 600, 1200, 0.8)
apply_frequency_mask(sp, fs, 2000, 2200, 20)
apply_frequency_mask(sp, fs, 5500, 7000, 20)


print("Fig.9")
draw_FFT_spectrum(sp, fs = 16000, phase_spectrum = False, real_wave = True, \
                  stem = False, level = True, draw_range = 90)
 # 逆FFTで波形に変換
waveform = np.fft.ifft(sp).real
 # 波形を再生
Audio(waveform, rate=fs)
Fig.9
Out[34]:
Your browser does not support the audio element.

Fig.9は5500Hz~7000Hzまで増幅させたスペクトルである.高周波数の影響が強まったことにより音声は先ほどよりも'i'に近づいた.

授業資料を参考にすると'a'と'i'の音声はフォルマント周波数が離れており,区別しやすいと考えられる.一方で'a'と'o'の音声は近いフォルマント周波数を持っている.

そこで'a'を'o'に変換する操作も試す.'o'の音声について,授業資料からF1=600Hz,F2=900Hz付近であることが読み取れる.

先ほどと同様にF1とF2のみを変化させた処理を行う.スペクトルは550~650Hzを増幅,651~850Hzを減衰,851~951Hzを増幅させる.

In [35]:
nfft = 1024      # FFTの点数は1024
start = np.array([2800, 4800, 6000, 9000, 11000])
# 各母音の開始ポイントをまとめて宣言

target = 4      # 対象として 4番目の音,すなわち /o/ を選択
voice_interval = (start[target], start[target] + nfft)
# 対象音の切り出し区間を設定します。

voice_data = wave_data[ voice_interval[0] : voice_interval[1] ]
voice_data = hanning( len(voice_data) ) * voice_data

sp = np.fft.fft(voice_data)

# マスクを適用
apply_frequency_mask(sp, fs, 550, 650, 10.0)
apply_frequency_mask(sp, fs, 651, 850, 0.8)
apply_frequency_mask(sp, fs, 851, 950, 10.0)


print("Fig.10")
draw_FFT_spectrum(sp, fs = 16000, phase_spectrum = False, real_wave = True, \
                  stem = False, level = True, draw_range = 90)
 # 逆FFTで波形に変換
waveform = np.fft.ifft(sp).real
 # 波形を再生
Audio(waveform, rate=fs)
Fig.10
Out[35]:
Your browser does not support the audio element.

Fig.10は'a'の音声スペクトルを'o'に寄せたものである.音声を聞くと,明瞭な発音ではないが'o'と聞こえる.

【まとめ】 本レポートでは第一フォルマント周波数と第二フォルマント周波数を変えることで,母音'a'の音声を他の母音'i'と'o'の音声に変えられるかどうかを調査した.

結果として音声は変えることができ,スペクトルの操作だけでも音声処理ができた. これはフォルマント周波数が母音の特徴量となりうるからである.

子音の場合はフォルマント周波数に加えてアンチフォルマント周波数が特徴量となるため,手動でのスペクトル操作のみでは他の発音に変化させることは困難だと予想される.