Pythonで音声処理(5)オーバーラップ+窓関数

IT

前回の記事では、リアル音声の周波数をグラフ化し、リアル音声処理を実施する上での課題について説明しました。

今回は、リアル音声を周波数解析する上で必要となる、オーバーラップ処理と窓関数の実装について、解説していきたいと思います。


今回作成するプログラムの流れは、次のようにします。

1.音声を読み込む。

2.オーバーラップ+窓関数処理を行い、一定時間ごとの音声データ群を作成する。

3.各音声データに対してFFT処理をかけて、周波数データ群を作成する。

4.周波数データ群に逆FFT処理をかけて、音声データ群に戻す。

5.音声データ群を元の音声に戻す。


一定時間ごとの周波数データに変換して、何もせずに元の音声データに戻すプログラムです。

このプログラムができてしまえば、あとは自由に周波数解析が可能になります。

では、早速プログラム全体を見ていきましょう。

# -*- coding: utf-8 -*-
import wave
import numpy as np
import matplotlib.pyplot as plt
import struct
# Wave読み込み
def read_wav(file_path):
wf = wave.open(file_path, "rb")
buf = wf.readframes(-1) # 全部読み込む
# 2なら16bit,4なら32bitごとに10進数化
if wf.getsampwidth() == 2:
data = np.frombuffer(buf, dtype='int16')
elif wf.getsampwidth() == 4:
data = np.frombuffer(buf, dtype='int32')
# ステレオの場合,チャンネルを分離
if wf.getnchannels()==2:
data_l = data[::2]
data_r = data[1::2]
else:
data_l = data
data_r = data
wf.close()
return data_l,data_r
# wavファイルの情報を取得
def getWavInfo(file_path):
ret = {}
wf = wave.open(file_path, "rb")
ret["ch"] = wf.getnchannels()
ret["byte"] = wf.getsampwidth()
ret["fs"] = wf.getframerate()
ret["N"] = wf.getnframes()
ret["sec"] = ret["N"] / ret["fs"]
wf.close()
return ret
# wavファイル書き込み
def write_wav(file_path, data_l, data_r, wav_info):
if wav_info["ch"] == 2:
data = np.array([[int(data_l[i]), int(data_r[i])] for i in range(int(wav_info["N"]))]).flatten()
else:
data = np.array([int(x) for x in data_l])
# 音量調整
amp = 32767 / max(data)
data = np.array([int(x * amp) for x in data])
# 最大値と最小値に収める
data = np.clip(data, -32768, 32767)
# バイナリ化
binwave = struct.pack("h" * len(data), *data)
# wavファイルとして書き出し
w = wave.Wave_write(file_path)
p = (wav_info["ch"], wav_info["byte"], wav_info["fs"], wav_info["N"], 'NONE', 'not compressed')
w.setparams(p)
w.writeframes(binwave)
w.close()
# オーバーラップ+窓関数
def half_overlap_win(data_l, data_r, wi, win_size):
ret_ls = []
ret_rs = []
win = np.hanning(win_size)
for i in range(0, wi["N"] ,int(win_size/2)):
endi = i + win_size
if endi < wi["N"]:
ret_ls.append(data_l[i:endi] * win)
ret_rs.append(data_r[i:endi] * win)
else:
win = np.hanning(len(data_l[i:-1]))
ret_ls.append(data_l[i:-1] * win)
ret_rs.append(data_r[i:-1] * win)
return ret_ls,ret_rs
# オーバラップ+窓関数の音声を元に戻す
def return_how(data_ls, data_rs, wi, win_size):
ret_l = np.zeros(wi["N"])
ret_r = np.zeros(wi["N"])
for i in range(0, len(data_ls)):
to = win_size
to = to if to <= len(data_ls[i].real) else len(data_ls[i].real)
for j in range(0, to):
ret_l[i*int(win_size/2)+j] += data_ls[i].real[j]
ret_r[i*int(win_size/2)+j] += data_rs[i].real[j]
return ret_l,ret_r
if __name__ == '__main__':
# 音声データパス
onsei_path = "arigato.wav"
# 出力音声パス
out_onsei_path = "i_arigato.wav"
# ウィンドウサイズ
win_size = 1024 * 2
# 音声読み込み
data_l,data_r = read_wav(onsei_path)
# Wavの情報取得
wi = getWavInfo(onsei_path)
# オーバーラップ+窓関数
data_ls,data_rs = half_overlap_win(data_l, data_r, wi, win_size)
# FFT
F_ls = []
F_rs = []
for dl in data_ls:
F_ls.append(np.fft.fft(dl))
for dr in data_rs:
F_rs.append(np.fft.fft(dr))
# 逆FFT
IF_ls = []
IF_rs = []
for fl in F_ls:
IF_ls.append(np.fft.ifft(fl))
for fr in F_rs:
IF_rs.append(np.fft.ifft(fr))
# オーバーラップ+窓関数を元に戻す
ret_l,ret_r = return_how(IF_ls, IF_rs, wi, win_size)
# wav書き出し
write_wav(out_onsei_path, ret_l, ret_r, wi)
view raw audiotest4.py hosted with ❤ by GitHub

少し長いですが、やっていることは上記の通りです。

今回追加したオーバーラップ+窓関数処理を見ていきましょう。

# オーバーラップ+窓関数
def half_overlap_win(data_l, data_r, wi, win_size):
ret_ls = []
ret_rs = []
win = np.hanning(win_size)
for i in range(0, wi["N"] ,int(win_size/2)):
endi = i + win_size
if endi < wi["N"]:
ret_ls.append(data_l[i:endi] * win)
ret_rs.append(data_r[i:endi] * win)
else:
win = np.hanning(len(data_l[i:-1]))
ret_ls.append(data_l[i:-1] * win)
ret_rs.append(data_r[i:-1] * win)
return ret_ls,ret_rs
view raw audiotest4.py hosted with ❤ by GitHub

61行目で、オーバーラップ+窓関数処理の関数「half_overlap_win」の定義を開始しています。一定時間毎の音声データを作成しますが、半分をオーバーラップさせるため「half」という名前をつけています。引数として、音声データのL、RそれぞれのデータとWavファイルの情報、切り取る大きさ(ウインドウサイズ)をインプットします。

62、63行目では、オーバーラップ+窓関数処理をかけたあとの音声データをリストとして返すための戻り値配列を作成しています。

64行目では、ウインドウサイズに合った窓関数を定義しています。窓関数は最も一般的なハニング窓を利用します。

65行目〜73行目のループで、音声信号を切り取り配列に格納していきます。

65行目は、forループのループ方法を定義しています。iを0からウインドウサイズの半分ずつ加えていき、音声の最後になるまでループします。

66行目で、endi=i+ウインドウサイズを計算しています。

67行目のif文は、endiが音声データの最後を超えてしまう場合があるので、超えていない場合と超える場合で処理を分けています。68行目、69行目は超えない場合の処理です。

68、69行目で、i〜endiで音声データを切り取り、ハニング窓を乗算した上で、戻り値配列に追加しています。

70行目〜73行目は、endiが音声データの最後を超えてしまう場合の処理です。この場合、ハニング窓の大きさも変わってくるので、71行目で再定義します。

72、73行目で、i〜音声データの最後までを切り取り、ハニング窓を乗算した上で、戻り値配列に追加しています。

74行目で、LとRそれぞれのデータを戻り値として返しています。


続いて、切り取った音声データを元の音声データに戻す関数定義について見ていきましょう。

# オーバラップ+窓関数の音声を元に戻す
def return_how(data_ls, data_rs, wi, win_size):
ret_l = np.zeros(wi["N"])
ret_r = np.zeros(wi["N"])
for i in range(0, len(data_ls)):
to = win_size
to = to if to <= len(data_ls[i].real) else len(data_ls[i].real)
for j in range(0, to):
ret_l[i*int(win_size/2)+j] += data_ls[i].real[j]
ret_r[i*int(win_size/2)+j] += data_rs[i].real[j]
return ret_l,ret_r
view raw audiotest4.py hosted with ❤ by GitHub

77行目で、切り取った音声データを元の音声データに戻す関数「return_how」の定義を開始しています。「how」は「half_overlap_win」の略です。引数として、LとRそれぞれの配列とWavファイルの情報、ウインドウサイズをインプットします。

78、79行目で、LとRそれぞれの元音声データを入れるための変数を定義しています。オーバーラップしたデータを加えるため、データは予めゼロ埋めしておきます。

80行目でforループを定義しています。iを0から配列の最大サイズまでループします。ループ内ではLとRの処理を行いますが、LとRの配列サイズは同じため、Lの配列で最大サイズを取得しています。

81行目で、toにウィンドウサイズを格納していますが、配列の最後には、ウィンドウサイズ未満のデータがあるので、82行目の三項演算子により、ウィンドウサイズ未満の場合は、toに配列サイズを入れるようにします。

83行目〜85行目は、切り取った音声データの中で更にjが0からtoになるまでループします。

84、85行目で、LとRそれぞれのデータに対して、i×ウィンドウサイズの半分+jの位置に、音声データを足し込んでいきます。

86行目で、LとRそれぞれで復元した音声データを返して処理完了です。


それでは、プログラムを実行して見ましょう。

入力する音声は、前回の記事と同様「arigato.wav」です。

この音声データを本プログラムで処理して得られた「i_arigato.wav」が以下になります。

色々と処理を施したにも関わらず、ほとんど何も変わっていないことがわかると思います。

これで、周波数変更のための下準備が整いました。

次回は本プログラムに、周波数変更処理を追加して、音声処理を行いたいと思います。

コメント

タイトルとURLをコピーしました