LoginSignup
3

More than 5 years have passed since last update.

【動物会話】カラスの音声認識・自動分類を「エッジ音声認識アプリ」でやってみた♬

Last updated at Posted at 2019-03-13

もはや、カラス君にはお話ししてもらわないとということで、カラスのいろいろな音声を識別できるのかやってみました。犬・猫・ポメラニアン・カラスはある意味異質な気がするので、識別できるけど、さてカラスはどうなのよということでやってみたわけです。
しかし、人間の音声識別と同じで多様にお話ししているカラスは一応中身はわかりませんが、まずはわかりやすい「威嚇・怒っている」「カーカー」「その他の音声」の3種類に分けてやってみることにしました。
方法やアプリは、基本これまでのものと変わりません。

とりあえずの目標

1.犬・猫の音声認識
2.動物の音声認識
3.犬の種類・猫の種類の認識
4.犬・猫・カラスの声にそれぞれの声で反応を返す
5.犬・猫・カラスの話を翻訳して、人の声で返すと翻訳して返す
。。。
最終目標;「動物と日常会話ができるようになる」

やったこと

・カラスの声をスペクトログラムを見て3つのカテゴリに分割しました
・データ数は93個、93個、そして43個を使って学習
・最後に学習したモデルでカラスのYoutube動画を3つのカテゴリに自動分類

・カラスの声をスペクトログラムを見て3つのカテゴリに分割しました

あんまりカラスの気持ちが思いつかない(知らない)のと、「威嚇・怒っている」「カーカー」「その他の音声」というカテゴリなら、カラス語が分からないウワンでも違いがつかめます。
下に、代表的な例を載せておきます。
「威嚇・怒っている」
figure_ikaku.jpg
「カーカー」
figure_normal.jpg
「その他;けけけけ。。」
figure_others.jpg
それなりに違うのが分かると思います。
ただし、カーカーもどうも少しずつ違っていて、あるものは威嚇に近いのもあって、分類しずらいものも多いです。そして、何より困ったのがその他でその他は「威嚇」や「カーカー」以外を入れようと思っていましたが、いろいろ入ってきてしまって、統一感が持てなくなりそうです。ということでいっそ一部だけにしてみれば、むしろ「威嚇」や「カーカー」に分類してくれるかなという期待もあって、その他はほかのカテゴリの半分位の数にしました。

・データ数は93個、93個、そして43個を使って学習

学習は、今までと変わりません。
カテゴリ3個で以下のモデルで学習しました。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
...
_________________________________________________________________
flatten_1 (Flatten)          (None, 65536)             0
_________________________________________________________________
dense_1 (Dense)              (None, 90)                5898330
_________________________________________________________________
activation_1 (Activation)    (None, 90)                0
_________________________________________________________________
dropout_6 (Dropout)          (None, 90)                0
_________________________________________________________________
dense_2 (Dense)              (None, 3)                 273
_________________________________________________________________
activation_2 (Activation)    (None, 3)                 0
=================================================================
Total params: 7,883,531
Trainable params: 7,882,827
Non-trainable params: 704
_________________________________________________________________
Train on 170 samples, validate on 59 samples
Epoch 256/256
170/170 [==============================] - 1s 5ms/step - loss: 0.0067 - acc: 0.9941 - val_loss: 1.8556 - val_acc: 0.8305
Test loss: 1.85562402919
Test accuracy: 0.830508463464

学習結果は犬・猫・ポメラニアンのときと比べると、少し悪い感じもしますが、学習データが少ないのでこんなものかもしれません。何より、val_acc=0.83なので分類はできるレベルと言えるでしょう。

・学習したモデルでカラスのYoutube動画を3つのカテゴリに自動分類

学習データを精度よくよりよくしていくかはこういうカテゴリ分類で大量にデータを使いたい場合は永遠のテーマになっていると思います。
ここでは、最初に小さく生んで、大きく育てる戦略を取ろうと思っています。
つまり、上記の最小のモデルからだんだん拡大していくことを考えています。

そもそも、最初のモデルで犬・猫・ポメラニアン・そしてカラスの4分類を実施しています。
そのあと、カラスを3分類しましたが、上記の4分類の後でそれぞれのジャンルをカテゴライズすればネズミ算式に分類できることになります。
※確かに分類精度はそれぞれの精度に強く依存するので、多段式で詳細化した場合の精度の評価は難しいですが、これはそのうちまじめに議論したいと思います

ということでその前段として今回は、上記の最小モデルを利用してYoutubeの動画からカラスの音声を3つに自動分類しました。

コードは以下におきました

主なプログラムの考え方は以下のとおりです。
1.動画の音声を録音してスペクトログラムを作成し保存する(プログラム①)
2.上記の音声ファイル(wavファイル)とスペクトログラムをスペクトログラムを利用して分類し、それぞれのディレクトリにこの二つのファイルを移動する(プログラム②)
3.1と2は、今回は余裕を持って非同期(時間差をつける)で実施する。
・①hirakegoma/pyaudio_realtime.py
・②hirakegoma/move_file.py

コード解説;カラスの鳴き声Sampling

簡単に、変更部分を説明します。
wavファイル等を格納するディレクトリを定義します。
また、fileを書込み用にwf=wave.openするために必要なパラメータを追加しました。

pyaudio_realtime.py
path='./dog-cat/3karasu/wav/'
FORMAT = pyaudio.paInt16
CHANNELS = 1  #monoral
#サンプリングレート、マイク性能に依存

for文の初期値は今までにサンプリングした次の値から始めます。

pyaudio_realtime.py
for s in range(501,1000,1):
    print(s)
    start_measure()
    stream=p.open(format = pyaudio.paInt16,
              channels = 1,
              rate = RATE,
              frames_per_buffer = CHUNK,
              input = True)
...

次に読込んだwavファイルを書き出すためにframesに格納し、wfで書き出します。

pyaudio_realtime.py
    frames = []
    frames.append(input)

    wf = wave.open(path+str(s)+'.wav', 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))  #width=2 ; 16bit
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()
...

最後に二つのファイル(wav,jpg)を保存します。

pyaudio_realtime.py
    plt.savefig('out_test/figure.jpg') #out_test/figure.jpg  #'train_images/0/figure' +str(s)+'.jpg' #dog-cat/1/figure' +str(s)+'.jpg
    plt.savefig(path+'figure'+str(s)+'.jpg')

これでカラスの鳴き声のSamplingができました。

コード解説;分類してファイル移動

このコードはほぼ先ほどの記事のとおりですが、分類するコードを追加しています。
hirakegoma/move_file.py
まず利用するものは以下のとおりです。

move_file.py
#-*- cording: utf-8 -*-
import shutil
import os
import datetime

import numpy as np
import wave
import pyaudio
from vgg16_like import model_family_cnn
from keras.preprocessing import image
import matplotlib.pyplot as plt
import keras
import time
import cv2
from keras.preprocessing import image

少なくとも予測するための関数が必要です。

move_file.py
def prediction(imgSrc):
    #np.random.seed(1337) # for reproducibility

    img = np.array(imgSrc)
    img = img.reshape(1, img_rows,img_cols,3)
    img = img.astype('float32')
    img /= 255
    t0=time.time()
    y_pred = model.predict(img)
    return y_pred

また、分類して二つのファイルを格納するためのディレクトリを定義します。

move_file.py
os.makedirs('./dog-cat/3karasu/wav/angry', exist_ok=True)
os.makedirs('./dog-cat/3karasu/wav/normal', exist_ok=True)
os.makedirs('./dog-cat/3karasu/wav/others', exist_ok=True)

以下にモデルを定義します。

move_file.py
num_classes = 3
img_rows,img_cols=128, 128
input_shape = (img_rows,img_cols,3)   #224, 224, 3)
model = model_family_cnn(input_shape, num_classes = num_classes)
# load the weights from the last epoch
model.load_weights('params_karasu-0angry-1normal-2others.hdf5', by_name=True) 
print('Model loaded.')

ディレクトリのパスを定義します。
※pathは今回は使わないので削除していいのですが、リアルタイムで分類するときに使っていたものです
path2が今回の二つのファイルが格納されているファイルであり、分類のためのスペクトログラムを参照するディレクトリです。

move_file.py
path = "./out_test/figure.jpg"
path2 = "./dog-cat/3karasu/wav/figure"
img_rows,img_cols=128,128
s=501
for i in range(501,1000,1):
    imgSrc=[]
    for j in range(0,100000,1):
        j += 1        

以下でスペクトログラムををロードして表示し、分類します。

    imgSrc = image.load_img(path2+str(s)+'.jpg', target_size=(img_rows,img_cols))
    plt.imshow(imgSrc)
    plt.pause(1)
    plt.close()
    pred = prediction(imgSrc)
    print(pred[0])

分類結果に基づいて、ファイルに移動します。
まず、以下でディレクトリをfilenameに代入します。

    if pred[0][0]>=0.5:
        filename = path2+"karasu-hageshii_out.wav"
        print("angry") #cat
        path = 'angry'
    elif pred[0][1]>=0.5:
        filename = path2+"karasu_kero_out3.wav"
        print("normal") #dog
        path = 'normal'
    elif pred[0][2]>=0.5:
        filename = path2+"karasu_kero_out1.wav"
        print("others") #pomeranian
        path = 'others'

分類履歴をtextファイルに記録し、二つのファイルを移動します。

    dt_now = datetime.datetime.now()
    with open('./dog-cat/3karasu/wav/file.txt', 'a') as f: #w
        f.write(str(dt_now)+'_'+str(s)+': '+path+'  '+str(pred[0])+'\n')
    new_path = shutil.move('./dog-cat/3karasu/wav/' + str(s)+'.wav', './dog-cat/3karasu/wav/'+ path)
    new_path = shutil.move('./dog-cat/3karasu/wav/figure' + str(s)+ '.jpg', './dog-cat/3karasu/wav/' + path)
    s += 1

面倒な処理だと思っていましたが、割と簡単に書けました。

結果

分類は500個やりました。ログは以下のようなイメージです。

ログ.txt
...
2019-03-13 20:07:38.142292_499: angry  [  1.00000000e+00   7.49878851e-27   3.58520909e-16]
2019-03-13 20:07:39.420503_500: others  [  2.88504745e-12   1.87371547e-35   1.00000000e+00]
2019-03-13 20:20:46.658751_501: others  [  9.00081138e-16   3.52498690e-14   1.00000000e+00]
2019-03-13 20:20:47.978263_502: angry  [  1.00000000e+00   4.42884610e-17   8.04723310e-09]
2019-03-13 20:20:49.272004_503: others  [  2.03828583e-03   1.36525068e-05   9.97948110e-01]
2019-03-13 20:20:50.557566_504: normal  [  8.63878711e-08   9.99960899e-01   3.90048735e-05]
2019-03-13 20:20:51.873164_505: angry  [  9.92528915e-01   2.41256207e-13   7.47109530e-03]
2019-03-13 20:20:53.145366_506: angry  [ 1.  0.  0.]
2019-03-13 20:20:54.465678_507: others  [  3.51682985e-07   1.57976808e-06   9.99998093e-01]
2019-03-13 20:20:55.761824_508: others  [  5.01290155e-11   9.77509451e-09   1.00000000e+00]
2019-03-13 20:20:57.071924_509: others  [  7.13103930e-07   1.23973150e-05   9.99986887e-01]
2019-03-13 20:20:58.400975_510: others  [  1.93091029e-11   1.30198128e-08   1.00000000e+00]
2019-03-13 20:20:59.713416_511: others  [  1.03083476e-05   5.70981577e-03   9.94279861e-01]
2019-03-13 20:21:01.157920_512: normal  [  8.86018654e-08   6.22941375e-01   3.77058625e-01]
2019-03-13 20:21:02.473179_513: angry  [  9.99552071e-01   6.39560294e-10   4.47940547e-04]
2019-03-13 20:21:03.731949_514: others  [  1.36581846e-07   6.12431859e-06   9.99993682e-01]
2019-03-13 20:21:04.962327_515: others  [  3.64129301e-06   1.39566464e-03   9.98600662e-01]
2019-03-13 20:21:06.198915_516: others  [  1.51473202e-07   6.39834941e-09   9.99999881e-01]
2019-03-13 20:21:07.449303_517: others  [  4.81473592e-07   1.01736687e-05   9.99989390e-01]
2019-03-13 20:21:08.671922_518: others  [  1.58946100e-03   1.13485248e-08   9.98410463e-01]
2019-03-13 20:21:09.918057_519: angry  [  9.81940329e-01   4.43458923e-08   1.80597156e-02]
2019-03-13 20:21:11.240528_520: others  [ 0.0671321   0.28824389  0.64462399]
2019-03-13 20:21:12.480411_521: angry  [  8.77450943e-01   1.57700768e-08   1.22549050e-01]
...

分類はあっという間に完了するので、上記のようにfor j in range(0,100000,1): j += 1plt.pause(1)でタイミングを計っています。
ということで、リーズナブルな分類ができました。
ただし、Othersに分類されたものに、以下のようにほぼNormalでいいかもと思われるものも含まれていました。
figure_others2.jpg
上記は、ほぼ「カーカー」と聞こえます。
ちなみに、以下の似たものは、ノーマルに分類されました。
figure_normal2.jpg
二つを比較すると、ほんの少しOthersに分類されたものの方が、ちょっとゴミゴミしていてかつ中間や低周波にうっすらスペクトログラムが出ています。
ということで、この手の二重線に見える「カーカー」をノーマルに追加して再学習したいと思います。

まとめ

・カラスの音声認識による自動分類をやってみた
・ほぼリアルタイムで自動分類が可能になった

・モデルを拡大して、多段方式でのカラスや犬・猫を分類したいと思う

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3