概要:音声から健康情報の抽出
音声認識ライブラリで音から文字に変換した場合、音声に含まれている情報の内、文章情報だけしか利用できない、音声に含まれているが今までノイズ(ゆらぎ)として捨てられていた情報の活用方法を探りたい。
今回は健康情報の抽出をテーマとする。実際に風を引いた時の音声を使うことがベストだが、すぐに入手できないため、演技で元気の良い声と疲れている声を出し、それを録音した。録音した音声に対して特徴量の抽出と判別を行った。
以下の方法で元気そうな声と疲れている声の判別に成功した。
%matplotlib inline
import glob
import numpy as np
from scipy.io import wavfile
import matplotlib.pyplot as plt
from sklearn.svm import SVC
Webカメラ付属のマイクで自分の声を録音し、wav形式で保存した。1ファイル1単語であり。単語はすべて『おはよう』で統一した。strongとファイル名に付いているファイルは元気よく発話した音声であり、weakと付いているものは元気がなさそうに発話した音声である。
filelist = glob.glob('./ohayou_*.wav')
filelist
['.\\ohayou_strong1.wav', '.\\ohayou_strong2.wav', '.\\ohayou_strong3.wav', '.\\ohayou_strong4.wav', '.\\ohayou_strong5.wav', '.\\ohayou_strong6.wav', '.\\ohayou_weak1.wav', '.\\ohayou_weak2.wav', '.\\ohayou_weak3.wav', '.\\ohayou_weak4.wav', '.\\ohayou_weak5.wav', '.\\ohayou_weak6.wav']
音声ファイルを読み込み。連想配列の配列に保存。ステレオ録音のため一列目(おそらく右)の波形だけ使用。
snd_array = []
for fname in filelist:
fs, snd = wavfile.read(fname)
snd_array.append({'data':snd[:, 1], 'sample_rate':fs, 'fname':fname})
一つ目のファイルをプロット。
plot_data = snd_array[0]
plt.plot(np.arange(0.0, np.size( plot_data['data']), 1)/plot_data['sample_rate'], plot_data['data'])
plt.xlim([0,1])
plt.ylim([-30000,30000])
plt.title(plot_data['fname'])
plt.xlabel('[second]')
<matplotlib.text.Text at 0x98ec330>
元気がなさそうに発音した時の音声をプロット。後ろの小さな山はため息(元気がなさそうに話すと無意識の内に出てしまった)。
plot_data = snd_array[7]
plt.plot(np.arange(0.0, np.size( plot_data['data']), 1)/plot_data['sample_rate'], plot_data['data'])
plt.xlim([0,1])
plt.ylim([-30000,30000])
plt.title(plot_data['fname'])
plt.xlabel('[ms]')
<matplotlib.text.Text at 0x9912ad0>
今後何度も使うので。時間のインデックスを返す関数を定義しておく。
def time_index(data):
import numpy as np
return np.arange(0.0, np.size(data['data']), 1)/data['sample_rate']
time_index(snd_array[0])
array([ 0.00000000e+00, 2.26757370e-05, 4.53514739e-05, ..., 5.49863946e-01, 5.49886621e-01, 5.49909297e-01])
すべての音声をプロットする。赤色は元気な声、青色は元気のない声(以降、特に断りのない限り同じ色分けを使う)。
for snd_data in snd_array:
if 'weak' in snd_data['fname']:
col = 'b'
elif 'strong' in snd_data['fname']:
col = 'r'
plt.figure()
plt.plot(time_index(snd_data), snd_data['data'], color=col)
plt.xlim([0,1])
plt.ylim([-2.**15, 2.**15])
plt.title(plot_data['fname'])
plt.xlabel('[second]')
振幅情報を抽出し、プロットする。振幅は元の音声データの絶対値を取り、500点平均したものとする。先頭に0を詰め数を合わせる。
for snd_data in snd_array:
n_window = 500
org_data = np.r_[np.zeros(n_window), snd_data['data']]
scan_range = range(0, snd_data['data'].shape[0])
amplitude_mean = np.zeros(len(scan_range))
for i_time in scan_range:
extract_data = org_data[i_time:i_time + n_window]
amplitude_mean[i_time] = np.mean(np.abs(extract_data))
snd_data['amp'] = amplitude_mean
if 'weak' in snd_data['fname']:
col = 'b'
elif 'strong' in snd_data['fname']:
col = 'r'
plt.figure()
plt.plot(time_index(snd_data), snd_data['amp'], color=col)
振幅が0.01以上かつ、0.1秒以上連続している区間を発話区間とする。
def extract_speak(snd, th_amp=1000, th_time=0.1):
import numpy as np
index_snd_buff = th_amp < snd['amp']
index_snd = np.bool_(np.zeros(index_snd_buff.shape[0]))
count = 0
for i, v in enumerate(index_snd_buff):
if v == False:
if th_time * snd['sample_rate'] < count:
index_snd[i-count:i] = True
count = 0
else:
count += 1
return index_snd
発話区間だけ抽出してプロット
for snd_data in snd_array:
plot_data = snd_data['amp'][extract_speak(snd_data)]
if 'weak' in snd_data['fname']:
col = 'b'
elif 'strong' in snd_data['fname']:
col = 'r'
plt.figure()
plt.plot(time_index(snd_data)[:plot_data.shape[0]], plot_data, color=col)