转载于知乎
参考
连续小波变换(CWT)叫做连续小波或者分析小波,其中ϕ叫做基本小波或者母小波,a,b均为实数,分别称为尺度和平移因子。
连续小波{ϕa,b}中,a称为尺度,是表征频率的参数,b是表征时间或空间位置的参数。它的时、频域窗口中心及宽度均随尺度a的变化而伸缩,而连续小波基函数的窗口面积不随参数a,b而变。尺度的倒数1/a对应于频率ω,即尺度越小,对应频率越高,a尺度越大,对应频率越低。假设将尺度理解为时间窗口,则大尺度信号为长时间信号,小尺度信号为短时间信号。
窗口宽度为:
通常称这个比值称为带通滤波器的品质因数。
由以上分析可知,小波基函数ϕa,b(t)作为带通滤波器,其品质因数不随尺度a变化,是一组频率特性等Q的带通滤波器组。当尺度增加时,时间窗变宽,频率窗变窄,这时适合于提取多成份信号的低频成份;当尺度减小时,时间窗变窄,频率窗变宽,此时就适合提取多成份信号的高频成份。图 2.1 给出小波变换的时间-频率窗的示意图:
在实际应用中,以计算机为中心的测量控制系统所能处理的信号是离散信号,另外,对于f(t)的连续小波变换,不但它的系数(Wϕf)(a,b)的信息量是冗余的,而且在计算量大。离散小波变换是将尺度和位移进行离散取值,在不丢失信息的情况下减少小波基的数量,符合实际情况,同时能较好的减少计算量。
这里使用连续小波变换进行时频域分析,数据只是示例,代码中的参数在实际应用的时候需要根据实际情况进行调整。代码中有关小波尺度的计算很有意思,这里单独拿出来详细说明下。
一般用小波的尺度来衡量小波的频率,两者之间的转换关系为:
其中Fs为采样频率,wcf为小波的中心频率。
我们以下文代码中的参数为例,当一共想要分析totalscal个频率的时候,第i个频率fi对应的是
带入上面的等式中
该部分对应的代码如下:
def TimeFrequencyWP(data, fs, wavelet, maxlevel = 8):
# 小波包变换这里的采样频率为250,如果maxlevel太小部分波段分析不到
wp = pywt.WaveletPacket(data=data, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
# 频谱由低到高的对应关系,这里需要注意小波变换的频带排列默认并不是顺序排列,所以这里需要使用’freq‘排序。
freqTree = [node.path for node in wp.get_level(maxlevel, 'freq')]
# 计算maxlevel最小频段的带宽
freqBand = fs/(2**maxlevel)
#######################根据实际情况计算频谱对应关系,这里要注意系数的顺序
# 绘图显示
fig, axes = plt.subplots(len(iter_freqs)+1, 1, figsize=(10, 7), sharex=True, sharey=False)
# 绘制原始数据
axes[0].plot(data)
axes[0].set_title('原始数据')
for iter in range(len(iter_freqs)):
# 构造空的小波包
new_wp = pywt.WaveletPacket(data=None, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
for i in range(len(freqTree)):
# 第i个频段的最小频率
bandMin = i * freqBand
# 第i个频段的最大频率
bandMax = bandMin + freqBand
# 判断第i个频段是否在要分析的范围内
if (iter_freqs[iter]['fmin']<=bandMin and iter_freqs[iter]['fmax']>= bandMax):
# 给新构造的小波包参数赋值
new_wp[freqTree[i]] = wp[freqTree[i]].data
# 绘制对应频率的数据
axes[iter+1].plot(new_wp.reconstruct(update=True))
# 设置图名
axes[iter+1].set_title(iter_freqs[iter]['name'])
plt.show()
if __name__ == "__main__":
epochsCom = mne.read_epochs(r'epoch_raw.fif')
#三层list get_data()[0][i]其中i是通道口
print(epochsCom[0].get_data()[0])
dataCom = epochsCom[0].get_data()[0][0]
TimeFrequencyCWT(dataCom,fs=250,totalscal=10,wavelet='cgau8')
小波包分解可以同时分析低频和高频部分的数据,分析结果的频率分辨率和小波树的深度有关,在使用的时候建议输入的数据个数最好为2的次方(如果不是2的次方重构后数据点数可能会和原始数据不同)。小波树节点的排序非常重要需要注意,在默认情况下并非由低频向高频排列。以深度为3的小波树为例,默认的节点排序为:[‘aaa’, ‘aad’, ‘ada’, ‘add’, ‘daa’, ‘dad’, ‘dda’, ‘ddd’],但是其对应频率由低到高的排序应为:[‘aaa’, ‘aad’, ‘add’, ‘ada’, ‘dda’, ‘ddd’, ‘dad’, ‘daa’]。程序中使用了一种判断方法,可以判断分析目标频率对应的小波参数,不足之处就是如果分析的目标频率的范围较低,受小波分析的分辨率限制,小波树的深度就要加深。具体小波深度的选取要结合采样频率,数据点数,分析目标频率的范围等因素综合考虑来确定。
对应的示例代码如下:
import numpy as np
import matplotlib.pyplot as plt
import pywt
import mne
# 需要分析的四个频段
iter_freqs = [
{'name': 'Delta', 'fmin': 0, 'fmax': 4},
{'name': 'Theta', 'fmin': 4, 'fmax': 8},
{'name': 'Alpha', 'fmin': 8, 'fmax': 13},
{'name': 'Beta', 'fmin': 13, 'fmax': 35},
]
plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
mne.set_log_level(False)
########################################小波包变换-重构造分析不同频段的特征(注意maxlevel,如果太小可能会导致)#########################
def TimeFrequencyWP(data, fs, wavelet, maxlevel = 8):
# 小波包变换这里的采样频率为250,如果maxlevel太小部分波段分析不到
wp = pywt.WaveletPacket(data=data, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
# 频谱由低到高的对应关系,这里需要注意小波变换的频带排列默认并不是顺序排列,所以这里需要使用’freq‘排序。
freqTree = [node.path for node in wp.get_level(maxlevel, 'freq')]
# 计算maxlevel最小频段的带宽
freqBand = fs/(2**maxlevel)
#######################根据实际情况计算频谱对应关系,这里要注意系数的顺序
# 绘图显示
fig, axes = plt.subplots(len(iter_freqs)+1, 1, figsize=(10, 7), sharex=True, sharey=False)
# 绘制原始数据
axes[0].plot(data)
axes[0].set_title('原始数据')
for iter in range(len(iter_freqs)):
# 构造空的小波包
new_wp = pywt.WaveletPacket(data=None, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
for i in range(len(freqTree)):
# 第i个频段的最小频率
bandMin = i * freqBand
# 第i个频段的最大频率
bandMax = bandMin + freqBand
# 判断第i个频段是否在要分析的范围内
if (iter_freqs[iter]['fmin']<=bandMin and iter_freqs[iter]['fmax']>= bandMax):
# 给新构造的小波包参数赋值
new_wp[freqTree[i]] = wp[freqTree[i]].data
# 绘制对应频率的数据
axes[iter+1].plot(new_wp.reconstruct(update=True))
# 设置图名
axes[iter+1].set_title(iter_freqs[iter]['name'])
plt.show()
if __name__ == '__main__':
# 读取筛选好的epoch数据
epochsCom = mne.read_epochs(r'epoch_raw.fif')
dataCom = epochsCom[0].get_data()[0][0]
TimeFrequencyWP(dataCom,250,wavelet='db4', maxlevel=8)
这里利用小波包的方法计算区间能量的累加和。
import numpy as np
import matplotlib.pyplot as plt
import pywt
import mne
# 需要分析的四个频段
iter_freqs = [
{'name': 'Delta', 'fmin': 0, 'fmax': 4},
{'name': 'Theta', 'fmin': 4, 'fmax': 8},
{'name': 'Alpha', 'fmin': 8, 'fmax': 13},
{'name': 'Beta', 'fmin': 13, 'fmax': 35},
]
plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
mne.set_log_level(False)
#############################################小波包计算四个频段的能量分布
def WPEnergy(data, fs, wavelet, maxlevel=6):
# 小波包分解
wp = pywt.WaveletPacket(data=data, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
# 频谱由低到高的对应关系,这里需要注意小波变换的频带排列默认并不是顺序排列,所以这里需要使用’freq‘排序。
freqTree = [node.path for node in wp.get_level(maxlevel, 'freq')]
# 计算maxlevel最小频段的带宽
freqBand = fs / (2 ** maxlevel)
# 定义能量数组
energy = []
# 循环遍历计算四个频段对应的能量
for iter in range(len(iter_freqs)):
iterEnergy = 0.0
for i in range(len(freqTree)):
# 第i个频段的最小频率
bandMin = i * freqBand
# 第i个频段的最大频率
bandMax = bandMin + freqBand
# 判断第i个频段是否在要分析的范围内
if (iter_freqs[iter]['fmin'] <= bandMin and iter_freqs[iter]['fmax'] >= bandMax):
# 计算对应频段的累加和
iterEnergy += pow(np.linalg.norm(wp[freqTree[i]].data, ord=None), 2)
# 保存四个频段对应的能量和
energy.append(iterEnergy)
# 绘制能量分布图
plt.plot([xLabel['name'] for xLabel in iter_freqs], energy, lw=0, marker='o')
plt.title('能量分布')
plt.show()
if __name__ == '__main__':
# 读取筛选好的epoch数据
epochsCom = mne.read_epochs(r'epoch_raw.fif')
dataCom = epochsCom[0].get_data()[0][0]
WPEnergy(dataCom, fs=250, wavelet='db4', maxlevel=6)