LoginSignup
3
2

More than 5 years have passed since last update.

【Scipy】FFT、STFTとwavelet変換で遊んでみた♬~音声分離アプリ

Last updated at Posted at 2019-02-05

いよいよ、STFTを利用したアプリを作ってみよう。ということで、まずは音声の分離アプリを作ってみました。

とりあえずの目標

以下のとおり、
※リンクされているものは記事にしたものであり、その記事を前提知識として書いて行くので、参照すると理解し易いと思います。
Scipy環境を作る
 ・環境の確認
不確定原理について
・FFT変換・逆変換してみる;時間軸が消える
・STFT変換・逆変換してみる;窓関数について
・wavelet変換・逆変換してみる;スペクトログラムの解釈と不確定原理
音声や地震データや株価や、。。。とにかく一次元の実時系列データに応用する
音声データ入力編
FFTからwavelet変換まで簡単にたどってみる(上記以外のちょっと理論)
⑤二次元データに応用してみる
⑥天体観測データに応用してみる
リアルタイムにスペクトログラムしてみる
高速化(バグあり)
高速化完成版

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

Scipy-Swan/out_wav.py

やったこと

・録音する
・音声の分離

録音する

③音声データ入力編で利用したコードを使って、音声を入力します。
今までの流れで「おはよう」5秒と「開けゴマ」10秒録音しました。
今回は、リアルタイムにはこだわらないこととします。

ということで、録音された音声は以下のとおりです。
「おはよう」
ohayo.jpg
「開けゴマ」
hirakegoma.jpg

・音声の分離

上記の音声は何度か「おはよう」と「開けゴマ」を繰り返しているので、これを分離します。
コードは以下のとおりです。
流れとしては、
①録音の全体を読み
②生データ、STFT、そしてFFTを表示する
③切り出したい時間軸を選ぶ
を繰り返す。
使うのは以下のとおり、見慣れたものです。

#coding: utf-8
import wave
import pyaudio
import matplotlib.pyplot as plt
import numpy as np
import time
import wave
from scipy.fftpack import fft, ifft
from scipy import signal

録音したwavファイルを読み込み、諸量を出力します。

def printWaveInfo(wf):
    """WAVEファイルの情報を取得"""
    print( "chn:", wf.getnchannels())
    print( "width:", wf.getsampwidth())
    print( "sampl rate:", wf.getframerate())
    print( "no. of frame:", wf.getnframes())
    print( "parames:", wf.getparams())
    print( "length(s):", float(wf.getnframes()) / wf.getframerate())

以下は、時間軸で切り出したファイルの生データ、STFTそしてFFTを表示します。
※リアルタイム・スペクトログラムのコードの表示とほぼ同じです。
最後にグラフを保存しています。

def plot_wav(filename,t1,t2):
    ws = wave.open(filename+'.wav', "rb")
    ch = ws.getnchannels()
    width = ws.getsampwidth()
    fr = ws.getframerate()
    fn = ws.getnframes()
    fs = fn / fr
    print(fn,fs)
    origin = ws.readframes(fn)
    data = origin[:]
    sig = np.frombuffer(data, dtype="int16")  /32768.0
    t = np.linspace(0,fs, fn, endpoint=False)
    fig = plt.figure(figsize=(12, 10))
    ax1 = fig.add_subplot(311)
    ax2 = fig.add_subplot(312)
    ax3 = fig.add_subplot(313)

    ax1.set_xlabel('Time [sec]')
    ax1.set_ylabel('Signal')
    ax1.set_ylim(-0.0075,0.0075)
    ax1.set_xlim(t1,t2)
    ax1.plot(t, sig)

    nperseg = 1024
    f, t, Zxx = signal.stft(sig, fs=fn, nperseg=nperseg)
    ax2.pcolormesh(fs*t, f/fs/2, np.abs(Zxx), cmap='hsv')
    ax2.set_xlim(t1,t2)
    ax2.set_ylim(20,20000)
    ax2.set_yscale('log')

    freq =fft(sig,int(fn))
    Pyy = np.sqrt(freq*freq.conj())*2/fn
    f = np.arange(20,20000,(20000-20)/int(fn)) #RATE11025,22050;N50,100
    ax3.set_ylim(0,0.000075)
    ax3.set_xlim(20,20000)
    ax3.set_xlabel('Freq[Hz]')
    ax3.set_ylabel('Power')
    ax3.set_xscale('log')
    ax3.plot(f*fr/44100,Pyy)

    plt.savefig(filename+'.jpg')
    plt.show()

    plt.close()  
    ws.close()

今回は、録音したwavファイルを読み込むこととしました。

if __name__ == '__main__':
    filename=input('input original filename=')
    wf = wave.open(filename+".wav", "r")

    printWaveInfo(wf)
    fr = wf.getframerate()
    fn = wf.getnframes()
    fs = float(fn / fr)

関数を呼び出して、画像表示します。
※最初は全体を表示したいので、plot_wav(filename,0,fs)としています。

    plot_wav(filename,0,fs)

    # ストリームを開く
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                    channels=wf.getnchannels(),
                    rate=wf.getframerate(),
                    output=True)

    # チャンク単位でストリームに出力し音声を再生
    CHANNELS = wf.getnchannels()
    width = wf.getsampwidth()
    RATE = wf.getframerate()
    #fn = wf.readframes(wf.getnframes())
    fn = wf.getnframes()
    fs = float(wf.getnframes()) / wf.getframerate()

ここで、stream.write(data)で読み込んだ録音を出力して確認します。
同時に、framesにデータを格納します。

    frames = []
    for i in range(0, int(RATE / 1024 *fs+0.5)):
        data = wf.readframes(1024)
        frames.append(data)
        stream.write(data)
    print(int(RATE / 1024 * fs+0.5))

表示された音声の切り出す範囲をt1,t2で指定します。
plot_wav(filename,t1,t2)で生データ等を出力します。

    t1=float(input('input t1='))
    t2=float(input('input t2='))
    plot_wav(filename,t1,t2)

    loff = wf.getnframes()/1024 #215 #len(frames)
    print(fs,loff,loff*t1/fs,loff*t2/fs)
    #wf.close()

切り出した音声を+out.wavつけて、ファイルに保管します。
※デバッグ用にprint出力しています
wr.writeframes(b''.join(frames[s1:s2]))が肝心なコードでこれでwavファイルに書き出しています。

    wr = wave.open('wav/'+filename+'_out.wav', 'wb')
    wr.setnchannels(CHANNELS)
    wr.setsampwidth(width)  #width=2 ; 16bit
    wr.setframerate(RATE)
    s1=int(loff*(t1)/fs)
    s2=int(loff*(t2)/fs)
    print(fs,loff,s1,s2,t1,t2)
    wr.writeframes(b''.join(frames[s1:s2]))  #int(loff*t2/fs)
    #wr.close()

最後に切り出したwavファイルに基づいて、データ、STFT、そしてFFTを表示して、終了します。

    fn = wr.getnframes()
    fs = float(fn / wr.getframerate())
    print(fn,fs)
    plot_wav('wav/'+filename+'_out',0,fs)

    stream.close()
    p.terminate()
    exit()

結果

「おはよう」は以下の3つのおはようを切り出しました。
※一応、同じような生データ、STFT、FFTになっています
ohayo_out.jpg
ohayo_out.jpg
ohayo_out.jpg
「開けゴマ」も以下のように切り出せました。
hirakegoma_out.jpg
hirakegoma_out.jpg
hirakegoma_out.jpg
hirakegoma_out.jpg
hirakegoma_out.jpg

まとめ

・音声の録音データを分離するアプリを作成した
・綺麗に分離することができた。
・時間的には変化していても、時間軸の縮尺を変えると似たような表示を得た

・次回は音声認識に挑戦したいと思う

3
2
0

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
2