FFTでインターホンの音を検知する(Python)
インターホンが鳴ったかどうかをFFT(高速フーリエ変換)で調べる。
フーリエ変換とは、ある信号を様々な周波数の正弦波の和として表す考え方。
フーリエ変換やFFTに関してはこちらの記事が参考になった。
⇒離散フーリエ変換 - 人工知能に関する断創録
PyAudioでマイクから録音する
まずは、PyAudioを使ってパソコンにつないだUSBマイクから音を録音した。
以下のコードで、インターホンの「ピンポーン」という音を含んだ3秒間の音声を録音し、sound_data.wavというファイルに保存した。
import pyaudio import numpy as np import wave CHUNK = 1024 RATE = 44100 RECORD_TIMES = 3 #3秒間録音 data = [] file_name = "./sound_data.wav" p = pyaudio.PyAudio() stream = p.open(format = pyaudio.paInt16, channels = 1, #モノラル input_device_index = 0, rate = RATE, frames_per_buffer = CHUNK, input = True, output = False) for i in range(int(RATE / CHUNK * RECORD_TIMES)): d = np.frombuffer(stream.read(CHUNK), dtype='int16') data.append(d) wf = wave.open(file_name, 'w') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(RATE) stream.stop_stream() stream.close() p.terminate()
録音した音声の波の形を見てみる
matplotlibを使ってグラフに表示してみる。
import matplotlib.pyplot as plt wf = wave.open(file, "rb") data = np.frombuffer(wf.readframes(wf.getnframes()), dtype='int16') wf.close() x = np.arange(data.shape[0]) / RATE plt.plot(x, data) plt.show()
するとこんなグラフがえられた。
横軸は時間、縦軸は振幅を表している。
約0.5秒くらいから「ピンポーン」が始まっている。
これを、FFTして各周波数成分ごとに分けて表示していく。
fft_data = np.abs(np.fft.fft(data)) #FFTした信号の強度 freqList = np.fft.fftfreq(data.shape[0], d=1.0/RATE) #周波数(グラフの横軸)の取得 plt.plot(freqList, fft_data) plt.xlim(0, 5000) #0~5000Hzまでとりあえず表示する plt.show()
横軸は周波数(Hz)、縦軸は周波数成分の強度を表している。
グラフの2600Hzと2050Hz付近に大きなピークがみられる。おそらく、これらが「ピーン」と「ポーン」だと思われる。
確認のため、雑音も含めたインターホンの音のグラフも見てみる。
このグラフは、インターホンの音とテレビの雑音が含まれている音の波形である。
これを、FFTすると
こんな感じになった。やはり、2600Hzと2050Hz付近に大きなピークがみられる。インターホンの周波数はこの2つで間違いないと思う。
インターホンが鳴ったかどうかを判断する
今回は、インターホンが鳴ったらその時の音を3秒間保存する機能を作った。
コードは以下の通り
import pyaudio import numpy as np import wave from datetime import datetime CHUNK = 1024 RATE = 44100 l = 10 ** 7 sound_count = 0 data1 = [] data2 = [] freqList = np.fft.fftfreq(int(1.5 * RATE / CHUNK) * CHUNK * 2, d = 1.0 / RATE) p = pyaudio.PyAudio() stream = p.open(format = pyaudio.paInt16, channels = 1, input_device_index = 0, rate = RATE, frames_per_buffer = CHUNK, input = True, output = False) try: while stream.is_active(): for i in range(int(1.5 * RATE / CHUNK)): d = np.frombuffer(stream.read(CHUNK), dtype='int16') if sound_count == 0: data1.append(d) else: data1.append(d) data2.append(d) if sound_count >= 1: if sound_count % 2 == 1: data = np.asarray(data1).flatten() fft_data = np.fft.fft(data) data1 = [] else: data = np.asarray(data2).flatten() fft_data = np.fft.fft(data) data2 = [] fft_abs = np.abs(fft_data) data2050 = fft_abs[np.where((freqList < 2150) & (freqList > 2000))] #2050Hz付近の周波数成分 data2600 = fft_abs[np.where((freqList < 2700) & (freqList > 2500))] #2600Hz付近の周波数成分 if (data2050.max() > 0.5 * l) & (data2600.max() > 1 * l): #2050Hz付近と2600Hz付近の強度が一定以上あったとき、インターホンが鳴ったと判断 this_time = datetime.now().strftime("%Y-%m-%d %H-%M-%S ") file_name = this_time + ".wav" wf = wave.open(file_name, 'w') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(RATE) wf.writeframes(data) wf.close() print("The bell is ringing! " + this_time) data1 = [] data2 = [] sound_count = 0 sound_count += 1 except KeyboardInterrupt: stream.stop_stream() stream.close() p.terminate()
tryとかexceptはよくわからないので適当。
data1とdata2に分けている理由は3秒間の録音の切れ目で「ピーン」と「ポーン」が分かれてしまい、判定できないことを防ぐため。また、datetimeでインターホンが鳴った時の時間を取得している。