いぬおさんのおもしろ数学実験室

おいしい紅茶でも飲みながら数学、物理、工学、プログラミング、そして読書を楽しみましょう

ノイズ入りのwavファイルからノイズを消す

 ここまでで確認した事実を組み合わせて、wavファイルのノイズを消す実験をしてみましょう。まず、以下のコードでノイズ入りのwavファイルを作ります。wavファイルの作り方は過去の記事を参照してください。
www.omoshiro-suugaku.com

#========================================================
#ノイズ入りのwaveファイルを作る。左は440Hz、右は700Hzで10秒間。
#
import scipy as sp
import sys
import numpy as np
import wave
from scipy.fftpack import fft
import matplotlib.pyplot as plt
pi = np.pi
fs = 22050 #サンプリング周波数
s = 10 #10秒の長さのwaveファイルを作る
#s秒分のサウンドデータを作るには、fs*sフレームが必要。
N = fs * s #フレーム数(サウンドデータ数。左右で1フレーム)
t = np.arange(0, s, 1/fs) #横軸の配列を作る
#sin(2π*□*t) の周波数は□の部分。16ビットで録音するので、
#-32768~32767がデータの範囲。だから-30000~30000の範囲の
#大きさの音を作ることにした。
ldata = 30000 * np.sin(2*pi*440*t) + 1500 * sp.randn(t.size) #440Hz+ノイズ
rdata = 30000 * np.sin(2*pi*700*t) + 1500 * sp.randn(t.size) #700Hz+ノイズ
sounddata = np.zeros(N * 2, dtype= "int16") #左右のデータ分が必要
sounddata[0::2] = ldata
sounddata[1::2] = rdata
writewave = wave.Wave_write("keshitene.wav") #保存先ファイル
#setparamの引数は
#(nchannels, sampwidth, framerate, nframes, comptype, compname)
#先頭から順にステレオ、サンプルのサイズ2バイト、
#サンプリング周波数fs、フレーム数N。
writewave.setparams((2, 2, fs, N, 'NONE', 'NONE'))
writewave.writeframes(sounddata) #編集したサウンドデータを保存
writewave.close()
sys.exit()
#========================================================

プログラムを実行してできあがる「keshitene.wav」を聴いてみると(ダブルクリックする)、2種類の高さの音のバックで「ザー」というノイズが聞こえています。この雑音を消します。その前にノイズがどの程度のものなのか確認しておきます。以下のコードです。これは上のコードに「#■■■」で挟まれた部分を追加したものです。FFTを施して、含まれる周波数成分の大きさを調べるのです(左の音のみ確認しています)。これも過去の記事にあります。
www.omoshiro-suugaku.com

#========================================================
#ノイズ入りのwaveファイルを作る。左は440Hz、右は700Hzで10秒間。
#
import scipy as sp
import sys
import numpy as np
import wave
from scipy.fftpack import fft
import matplotlib.pyplot as plt
pi = np.pi
fs = 22050      #サンプリング周波数
s  = 10         #10秒の長さのwaveファイルを作る
#s秒分のサウンドデータを作るには、fs*sフレームが必要。
N  = fs * s     #フレーム数(サウンドデータ数。左右で1フレーム)
t = np.arange(0, s, 1/fs)   #横軸の配列を作る
#sin(2π*□*t) の周波数は□の部分。16ビットで録音するので、
#-32768~32767がデータの範囲。だから-30000~30000の範囲の
#大きさの音を作ることにした。
ldata = 30000 * np.sin(2*pi*440*t) + 1500 * sp.randn(t.size)
rdata = 30000 * np.sin(2*pi*700*t) + 1500 * sp.randn(t.size)
#■■■
yf = sp.fftpack.fft(ldata[:N])
plt.plot(t, abs(yf))
plt.show()
#■■■
sounddata = np.zeros(N * 2, dtype= "int16") #左右のデータ分が必要
sounddata[0::2] = ldata
sounddata[1::2] = rdata
writewave = wave.Wave_write("keshitene.wav") #保存先ファイル
#setparamの引数は
#(nchannels, sampwidth, framerate, nframes, comptype, compname)
#先頭から順にステレオ、サンプルのサイズ2バイト、
#サンプリング周波数fs、フレーム数N。
writewave.setparams((2, 2, fs, N, 'NONE', 'NONE'))
writewave.writeframes(sounddata) #編集したサウンドデータを保存
writewave.close()
sys.exit()
#========================================================

これを実行してみると以下のような周波数成分のグラフが表示されます。
f:id:Inuosann:20200106223319p:plain:w300
左下あたりを何回か拡大してみると
f:id:Inuosann:20200106223811p:plain:w300
となり、どうやらノイズ部分はFFTの結果の絶対値が200万以下らしいです。そこで、一応300万以下の結果はすべて0にしてしまいます。そうした上で IFFT を施して音のデータに戻します。上のコードの「#■■■」で囲まれた部分をさらに書き換えます。以下のコードです。

|#========================================================
#ノイズ入りのwaveファイルを作る。左は440Hz、右は700Hzで10秒間。
#
import scipy as sp
import sys
import numpy as np
import wave
from scipy.fftpack import fft
import matplotlib.pyplot as plt
pi = np.pi
fs = 22050      #サンプリング周波数
s  = 10         #10秒の長さのwaveファイルを作る
#s秒分のサウンドデータを作るには、fs*sフレームが必要。
N  = fs * s     #フレーム数(サウンドデータ数。左右で1フレーム)
t = np.arange(0, s, 1/fs)   #横軸の配列を作る
#sin(2π*□*t) の周波数は□の部分。16ビットで録音するので、
#-32768~32767がデータの範囲。だから-30000~30000の範囲の
#大きさの音を作ることにした。
ldata = 30000 * np.sin(2*pi*440*t) + 1500 * sp.randn(t.size)
rdata = 30000 * np.sin(2*pi*700*t) + 1500 * sp.randn(t.size)
#■■■
yf = sp.fftpack.fft(ldata[:N])
yf[abs(yf) < 3000000] = 0 #yfの絶対値が300万未満のデータは0に
ldata = sp.fftpack.ifft(yf[:N])
yf = sp.fftpack.fft(rdata[:N])
yf[abs(yf) < 3000000] = 0 #yfの絶対値が300万未満のデータは0に
rdata = sp.fftpack.ifft(yf[:N])
#■■■
sounddata = np.zeros(N * 2, dtype= "int16") #左右のデータ分が必要
sounddata[0::2] = ldata
sounddata[1::2] = rdata
writewave = wave.Wave_write("keshitene.wav") #保存先ファイル
#setparamの引数は
#(nchannels, sampwidth, framerate, nframes, comptype, compname)
#先頭から順にステレオ、サンプルのサイズ2バイト、
#サンプリング周波数fs、フレーム数N。
writewave.setparams((2, 2, fs, N, 'NONE', 'NONE'))
writewave.writeframes(sounddata) #編集したサウンドデータを保存
writewave.close()
sys.exit()
#========================================================

できあがった「keshitene.wav」を聴いてみると、確かにノイズが消えてきれいな音に戻っています。念のため、「#■■■」で囲まれた部分(雑音をカットするコード)をコメントアウトしてみると、もちろんノイズ入りのwavファイルができあがります。

 なかなか面白い実験でした。やはり、自分でコーディングして実行してみて……とやらないとダメですね。そうして初めて分かるいろいろなことがあります。
 プログラミング言語の習得の効果的な方法は「とにかく実験をやってみること。結果はきちんとまとめて、あとで使えるようにしておくこと」だと思います。老練な職人が自分の道具を手入れするようなものです。大きなプログラムを書く段になるとまた別の技術が必要になりますが……。足下から、しっかりした土台の上に丁寧に順番に組み立てて行く感じがよいと思います。時間は多少かかっても、東京タワーやスカイツリーみたいな立派なものが作れます。