不确定如何将FFT数据用于频谱分析仪

时间:2016-12-12 04:07:36

标签: python numpy fft

我正在尝试用8条LED灯创建一个自制频谱分析仪。

我正在努力的部分是执行FFT并了解如何使用结果。

到目前为止,这就是我所拥有的:

import opc
import time
import pyaudio
import wave
import sys
import numpy
import math

CHUNK = 1024

# Gets the pitch from the audio
def pitch(signal):
  # NOT SURE IF ANY OF THIS IS CORRECT
  signal = numpy.fromstring(signal, 'Int16');
  print "signal = ", signal

  testing = numpy.fft.fft(signal)
  print "testing = ", testing

wf = wave.open(sys.argv[1], 'rb')
RATE = wf.getframerate()
p = pyaudio.PyAudio() # Instantiate PyAudio

# Open Stream
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

# Read data
data = wf.readframes(CHUNK)

# Play Stream
while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)
    frequency = pitch(data)
    print "%f frequency" %frequency

我正忙着在pitch方法中做些什么。我知道我需要对传入的数据执行FFT,但我真的不确定该怎么做。

还应该使用this功能吗?

2 个答案:

答案 0 :(得分:4)

由于np.fft.fft的工作方式,如果使用1024个数据点,您将获得512个频率的值(加上零值Hz, DC偏移)。如果您只需要8个频率,则必须为其提供16个数据点。

你可以通过64倍的下采样来做你想要的事情 - 然后16个下采样点将时间等效到1024个原始点。我从来没有探究过这个,所以我不知道这会带来什么或者可能是什么陷阱。

你将不得不做一些学习 - The Scientist and Engineer's Guide to Digital Signal Processing真的是一种优秀的资源,至少对我而言。

请记住,对于音频CD .wav文件,采样频率为44100 Hz - 1024个采样块仅为声音的23 mS。

scipy.io.wavfile.read可让您轻松获取数据。

samp_rate, data = scipy.io.wavfile.read(filename)

data是一个2-d numpy数组,其中一个通道位于第0列,数据为[:,0],另一个位于第1列,数据为[:,1]

Matplotlib的specgram和psd函数可以为您提供所需的数据。与您尝试做的相似的图形将是。

from matplotlib import pyplot as plt
import scipy.io.wavfile
samp_rate, data = scipy.io.wavfile.read(filename)
Pxx, freqs, bins, im = plt.specgram(data[:1024,0], NFFT = 16, noverlap = 0, Fs = samp_rate)
plt.show()
plt.close()

由于您没有进行任何绘图,只需使用matplolib.mlab.specgram

Pxx, freqs, t = matplolib.mlab.specgram(data[:1024,0], NFFT = 16, noverlap = 0, Fs = samp_rate)

其返回值( Pxx freqs t

     - *Pxx*: 2-D array, columns are the periodograms of successive segments

     - *freqs*: 1-D array of frequencies corresponding to the rows in Pxx

     - *t*: 1-D array of times corresponding to midpoints of segments.

Pxx[1:, 0]将是T0的频率值,T1的Pxx[1:, 1],T2的Pxx[1:, 2],......这是您要显示的内容。您不使用Pxx[0, :],因为它是0 Hz。

功率谱密度 - matplotlib.mlab.psd()

可能另一种降低8个频段的策略是使用大块并对值进行规范化。然后,您可以将值分解为八个段,并获得每个段的总和。我认为这是有效的 - 可能仅适用于功率谱密度。 sklearn.preprocessing.normalize

w = sklearn.preprocessing.normalize(Pxx[1:,:], norm = 'l1', axis = 0)

但话又说回来,我就把这一切都搞定了。

答案 1 :(得分:1)

我不知道@wwii在他的回答中提到的scipy.io.wavfile.read函数,但似乎他的建议是处理信号加载的方法。但是,我只是想评论傅里叶变换。

我认为您打算对LED设置做的是根据您打算使用的8个频段中每个频段的光谱功率来改变每个LED的亮度。因此,我所理解的你需要的是随着时间的推移以某种方式计算能量。第一个复杂因素是"如何计算光谱功率?"

执行此操作的最佳方法是使用numpy.fft.rfft,它计算仅具有实数(不是复数)的信号的傅里叶变换。另一方面,函数numpy.fft.fft是一个通用函数,可以为具有复数的信号计算快速傅里叶变换。概念上的差异是numpy.fft.fft可用于研究行波及其传播方向。这是因为返回的幅度对应于positive or negative frequencies,表示波的传播方式。 numpy.fft.rfft产生实值频率的幅度,如numpy.fft.rfftfreq所示,这就是你需要的。

最后一个问题是选择适当的频段来计算频谱功率。人耳具有巨大的频率响应范围,并且每个频带的宽度将变化很大,低频带非常窄并且高频带非常宽。谷歌搜索,我发现this很好的资源,定义了7个相关的频段

  1. 次低音:20至60 Hz
  2. 低音:60至250 Hz
  3. 低中频:250至500 Hz
  4. 中频:500 Hz至2 kHz
  5. 中上部:2至4 kHz
  6. 存在:4至6 kHz
  7. 亮度:6至20 kHz
  8. 我建议使用这些频段,但将上中频分为2-3 kHz和3-4 kHz。这样您就可以使用8 LED设置。我上传了更新的音高函数供您使用

    wf = wave.open(sys.argv[1], 'rb')
    CHUNK = 1024
    RATE = wf.getframerate()
    DT = 1./float(RATE)   # time between two successive audio frames
    FFT_FREQS = numpy.fft.nfftfreq(CHUNCK,DT)
    FFT_FREQS_INDS = -numpy.ones_like(FFT_FREQS)
    bands_bounds = [[20,60],      # Sub-bass
                    [60,250],     # Bass
                    [250,500],    # Low midrange
                    [500,2000],   # Midrange
                    [2000,3000],  # Upper midrange 0
                    [3000,4000],  # Upper midrange 1
                    [4000,6000],  # Presence
                    [6000,20000]] # Brilliance
    
    for f_ind,freq in enumerate(FFT_FREQS):
        for led_ind,bounds in enumerate(bands_bounds):
            if freq<bounds[1] and freq>=bounds[0]:
                FFT_FREQS_INDS[ind] = led_ind
    
    # Returns the spectral power in each of the 8 bands assigned to the LEDs
    def pitch(signal):
        # CONSIDER SWITCHING TO scipy.io.wavfile.read TO GET SIGNAL
        signal = numpy.fromstring(signal, 'Int16');
        amplitude = numpy.fft.rfft(signal.astype(numpy.float))
        power = [np.sum(np.abs(amplitude[FFT_FREQS_INDS==led_ind])**2) for led_ind in range(len(bands_bounds))]
        return power
    

    代码的第一部分计算fft频率并构造数组FFT_FREQS_INDS,指示fft频率对应的8个频段中的哪一个。然后,在pitch中,计算每个频带中的频谱功率。当然,这可以优化,但我试图使代码不言自明。