我的主要任务是实时识别人从麦克风发出的嗡嗡声。一般来说,作为识别信号的第一步,我记录了从手机上的应用程序生成的440 Hz信号的5秒钟,并尝试检测相同的频率。
我使用Audacity绘制并验证了来自相同440Hz wav文件的频谱,我得到了这一结果,这表明440Hz实际上是主要频率: (https://i.imgur.com/2UImEkR.png)
要使用python做到这一点,我使用PyAudio库并引用this blog。到目前为止,我使用wav文件运行的代码是:
"""PyAudio Example: Play a WAVE file."""
import pyaudio
import wave
import sys
import struct
import numpy as np
import matplotlib.pyplot as plt
CHUNK = 1024
if len(sys.argv) < 2:
print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(CHUNK)
i = 0
while data != '':
i += 1
data_unpacked = struct.unpack('{n}h'.format(n= len(data)/2 ), data)
data_np = np.array(data_unpacked)
data_fft = np.fft.fft(data_np)
data_freq = np.abs(data_fft)/len(data_fft) # Dividing by length to normalize the amplitude as per https://www.mathworks.com/matlabcentral/answers/162846-amplitude-of-signal-after-fft-operation
print("Chunk: {} max_freq: {}".format(i,np.argmax(data_freq)))
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(data_freq)
ax.set_xscale('log')
plt.show()
stream.write(data)
data = wf.readframes(CHUNK)
stream.stop_stream()
stream.close()
p.terminate()
在输出中,我得到所有块的最大频率为10,其中一个图的示例为: (https://i.imgur.com/zsAXME5.png)
我本来希望所有块的这个值是440,而不是10。我承认我对FFT的理论了解甚少,也感谢我为解决这个问题所提供的帮助。
编辑: 采样率为44100。的通道数是2,样本宽度也是2。
答案 0 :(得分:0)
正如xdurch0
所指出的那样,您正在读取一种索引而不是频率。如果要自己进行所有计算,则在绘制图形之前,如果要获得一致的结果,则需要计算自己的频率向量。阅读此answer可能有助于您找到解决方案。
FFT(半平面)的频率向量为:
f = np.linspace(0, rate/2, N_fft/2)
或(全平面):
f = np.linspace(-rate/2, rate/2, N_fft)
另一方面,我们可以将大部分工作委托给出色的scipy.signal
工具箱,该工具箱旨在解决此类问题(以及更多问题)。
使用scipy
包,可以很容易地为单一频率(source)的简单WAV
文件获得所需的结果:
import numpy as np
from scipy import signal
from scipy.io import wavfile
import matplotlib.pyplot as plt
# Read the file (rate and data):
rate, data = wavfile.read('tone.wav') # See source
# Compute PSD:
f, P = signal.periodogram(data, rate) # Frequencies and PSD
# Display PSD:
fig, axe = plt.subplots()
axe.semilogy(f, P)
axe.set_xlim([0,500])
axe.set_ylim([1e-8, 1e10])
axe.set_xlabel(r'Frequency, $\nu$ $[\mathrm{Hz}]$')
axe.set_ylabel(r'PSD, $P$ $[\mathrm{AU^2Hz}^{-1}]$')
axe.set_title('Periodogram')
axe.grid(which='both')
基本上:
wav
file并获得采样率(此处为44.1kHz
); matplotlib
显示它。这将输出:
然后,我们可以使用find_peaks
找到第一个最高峰的频率(P>1e-2
,此准则可能需要调整):
idx = signal.find_peaks(P, height=1e-2)[0][0]
f[idx] # 440.0 Hz
将所有内容放在一起只能归结为:
def freq(filename, setup={'height': 1e-2}):
rate, data = wavfile.read(filename)
f, P = signal.periodogram(data, rate)
return f[signal.find_peaks(P, **setup)[0][0]]
我在我的wav文件中尝试了此代码,并得到该行的错误 axe.semilogy(f,Pxx_den)如下:ValueError:x和y必须具有 相同的第一维度。我检查了形状,f有(2,) Pxx_den具有(220160,2)。另外,Pxx_den数组似乎拥有全部 仅零。
Wav file可以容纳多个通道,主要是单声道或立体声文件(最多2**16 - 1
个通道)。您强调的问题是由于多个频道文件(stereo sample)引起的。
rate, data = wavfile.read('aaaah.wav') # Shape: (46447, 2), Rate: 48 kHz
它的文档尚不完善,但是方法signal.periodogram
也在矩阵上执行,其输入与wavfile.read
的输出不直接相符(默认情况下,它们在不同的轴上执行)。因此,在执行PSD时,我们需要仔细调整尺寸方向(使用axis
开关:
f, P = signal.periodogram(data, rate, axis=0, detrend='linear')
它也可以与转置data.T
一起使用,但是我们需要向后转置结果。
指定轴可解决问题:频率矢量正确且PSD并非在所有位置都为空(在长度为axis=1
的{{1}}上执行之前,在您的情况下,它在2上执行了220160 PSD -采样我们想要相反的信号。
2
开关可确保信号均值为零,并且线性趋势已消除。
只要块中包含足够的数据(请参见Nyquist-Shannon sampling theorem),该方法应适用于实际的块状样本。然后,数据是信号(块)的子样本,并且速率在此过程中不会改变,因此保持恒定。
具有detrend
大小的块似乎有效,我们可以从中识别出特定的频率:
2**10
这时,最棘手的部分是对f, P = signal.periodogram(data[:2**10,:], rate, axis=0, detrend='linear') # Shapes: (513,) (513, 2)
idx0 = signal.find_peaks(P[:,0], threshold=0.01, distance=50)[0] # Peaks: [46.875, 2625., 13312.5, 16921.875] Hz
fig, axe = plt.subplots(2, 1, sharex=True, sharey=True)
axe[0].loglog(f, P[:,0])
axe[0].loglog(f[idx0], P[idx0,0], '.')
# [...]
方法的微调以捕获所需的频率。您可能需要考虑对信号进行预滤波或对PSD进行后处理,以便于识别。