今回やりたかったことは二人の人物が対談しているときにどちらの人物がしべっているかをプログラムに判定させることです。
以下の方針で実現します
まずは、音声データの特徴量をどうするかです。
調べたらメル周波数ケプストラム係数というのが良いらしいのでこれを使うことにしました。
次に分類器。
SVM(サポートベクターマシン)というモデルが良いらしいのでこれを使います。
色々と、難しい単語が出てきましたが、そんなに理解しなくてもライブラリをインストールすれば小難しいことはやってくれるので大丈夫です。
以下をconda install で導入する
機械学習用ライブラリ。モデルを作成、使用するのに使う
conda install scikit-learn
オーディオ分析用ライブラリ。メル周波数ケプストラム係数を求めるのに使う
描画用ライブラリ。しゃべってる人を判定してその人に対応するアバターの口が動くと面白いかなと思い描画用に使う。
EJPゲームズのYoutube動画用の素材データ(十数分くらい、未編集(BGM等なし))を使って試します。
ドライブに置いてあったのが映像データだったのでffmpegで音声成分を分離。soxで無音区間で分割しました。これで300と少しくらいの音声データに分割されました。これを一つずつ実際に聞いて人物毎または使えないデータに振り分けます。bashスクリプト組んでセミオートでやりましたがめんどくさかったです。振り分けた二人の音声データをそれぞれsoxで結合して学習用の音声データとします(data1.wav, data2.wav)。二人の声が重なっている部分も多くなんだかんだでそれぞれ2~3分くらいの長さです。もっとデータが多い方がいいような気もしますが、二人を判定するだけなので大丈夫だとうという希望的推測で進みます。
以下分類器作成用のコードになります。
from sklearn import svm
from sklearn.metrics import accuracy_score
import pickle
import librosa
import numpy as np
## データロード
def data_load(data_path):
print("データ読み込み")
data,samplerate=[],[]
for i in range(len(data_path)):
d, sr = librosa.load(data_path[i], sr = 44100)
data.append(d)
samplerate.append(sr)
print(sr)
print("完了")
return data, samplerate
## mfcc計算
def get_mfccs(data_array, sr):
mfccs = []
print("mfcc 変換")
for i in range(len(data_array)):
mfcc = librosa.feature.mfcc(y=data_array[i], sr=sr[i])
mfccs.append(mfcc)
print("完了")
return mfccs
## mfccデータを整える
def format_train_data(mfccs_data):
data = []
train_data, train_label = [], []
print("学習用データ 整形")
for i in range(len(mfccs_data)):
for j in range(len(mfccs_data[i][0])):
data.append([mfccs_data[i][1:,j],i])
np.random.shuffle(data)
for i in range(len(data)):
train_data.append(data[i][0])
train_label.append(data[i][1])
print("完了")
return train_data, train_label
## 一部データをテスト用に退避させる
def get_test_data(data, label, num):
print("テスト用データ 取得")
test_data = data[:num]
test_label = label[:num]
train_data = data[num:]
train_label = label[num:]
print("完了")
return test_data, test_label, train_data, train_label
## 分類器を作成
def creat_svm_clf(clf_name,train_label,train_data):
print("SVM 分類器 作成")
clf = svm.SVC(C=1,gamma=0.0001)
clf.fit(train_data, train_label)
print("完了")
## ファイルとして保存
with open(clf_name, mode='wb') as f:
pickle.dump(clf,f,protocol=2)
print("分類器を保存しました:",clf_name)
return clf
if __name__ == '__main__':
DATA_PATH1 = 'data1.wav'
DATA_PATH2 = 'data2.wav'
CLF_FILE_NAME = 'svm_clf.pickle'
TEST_NUM = 100
data, sr = data_load([DATA_PATH1, DATA_PATH2])
mfccs = get_mfccs(data, sr)
train_data, train_label = format_train_data(mfccs)
test_data, test_label, train_data, train_label = get_test_data(
train_data, train_label, TEST_NUM)
clf = creat_svm_clf(CLF_FILE_NAME,train_label,train_data)
## テスト
predict_label = clf.predict(test_data)
print("テストデータ数:%d"%len(test_label))
print("正解率 = %f"%accuracy_score(test_label, predict_label))
実行結果
データ読み込み
完了
mfcc 変換
完了
学習用データ 整形
完了
テスト用データ 取得
完了
SVM 分類器 作成
完了
分類器を保存しました: svm_clf.pickle
テストデータ数:100
正解率 = 0.980000
ランダムに100個を学習用データから退避させてテスト用のデータに使っていますので、実行ごとに正解率は変わりますが大体95%以上は出ています。(ロジックを間違えていない前提ですが)
これでsvm_clf.pickleというモデルを保存したファイルができますのでこれを使って発言者のリアルタイム判定を行います。
作成したコードが以下です。
import pickle
import librosa
import pyaudio
import numpy as np
import cv2
BUFSIZE = 8192
SMPL_RATE = 44100 ## サンプリングレート
## pcの標準音声入力から音声取得
audio = pyaudio.PyAudio()
stream = audio.open( rate=SMPL_RATE,
channels=1,
format=pyaudio.paFloat32,
input=True,
frames_per_buffer=BUFSIZE)
## 描画画像準備
img1 = cv2.resize(cv2.imread('img001.png'), (800,450))
img2 = cv2.resize(cv2.imread('img002.png'), (800,450))
img3 = cv2.resize(cv2.imread('img003.png'), (800,450))
status = 0
## 分類器を取り込む
with open('svm_clf.pickle', mode='rb') as f:
clf = pickle.load(f)
while True:
try:
## 推測結果に応じて描画を変更
if status == 0:
show_image = img1
elif status == 1:
show_image = img2
else:
show_image = img3
cv2.imshow('sample',show_image)
cv2.waitKey(1)
## 音声取得
audio_data=stream.read(BUFSIZE)
data=np.frombuffer(audio_data,dtype='float32')
if max(data) < 0.05: ## 無音判定
status = 0
continue
## mfccを算出
mfcc = librosa.feature.mfcc(y=data, sr=SMPL_RATE)
mfcc_mean = np.array([])
for i in range(1,len(mfcc)):
mfcc_mean = np.append(mfcc_mean,np.mean(mfcc[i]))
## 分類器に入れる
predicted_value= clf.predict([mfcc_mean])
## 推測結果
print(predicted_value[0])
## アバターのくちに反映
if predicted_value[0] == 0:
status = 1 if np.random.randint(0,3) != 1 else 0
else:
status = 2 if np.random.randint(0,3) != 1 else 0
except KeyboardInterrupt: ## ctrl + c
break
## 後始末
cv2.destroyAllWindows()
stream.stop_stream()
stream.close()
audio.terminate()
上記コードではPCの標準音声入力からの一定サイズごとの音声データをmfccに変換して分類器clfに入れて推測結果を得ます。そして推測結果から二人に対応する2種類のアバターのクチを開けたり閉じたりさせます(img001.png:二人とも口を閉じている、img002.pngとimg003.pngはそれぞれ口を開いている)。
今回はYoutubeから最新のEJPラジオ(編集済み(BGMあり))をPCで実際に物理的な音として流しながらそれをPCのマイクで拾っています。BGMがのっていたり、外部雑音が気になりますがまあたぶんに大丈夫だと真して実行です。(ctrl+cで終了)
実行結果
まあ、データが少なくかつテキトーな実装の割にはそこそこの精度はあるように思えます。遊びに使うなら十分です。