インターホンが鳴ったかどうかを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でインターホンが鳴った時の時間を取得している。
コメント
この記事を参考にさせていただいているものです。とてもありがたいです。一つ質問ですが、この記事で初めて周波数分布を出す時に「ampSpe」というものが出てきます。これまでのプログラム内には一切出てきていないようなのですが、これはなんでしょうか?
N.Y.さん
“ampSpe”の部分は2行上で定義した”fft_data”とするべき部分でした。
記事のほうは修正させていただきました。
ご指摘いただきありがとうございます。