音声有効区間とモーラの検出



# 音声有効区間とモーラの検出
MFCCを使って音声有効区間とモーラを検出します。ノイズ対策はありません、そのため録音時点でノイズが少ない音声を対象とした内容です。元論文等は特に無く独自のロジックです、音声界隈？で常套手段等あれば知りたいです。
<br>


py-webrtcvad というpython用ライブラリでも音声有効区間を検出することは可能ですが「サ行」の取りこぼし、喋り始めの取りこぼしが目立ちました。

> https://github.com/wiseman/py-webrtcvad


py-webrtcvadについてですが前後に＋５０ｍｓくらい区間を広げる等工夫すれば十分実用可能と思います、実際 py-webrtcvad で特徴抽出をしてそこまで大きな問題はありませんでしたが今回はできる限り正確に喋り始めや発音の区切りを検出したかったため自作することにしました
<br>
本実験のソースは最後に載せます

# サンプルによる実験結果
下記サイトでサンプル音声を作ります

> https://note.cman.jp/other/voice/

文書は下記としました

> 吾輩は猫であるまだ名前はない。夏目漱石
> わがはいはねこであるなまえはまだない　なつめそうせき

![fig.png](https://qiita-image-store.s3.amazonaws.com/0/104691/7cda84bd-62a9-543d-49c6-1ac149c3abc4.png)


グラフは上から

- 「MFCCヒートマップ」
- 「MFCCパワー、MFCC⊿パワー」
- 「有効音声区間指標値」
- 「MFCC⊿のピーク」（モーラ指標）
- 「有効音声区間とモーラ」

グラフを見ると文字数（発音数？）とモーラがだいたい合っており検出できている事が確認できます。機械合成音で検出が容易ではありましたが人の録音音声に対しても同様に検出できることは確認しています。


# 音声有効区間の考え方
有効音声区間はMFCCパワーがありMFCC⊿に変動のある区間を有効音声区間とします、「音があって緩急があるのならそこは喋っている区間であろう」という考え方です。

# 有効なモーラの考え方
MFCC⊿変動の上のピークは全てモーラと考えるが有効音声区間内に限ります、当たり前ですがその他の区間のピークはモーラとは考えません。

# 音声有効区間の指標値の作り方
MFCCパワーの０を下回る数字を全て０へ丸めMFCC⊿を絶対値に変換した値を乗算、そこに分散１０のガウシアンフィルタをかける。これによりパワーがありかつ変動のあるフレームが強調されたデータになります、データ中 **ある閾値を上回っている箇所** 全てを音声有効区間とします。
<br>
「ある閾値」とは２～１０程度の整数になります、サンプル郡の平均的な静音区間を見て任意に決めます。元音声の周波数やサー音等の混ざり具合により変わります。


# モーラの作り方
MFCCパワーとMFCC⊿パワーを掛け算して分散４のガウシアンフィルタをかけ上のピークをモーラとします


# ソース

```python

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plot

import numpy as np
import python_speech_features as psf
import scipy
import scipy.io
import scipy.io.wavfile
import scipy.ndimage
import scipy.signal

def getMfcc(fileName):
    (rate, sig) = scipy.io.wavfile.read(fileName)
    mfcc = psf.mfcc(sig, rate)
    delta = psf.delta(mfcc, 2)
    deltaDelta = psf.delta(delta, 2)
    mfccFeature = np.c_[mfcc, delta, deltaDelta]
    return mfccFeature

def getVadFluctuation(mfccPower, deltaPower, filterWidth=10):
    mfccPower[mfccPower < 0] = 0
    deltaPower = np.abs(deltaPower)
    y = mfccPower * deltaPower
    y = scipy.ndimage.gaussian_filter(y, filterWidth)
    minId = scipy.signal.argrelmin(y, order=1)
    minPeek = np.zeros(len(y))
    minPeek[minId] = 1
    maxId = scipy.signal.argrelmax(y, order=1)
    maxPeek = np.zeros(len(y))
    maxPeek[maxId] = 1
    return y, minPeek, maxPeek

def getMoraFlactuation(mfccPower, deltaPower, filterWidth=4):
    y = mfccPower * deltaPower
    y = scipy.ndimage.gaussian_filter(deltaPower, filterWidth)
    minId = scipy.signal.argrelmin(y, order=1)
    minPeek = np.zeros(len(y))
    minPeek[minId] = 1
    maxId = scipy.signal.argrelmax(y, order=1)
    maxPeek = np.zeros(len(y))
    maxPeek[maxId] = 1
    return y, minPeek, maxPeek

def run():
    # defines
    vadThreshold = 3
    
    # MFCC取得
    mfcc = getMfcc("./sample1.wav")
    dataLength = len(mfcc)
    mfccPower = mfcc[:,0]
    deltaPower = mfcc[:,13]
    
    # Voice active detection
    vad, vadPeekMin, vadPeekMax = getVadFluctuation(mfccPower, deltaPower)
    
    # mora
    mora, moraPeekMin, moraPeekMax = getMoraFlactuation(mfccPower, deltaPower)
    
    # voice active detection
    vadSection = np.zeros(dataLength)
    vadSection[vad >= vadThreshold] = 1
    moraPositions = np.zeros(dataLength)
    moraPositions[np.where(moraPeekMax == 1)] = 1
    moraPositions[vad <= vadThreshold] = 0
    
    # plot data
    mfccHeatmap = mfcc[:,np.arange(1, 13)].T
    
    # max len
    xlim = [0, dataLength]
    
    plot.style.use('classic')
    plot.figure(figsize=(12,10))
    # heatmap
    plot.subplot(5, 1, 1)
    plot.xlim(xlim)
    #plot.heatmap(heatmap)
    plot.pcolor(mfccHeatmap, cmap=plot.cm.Blues)
    
    # power, delta
    plot.subplot(5, 1, 2)
    plot.xlim(xlim)
    plot.plot(mfccPower)
    plot.plot(deltaPower)
    
    # vad
    plot.subplot(5, 1, 3)
    plot.xlim(xlim)
    plot.plot(vad)
    sx = np.where(vadPeekMin == 1)[0]
    sy = vad[sx]
    plot.scatter(sx, sy, c="blue")
    sx = np.where(vadPeekMax == 1)[0]
    sy = vad[sx]
    plot.scatter(sx, sy, c="red")
    yline = [vadThreshold] * dataLength
    plot.plot(yline)
    
    # mora
    plot.subplot(5, 1, 4)
    plot.xlim(xlim)
    plot.plot(mora)
    sx = np.where(moraPeekMin == 1)[0]
    sy = mora[sx]
    plot.scatter(sx, sy, c="blue")
    sx = np.where(moraPeekMax == 1)[0]
    sy = mora[sx]
    plot.scatter(sx, sy, c="red")
    
    # vad
    plot.subplot(5, 1, 5)
    plot.xlim(xlim)
    plot.plot(vadSection)
    sx = np.where(moraPositions == 1)[0]
    sy = np.ones(len(sx))
    plot.scatter(sx, sy)
    #vadSection
    #moraPositions
    
    plot.savefig("./fig.png")
    
    
    vadThreashold = 2
    

if __name__ == "__main__":
    run()

"""
python main.py
"""

```