首先引入一个平稳与非平稳的概念:
非平稳信号分析的挑战:
这里使用一个例子来说明,例如对以下两个函数(一个平稳、另一个非平稳) f 1 ( t ) = cos ( 2 π × 10 t ) + cos ( 2 π × 25 t ) + cos ( 2 π × 50 t ) + cos ( 2 π × 100 t ) f_1(t) = \cos(2\pi\times10t) + \cos(2\pi\times25t) + \cos(2\pi\times50t)+\cos(2\pi\times100t) f1(t)=cos(2π×10t)+cos(2π×25t)+cos(2π×50t)+cos(2π×100t)
f 2 ( t ) = { cos ( 2 π × 10 t ) 0 ≤ t ≤ 0.20 cos ( 2 π × 25 t ) 0.20 ≤ t ≤ 0.40 cos ( 2 π × 50 t ) 0.40 ≤ t ≤ 0.70 cos ( 2 π × 100 t ) 0.70 ≤ t ≤ 1.00 f_2(t) = \left\{ \begin{aligned} &\cos(2\pi\times10t)~~~0\le t\le 0.20 \\ &\cos(2\pi\times25t)~~~0.20\le t\le 0.40 \\ &\cos(2\pi\times50t)~~~0.40\le t\le 0.70 \\ &\cos(2\pi\times100t)~~~0.70\le t\le 1.00 \\ \end{aligned}\right. f2(t)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧cos(2π×10t) 0≤t≤0.20cos(2π×25t) 0.20≤t≤0.40cos(2π×50t) 0.40≤t≤0.70cos(2π×100t) 0.70≤t≤1.00
两个信号的幅频特性:四个主要的尖峰。
50Hz和100Hz分量的幅度比25Hz和10Hz分量大,这是因为高频信号比低频信号持续时间更长一些(分别为300ms和200ms)。若忽略掉因频率突变引起的毛刺(有时候他们与噪声很难区分)和两幅图中各频率分量的幅值(这些幅值可以做归一化处理),两个信号的频谱图几乎是一致的,但实际上两个时域信号的差别极大。
说明引入的原因:
(1)傅里叶变换的全局积分导致变换结果无法提供频率分量的时间信息。
(2)对于非平稳信号来说,傅里叶变换一般是不合适的。
(3)只有仅仅关心信号中是否包含某个频率分量而不关心它出现的时间的时候,傅里叶变换才可以用于处理非平稳信号。
概括起来就是:增加一维的自由度,使频谱可以反映其不同频率状态所处的不同时间。
矩形窗
使用不同的窗,频域曲线的平滑程度,不同频率分量信息的清晰程度均有区别。从下面这个例子中可以看到 Blackman Window 的效果相对较好,频域曲线相对比较平滑并且不同频率分量的信息也比较清晰。
不同的窗长影响频域幅度分量信息的含量,窗越长,频域所反映出来的信息也越多。
窗长度的奇偶性主要影响频域的相位分量的对称性,窗长为奇数时(对应M为偶数)相位是关于纵坐标轴完全对称的,因此一般我们选择窗长为奇数(对应M为偶数)。
更长的 FFT 的长度(使用到Zero Padding)可以保证频域幅度曲线更加平滑,并且幅度的细节体现的更好。
我们希望的情况是通过叠加,最终呈现结果是一条取值为 1 的直线,这样可以保证不会由叠加这个行为产生信号畸变与失真。
窗函数的宽度与信号的表示方式有关,它决定是具有良好的频率分辨率(靠的很近的频率成分可以被分离)还是良好的时间分辨率(频率变化的时间)。较宽的窗口提供更好的频率分辨率,但时间分辨率较差。较窄的窗口给出较好的时间分辨率,但频率分辨率较差。这个性质与海森堡测不准原理有关,但更具体的可以参考Gabor极限。
可以用一个例子来更好的说明这个问题,对如下这个函数采用不同的窗长进行变换:
f ( t ) = { cos ( 2 π × 10 t ) 0 ≤ t ≤ 5 cos ( 2 π × 25 t ) 5 ≤ t ≤ 10 cos ( 2 π × 50 t ) 10 ≤ t ≤ 15 cos ( 2 π × 100 t ) 15 ≤ t ≤ 20 f(t) = \left\{ \begin{aligned} &\cos(2\pi\times10t)~~~0\le t\le 5 \\ &\cos(2\pi\times25t)~~~5\le t\le 10 \\ &\cos(2\pi\times50t)~~~10\le t\le 15 \\ &\cos(2\pi\times100t)~~~15\le t\le 20 \\ \end{aligned}\right. f(t)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧cos(2π×10t) 0≤t≤5cos(2π×25t) 5≤t≤10cos(2π×50t) 10≤t≤15cos(2π×100t) 15≤t≤20
25 ms 的窗保证了比较好的时间分辨率,但是频率分辨率很差;1000 ms 的窗保证了比较好的频率分辨率,但是时间分辨率很差。
stftAnal
函数输出结果的单位是 dB。stftAnal
函数的处理中使用了在信号 x 的最前面与最后面补零这一技巧,目的是为了尽可能的使输出和输入的信号保持一致,减小因为进行stft而引入的噪声。DFT.dftAnal
与 DFT.dftSynth
下面直接给出代码(博主GPS还没预习完,先偷个懒直接附上代码)。def stftAnal(x, w, N, H):
"""
x: 输入信号, w: 分析窗, N: FFT 的大小, H: hop 的大小
返回 xmX, xpX: 振幅和相位,以 dB 为单位
"""
M = w.size # 分析窗的大小
hM1 = (M+1)//2
hM2 = M//2
x = np.append(np.zeros(hM2),x) # 在信号 x 的最前面与最后面补零
x = np.append(x,np.zeros(hM2))
pin = hM1 # 初始化指针,用来指示现在指示现在正在处理哪一帧
pend = x.size-hM1 # 最后一帧的位置
w = w / sum(w) # 归一化分析窗
xmX = []
xpX = []
while pin<=pend:
x1 = x[pin-hM1:pin+hM2] # 选择一帧输入的信号
mX, pX = dftAnal(x1, w, N) # 计算 DFT(这个函数不是库中的)
xmX.append(np.array(mX)) # 添加到 list 中
xpX.append(np.array(pX))
pin += H # 更新指针指示的位置
xmX = np.array(xmX) # 转换为 numpy 数组
xpX = np.array(xpX)
return xmX, xpX
def stftSynth(mY, pY, M, H) :
"""
mY: 振幅谱以dB为单位, pY: 相位谱, M: 分析窗的大小, H: hop 的大小
返回 y 还原后的信号
"""
hM1 = (M+1)//2
hM2 = M//2
nFrames = mY[:,0].size # 计算帧的数量
y = np.zeros(nFrames*H + hM1 + hM2) # 初始化输出向量
pin = hM1
for i in range(nFrames): # 迭代所有帧
y1 = dftSynth(mY[i,:], pY[i,:], M) # 计算IDFT(这个函数不是库中的)
y[pin-hM1:pin+hM2] += H*y1 # overlap-add
pin += H # pin是一个指针,用来指示现在指示现在正在处理哪一帧
y = np.delete(y, range(hM2)) # 删除头部在stftAnal中添加的部分
y = np.delete(y, range(y.size-hM1, y.size)) # 删除尾部在stftAnal中添加的部分
return y
附上dft的两段代码:
def dftAnal(x, w, N):
"""
Analysis of a signal using the discrete Fourier transform
x: input signal, w: analysis window, N: FFT size
returns mX, pX: magnitude and phase spectrum
"""
if not(UF.isPower2(N)): # raise error if N not a power of two
raise ValueError("FFT size (N) is not a power of 2")
if (w.size > N): # raise error if window size bigger than fft size
raise ValueError("Window size (M) is bigger than FFT size")
hN = (N//2)+1 # size of positive spectrum, it includes sample 0
hM1 = (w.size+1)//2 # half analysis window size by rounding
hM2 = w.size//2 # half analysis window size by floor
fftbuffer = np.zeros(N) # initialize buffer for FFT
w = w / sum(w) # normalize analysis window
xw = x*w # window the input sound
fftbuffer[:hM1] = xw[hM2:] # zero-phase window in fftbuffer
fftbuffer[-hM2:] = xw[:hM2]
X = fft(fftbuffer) # compute FFT
absX = abs(X[:hN]) # compute ansolute value of positive side
absX[absX<np.finfo(float).eps] = np.finfo(float).eps # if zeros add epsilon to handle log
mX = 20 * np.log10(absX) # magnitude spectrum of positive frequencies in dB
X[:hN].real[np.abs(X[:hN].real) < tol] = 0.0 # for phase calculation set to 0 the small values
X[:hN].imag[np.abs(X[:hN].imag) < tol] = 0.0 # for phase calculation set to 0 the small values
pX = np.unwrap(np.angle(X[:hN])) # unwrapped phase spectrum of positive frequencies
return mX, pX
def dftSynth(mX, pX, M):
"""
Synthesis of a signal using the discrete Fourier transform
mX: magnitude spectrum, pX: phase spectrum, M: window size
returns y: output signal
"""
hN = mX.size # size of positive spectrum, it includes sample 0
N = (hN-1)*2 # FFT size
if not(UF.isPower2(N)): # raise error if N not a power of two, thus mX is wrong
raise ValueError("size of mX is not (N/2)+1")
hM1 = int(math.floor((M+1)/2)) # half analysis window size by rounding
hM2 = int(math.floor(M/2)) # half analysis window size by floor
fftbuffer = np.zeros(N) # initialize buffer for FFT
y = np.zeros(M) # initialize output array
Y = np.zeros(N, dtype = complex) # clean output spectrum
Y[:hN] = 10**(mX/20) * np.exp(1j*pX) # generate positive frequencies
Y[hN:] = 10**(mX[-2:0:-1]/20) * np.exp(-1j*pX[-2:0:-1]) # generate negative frequencies
fftbuffer = np.real(ifft(Y)) # compute inverse FFT
y[:hM2] = fftbuffer[-hM2:] # undo zero-phase window
y[hM2:] = fftbuffer[:hM1]
return y