Pythonで簡単な音声ノイズ除去してplotしてみた

#はじめに
大学一回の初めての春休み。思ったよりやることがなくて随分と暇な生活です。サークルの活動ではpocketsphinx等の音声認識ソフトを使って開発していた折、具体的にソフトの仕組みがわかっていないのもなあ。。。と思ったので音声認識ソフトを暇つぶしに作ってみよう！ということで勉強をはじめました。
**プログラムもQiitaも音声処理も初心者なので、アドバイスが貰えれば幸いです。**
#環境
- ubuntu16.04
- python2.7

#使用したPythonのモジュール
- Pyaudio
- Numpy
- Matplotlib
- wave
- time
- sys

#したいこと
音声認識ソフトの第一歩、データセットの作成をしたいです。録音した音声からノイズ除去した綺麗なデータがほしいので、今回は一番手軽な人間の声の周数帯以外の周波数のデシベルを０にしてノイズ除去を行う方法でやってみます。
視覚的にわかりやすくしたかったので、いろいろプロットして模索しました。
#書いたクソコード
```sound_plot.py
#! -*- coding:utf-8 -*-
import pyaudio
import sys
import time
import wave
import numpy as np
import matplotlib.pyplot as plt
CHUNK = 1024#一回あたり(1/RATE秒毎)に取るデータサイズ,2^10が多い
FORMAT = pyaudio.paInt16
CHANNELS = 1##モノラル入力
RATE = 44100 #サンプリングレート.....マイク性能に依存......
#### input overflow が出たらサンプリングレートを下げる(16000Hz推奨)

RECORD_SECONDS = 3 #計測時間

#######ノイズ除去周波数帯の設定#########
HIGH_CUT = 2400 ##HIGH_CUT周波数
LOW_CUT = 300  ##LOW_CUT周波数

###FFTしてplotするクラス
class FFT_plot:
    def __init__(self,data):
        self.data = data##録音したdata
        self.fft = np.fft.fft(np.frombuffer(data,dtype="int16"))##FFTしたデータ
        self.abs=np.abs(self.fft)##FFTを絶対値化したデータ
        self.fq = np.linspace(0,len(self.fft),len(self.fft))##周波数軸
    def wave_plot(self):##録音をそのままプロット
        x = self.data

        plt.figure(figsize=(15,3))
        plt.plot(x)
        plt.show()
    def fft_plot(self):##FFTをプロット    
        x = self.fft

        plt.figure(figsize=(15,3))
        plt.plot(x.real[:int(len(x)/2)])
        plt.show()


    def abs_plot(self):##FFT絶対値化をプロット
        x = self.abs
        F_abs_amp = x/len(x)*2
        fq = self.fq

        plt.plot(fq,F_abs_amp)
        plt.show()

class noise_delete_plot(FFT_plot,object):
    def __init__(self,data):
        super(noise_delete_plot,self).__init__(data)
    
    def noise_delete(self):##noise除去後のFFTデータを返す
        fq = self.fq
        fh = HIGH_CUT
        fl = LOW_CUT
        delete_fft = self.fft
        delete_fft[(fq>fh)]=0
        delete_fft[(fq<fl)]=0

        return delete_fft
    def noise_delete_abs(self,delete_fft):##noise除去後のFFT絶対値化データをプロット
        fq = self.fq
        F2_abs = np.abs(delete_fft)
        F2_abs_amp = F2_abs/len(F2_abs)*2

        plt.plot(fq,F2_abs_amp)
        plt.show()
    def noise_delete_wave(self,delete_fft):##noise除去後の音声をプロット
        
        F2_ifft = np.fft.ifft(delete_fft)
        F2_ifft_real = F2_ifft.real * 2

        plt.figure(figsize=(15,3))
        plt.plot(F2_ifft_real)
        plt.show()
def record():
    p = pyaudio.PyAudio()#インスタンスの作成
#####streamを開く,input = Trueなのでデータを流し込む
    stream = p.open(
        format = FORMAT,
        channels = CHANNELS,
        rate = RATE,
        input = True,
        frames_per_buffer = CHUNK
        )

    wav_data = []
######動的音源からの音声取得時の注意##########################
#forループが1フレーム(1/RATE秒毎)ごとにループではない
#すべてのchunkに対してループしてしまうのでCHUNKで割ってデータ数を合わせる
##########################################################33
    print ("録音開始")
    for i in range(0, int(RATE * RECORD_SECONDS/CHUNK)):
        data = stream.read(CHUNK)
        ###streamから1024サイズのデータ(1frame)まるごと取得
        ###が、得たデータは2048になるのでreshapeする
        ###特に特殊な操作はせずint16型に治すと自動で戻る
        wav_data.append(data)
    print("録音終了")
##PyAudioのインスタンスとstreamを終了させる
    stream.close()
    p.terminate()
##int16型に治す
    wav_data = np.array(wav_data)
    wav_data = np.frombuffer(wav_data,dtype="int16")

    print (wav_data.shape)
    return wav_data

if __name__ =='__main__':
    r = record()
    n = noise_delete_plot(r)
    delete_fft = n.noise_delete()
    n.noise_delete_wave(delete_fft)


```
少し長いので分けて説明します。
###Class FFT_plot 
```FFT_plot.py
###FFTしてplotするクラス
class FFT_plot:
    def __init__(self,data):
        self.data = data##録音したdata
        self.fft = np.fft.fft(np.frombuffer(data,dtype="int16"))##FFTしたデータ
        self.abs=np.abs(self.fft)##FFTを絶対値化したデータ
        self.fq = np.linspace(0,len(self.fft),len(self.fft))##周波数軸
    def wave_plot(self):##録音をそのままプロット
        x = self.data

        plt.figure(figsize=(15,3))
        plt.plot(x)
        plt.show()
    def fft_plot(self):##FFTをプロット    
        x = self.fft

        plt.figure(figsize=(15,3))
        plt.plot(x.real[:int(len(x)/2)])
        plt.show()
    def abs_plot(self):##FFT絶対値化をプロット
        x = self.abs
        F_abs_amp = x/len(x)*2
        fq = self.fq

        plt.plot(fq,F_abs_amp)
        plt.show()
```
録音したデータをそのままプロットする関数、FFTしたデータをプロットする関数、FFTしたデータの複素数成分を絶対値をとってプロットする関数からなるクラスです。アンプは交流成分をデータ数で割って２倍すれば作成可能です。
###Class noise_delete_plot
```noise_delete_plot.py
class noise_delete_plot(FFT_plot,object):
    def __init__(self,data):
        super(noise_delete_plot,self).__init__(data)
    
    def noise_delete(self):##noise除去後のFFTデータを返す
        fq = self.fq
        fh = HIGH_CUT
        fl = LOW_CUT
        delete_fft = self.fft
        delete_fft[(fq>fh)]=0
        delete_fft[(fq<fl)]=0

        return delete_fft
    def noise_delete_abs(self,delete_fft):##noise除去後のFFT絶対値化データをプロット
        fq = self.fq
        F2_abs = np.abs(delete_fft)
        F2_abs_amp = F2_abs/len(F2_abs)*2

        plt.plot(fq,F2_abs_amp)
        plt.show()
    def noise_delete_wave(self,delete_fft):##noise除去後の音声をプロット
        
        F2_ifft = np.fft.ifft(delete_fft)
        F2_ifft_real = F2_ifft.real * 2

        plt.figure(figsize=(15,3))
        plt.plot(F2_ifft_real)
        plt.show()
```
noise除去する関数、noise除去後のFFT絶対値化データをプロットする関数、noise除去したデータをプロットする関数からなるクラスです。
ノイズ除去した後、IFFTしています。
###def record
```record.py
def record():
    p = pyaudio.PyAudio()#インスタンスの作成
#####streamを開く,input = Trueなのでデータを流し込む
    stream = p.open(
        format = FORMAT,
        channels = CHANNELS,
        rate = RATE,
        input = True,
        frames_per_buffer = CHUNK
        )

    wav_data = []
######動的音源からの音声取得時の注意##########################
#forループが1フレーム(1/RATE秒毎)ごとにループではない
#すべてのchunkに対してループしてしまうのでCHUNKで割ってデータ数を合わせる
##########################################################33
    print ("録音開始")
    for i in range(0, int(RATE * RECORD_SECONDS/CHUNK)):
        data = stream.read(CHUNK)
        ###streamから1024サイズのデータ(1frame)まるごと取得
        ###が、得たデータは2048になるのでreshapeする
        ###特に特殊な操作はせずint16型に治すと自動で戻る
        wav_data.append(data)
    print("録音終了")
##PyAudioのインスタンスとstreamを終了させる
    stream.close()
    p.terminate()
##int16型に治す
    wav_data = np.array(wav_data)
    wav_data = np.frombuffer(wav_data,dtype="int16")

    print (wav_data.shape)
    return wav_data

```
for文の条件に関して正確なデータ数かと言われればそうではありません。サンプリングレート44100Hzで計測時間が3秒ならば、44100*3=132300フレーム分のデータが得られますが、int(RATE * RECORD_SECONDS/CHUNK)で強制的に整数にしているので、その分のズレがでます。この例では132096フレーム分のデータになるので、少し気になります。。。現状、解決策としては、RATE * RECORD_SECONDSを、CHUNKの倍数にするくらいしか思いつきません。。実際はそんなに影響ないのかも？？

##ノイズ除去の性能
###処理前
![qiita_wave.png](https://qiita-image-store.s3.amazonaws.com/0/348051/3552e3f1-a511-8a3a-e8ca-251256493630.png)
###処理後
![qiita_noiseremove.png](https://qiita-image-store.s3.amazonaws.com/0/348051/08a08abd-c641-8e84-26b1-c967ec3de6b0.png)
うーん、なにこれ（）
##反省、次にしてみたいこと
なんとなく綺麗になったような気がしなくもない。。。ですね。。０にするのが正しいのかも怪しいですし。。。（そもそも綺麗に見えるのが正しいわけではない）今回はフェルタリングをかける方法でやってみましたが、他の方法も試して比較、検討してデータセットを作る上で最適な方法を模索したいと思います。**まだまだQiitaもプログラムも初心者なので、アドバイスがあれば嬉しいです。**
#参考にさせて頂いたサイト
http://takeshid.hatenadiary.jp/entry/2016/01/10/153503
http://hanpakousaku.tumblr.com/post/105771613672/raspberrypi-%E3%81%A8-pyaudio%E3%81%A7%E9%8C%B2%E9%9F%B3%E9%9F%B3%E5%A3%B0%E6%B3%A2%E5%BD%A2%E5%87%A6%E7%90%86
https://momonoki2017.blogspot.com/2018/03/pythonfft-4.html