【音频处理】短时傅里叶变换

前言

上一篇博客讲了离散傅里叶变换,里面的实例是对整个信号进行计算,虽然理论上有N点傅里叶变换(本博客就不区分FFT和DFT了,因为它俩就是一个东东,只不过复杂度不同),但是我个人理解是这个N点是信号前面连续的N个数值,即N点FFT意思就是截取前面N个信号进行FFT,这样就要求我们的前N个采样点必须包含当前信号的一个周期,不然提取的余弦波参数与正确的叠加波的参数相差很大。

如果在N点FFT的时候,如果这N个采样点不包含一个周期呢?或者说我们的信号压根不是一个周期函数咋办?或者有一段是噪音数据呢?如果用FFT计算,就会对整体结果影响很大,然后就有人想通过局部来逼近整体,跟微积分的思想很像,将信号分成一小段一小段,然后对每一小段做FFT,就跟分段函数似的,无数个分段函数能逼近任意的曲线((⊙o⊙)…应该没错吧),这样每一段都不会互相影响到了。

下面的参考博客中有一篇的一句话很不错:在短时傅里叶变换过程中,窗的长度决定频谱图的时间分辨率和频率分辨率,窗长越长,截取的信号越长,信号越长,傅里叶变换后频率分辨率越高,时间分辨率越差;相反,窗长越短,截取的信号就越短,频率分辨率越差,时间分辨率越好,也就是说短时傅里叶变换中,时间分辨率和频率分辨率之间不能兼得,应该根据具体需求进行取舍。

国际惯例,参考博客:

基于MATLAB短时傅里叶变换和小波变换的时频分析

小波前奏–短时傅里叶变换

短时傅里叶变换原理解

matlab自带的短时傅里叶变换函数spectrogram

理论及实现

其实就是多了几个参数,需要指定的有:

  • 每个窗口的长度:nsc
  • 每相邻两个窗口的重叠率:nov
  • 每个窗口的FFT采样点数:nff

可以计算的有:

  • 海明窗:w=hamming(nsc, 'periodic')

  • 信号被分成了多少片:len(S)−nscnsc−nov\frac{len(S)-nsc}{nsc-nov}nscnovlen(S)nsc

  • 短时傅里叶变换:
    X(k)=∑n=1Nw(n)∗x(n)∗exp(−j∗2π∗(k−1)∗(n−1)/N),1<=k<=NX(k) = \sum_{n=1}^N w(n)* x(n)*exp(-j*2\pi*(k-1)*(n-1)/N), 1 <= k <= N X(k)=n=1Nw(n)x(n)exp(j2π(k1)(n1)/N),1<=k<=N
    其实和FFT的公式一样,只不过多了个海明窗加权

直接撸代码:

①先设置参数:

%默认设置:
% nsc=floor(L/4.5);%海明窗的长度
% nov=floor(nsc/2);%重叠率
% nff=max(256,2^nextpow2(nsc));%N点采样长度
%也可手动设置
nsc=100;%海明窗的长度,即每个窗口的长度
nov=30;%重叠率
nff=256;%N点采样长度

这里面有个默认设置,就是调用matlab自带的短时傅里叶变换时,如果没指定相关参数,就会采用默认参数值,这个可以去mathwork官网看。

②计算海明窗以及初始化结果值:

h=hamming(nsc, 'periodic');%计算海明窗的数值,给窗口内的信号加权重
coln = 1+fix((L-nsc)/(nsc-nov));%信号被分成了多少个片段
%如果nfft为偶数,则S的行数为(nfft/2+1),如果nfft为奇数,则行数为(nfft+1)/2
%因为matlab的FFT结果是对称的,只需要一半
rown=nff/2+1;
STFT_X=zeros(rown,coln);%初始化最终结果

这里的信号被划分的片段数目可以按照卷积的方法计算

③对每个片段码公式:

%对每个片段进行fft变换
index=1;%当前片段第一个信号位置在原始信号中的索引
for i=1:coln%提取当前片段信号值,并用海明窗进行加权temp_S=S(index:index+nsc-1).*h';%进行N点FFT变换temp_X=fft(temp_S,nff);%取一半STFT_X(:,i)=temp_X(1:rown)';%将索引后移index=index+(nsc-nov);
end

可以发现我这里没码公式,因为上一篇博客证明了手撸的DFT与matlab自带的FFT公式一样,有高度强迫症的可以把上一篇博客的DFT写成一个函数,然后把此处的FFT换成你的函数名即可。注意这里的关键操作有两点:

  • 对当前窗口的输入信号进行海明加权
  • 窗口中输入信号的获取方法有点类似于卷积,卷积核大小是1*nsc,步长是nsc-nov

④正确性验证:与matlab自带的STFT函数spectrogram的结果进行比较:

%% matlab自带函数
[spec_s,spec_f,spec_t]=spectrogram(S,hamming(nsc, 'periodic'),nov,nff,Fs);
%减法,看看差距
plot(abs(spec_s)-abs(STFT_X))

这里写图片描述

啥也不说了,稳如狗

频谱解读

直接计算每个坐标轴的数值就知道其代表的意思了,其实它和一款叫做Praat的软件所画的图很类似,贴一张图,来源百度

这里写图片描述

上面是声线,就是直接audioread声音得到的数值,下面就是声谱图,看着是二维图形,其实是3D图形,坐标轴分别代表时间,频率,以及当前时间在当前频率上的幅值。

那么在matlab中如何计算这些值?如下:

回顾一下整个快速离散傅里叶变换的流程:

  • 利用窗口和重叠率对整个输入信号进行片段划分
  • 对每个片段的信号做N点傅里叶变换,并利用海明窗加权

接下来解析三个坐标,先规定一下横坐标表示频率,纵坐标表示时间,第三个坐标表示幅值:

  • 第三个坐标:幅值的计算与FFT的幅值计算一样,都是计算得到的结果除以采样点N,再乘以2,只不过这里要利用海明值进行缩放处理

    % 海明窗口的均值
    K = sum(hamming(nsc, 'periodic'))/nsc;
    %获取幅值(除以采样长度即可,后面再决定那几个除以2),并依据窗口的海明系数缩放
    STFT_X = abs(STFT_X)/nsc/K;
    % correction of the DC & Nyquist component
    if rem(nff, 2)                     % 如果是奇数,说明第一个是奈奎斯特点,只需对2:end的数据乘以2STFT_X(2:end, :) = STFT_X(2:end, :).*2;
    else                                % 如果是偶数,说明首尾是奈奎斯特点,只需对2:end-1的数据乘以2STFT_X(2:end-1, :) = STFT_X(2:end-1, :).*2;
    end
    
  • 横坐标:频率
    首先要知道当前窗口代表了多少频率,它是在总频率Fs的基础上,对每个窗口进行nff点采样,需要注意的是进行多少次nff采样,当前窗口就有多少个频率值,它们之间是均匀的Fs/nff,这个也通常被称作频率到分辨率的比率。这里看论文《Constant-Q transform toolbox for music processing 》中的一句话:

    The discrete short-time Fourier transform gives a constant resolution for each bin or frequency sampled equal to the sampling rate dividedbythewindowsizein samples. This means,for example,if we takea window of 1024 samples with a sampling rate of 32O30 samples/s (reasonable for musical signals),there solution is 31.3Hz
    

    就是说我们从采样率为32030Hz的样本中挑选包含1024个样本的窗口,那么分辨率就是320301024=31.3Hz\frac{32030}{1024}=31.3Hz102432030=31.3Hz

    所以横坐标的值就是i×Fsnff,(i∈(0,nff))i\times \frac{Fs}{nff},(i\in(0,nff))i×nffFs,(i(0,nff))

    %频率轴
    STFT_f=(0:rown-1)*Fs./nff;
    
  • 纵坐标时间:
    因为采用了窗口,所以纵坐标的时间比实际时间短,每个坐标代表当前窗口中间信号在原始信号中的索引,窗口是nsc,重叠率是nov,那么每次向前挪的步长为nsc-nov,总共挪coln-1次,需要注意的是这个挪是在采样点上挪,需要将采样点挪转换为时间挪,由于整个信号采样率是Fs,代表每秒的采样数,那么相邻的采样点时间间隔是1/Fs,自然就得到了纵坐标的刻度:

    %时间轴
    STFT_t=(nsc/2:(nsc-nov):nsc/2+(coln-1)*(nsc-nov))/Fs;
    
  • 额外信息:赋值转分贝
    我也不清楚这个意义是啥,但是在matlab中有对应函数,而且软件praat和默认函数spectrogram的结果图中都有这个信息,所以还是算一下吧,公式很简单y=20∗log⁡10(x)y=20*\log10(x)y=20log10(x),直接码公式:

    STFT_X = 20*log10(STFT_X + 1e-6);
    

    这里加个很小的值以后,画图好看点

坐标值都得到,直接mesh出来就行,整个代码如下:

%% 画频谱图
% 海明窗口的均值
K = sum(hamming(nsc, 'periodic'))/nsc;
%获取幅值(除以采样长度即可,后面再决定哪几个除以2),并依据窗口的海明系数缩放
STFT_X = abs(STFT_X)/nsc/K;
% correction of the DC & Nyquist component
if rem(nff, 2)                     % 如果是奇数,说明第一个是奈奎斯特点,只需对2:end的数据乘以2STFT_X(2:end, :) = STFT_X(2:end, :).*2;
else                                % 如果是偶数,说明首尾是奈奎斯特点,只需对2:end-1的数据乘以2STFT_X(2:end-1, :) = STFT_X(2:end-1, :).*2;
end% convert amplitude spectrum to dB (min = -120 dB)
%将幅值转换成分贝有函数是ydb=mag2db(y),这里直接算
STFT_X = 20*log10(STFT_X + 1e-6);
%时间轴
STFT_t=(nsc/2:(nsc-nov):nsc/2+(coln-1)*(nsc-nov))/Fs;
%频率轴
STFT_f=(0:rown-1)*Fs./nff;
% plot the spectrogram
figure
surf(STFT_f, STFT_t,  STFT_X')
shading interp
axis tight
box on
view(0, 90)
set(gca, 'FontName', 'Times New Roman', 'FontSize', 14)
xlabel('Frequency, Hz')
ylabel('Time, s')
% title('Amplitude spectrogram of the signal')
title('手绘图')handl = colorbar;
set(handl, 'FontName', 'Times New Roman', 'FontSize', 14)
ylabel(handl, 'Magnitude, dB')

对比一下matlab自带函数spectrogram和我们手绘图的正面图和3D图:

这里写图片描述

这里写图片描述

从图里面很容易看出来咱们输入的这个音频信号由50和100两个频率组成,幅值大概在10到20,what?咋少了一半,(⊙o⊙)…,后面再研究为啥少了一半还,按理说乘以2了呀,反正频率对了

后记

感觉对于FFT的理解告一段落,先把蝶形算法搁着,下一步就是折腾常Q变换(Constant-Q transform)了,目前的用处是一个音乐的一拍可能有很多音组合而成,但是每个音的频率又不一样,那么就需要设置不同的窗口进行采样,相当于进行了多次STFT操作,只不过每次的窗口大小不同罢了,有兴趣可以看一波论文:《Calculation of a constant Q spectral transform 》,有张图介绍了CQT和DFT的区别,具体我还在研究,感觉就是把STFT的for循环里面的N变成动态计算的就行了。

这里写图片描述

贴一波代码,直接贴了,懒得贴网盘:

%短时傅里叶变换STFT
%依据FFT手动实现STFT
clear
clc
close all
Fs = 1000;            % Sampling frequency
T = 1/Fs;             % Sampling period
L = 1000;             % Length of signal
t = (0:L-1)*T;        % Time vector
S = 20*cos(100*2*pi*t)+40*cos(50*2*pi*t);%0.2-0.7*cos(2*pi*50*t+20/180*pi) + 0.2*cos(2*pi*100*t+70/180*pi) ;%% 所需参数
%主要包含:信号分割长度(默认分割8个窗口),海明窗口,重叠率,N点采样
%默认设置:
% nsc=floor(L/4.5);%海明窗的长度
% nov=floor(nsc/2);%重叠率
% nff=max(256,2^nextpow2(nsc));%N点采样长度
%也可手动设置
nsc=100;%海明窗的长度,即每个窗口的长度
nov=0;%重叠率
nff=max(256,2^nextpow2(nsc));%N点采样长度
%% 手动实现STFT
h=hamming(nsc, 'periodic');%计算海明窗的数值,给窗口内的信号加权重
coln = 1+fix((L-nsc)/(nsc-nov));%信号被分成了多少个片段
%如果nfft为偶数,则S的行数为(nfft/2+1),如果nfft为奇数,则行数为(nfft+1)/2
%因为matlab的FFT结果是对称的,只需要一半
rown=nff/2+1;
STFT_X=zeros(rown,coln);%初始化最终结果
%对每个片段进行fft变换
index=1;%当前片段第一个信号位置在原始信号中的索引
for i=1:coln%提取当前片段信号值,并用海明窗进行加权temp_S=S(index:index+nsc-1).*h';%进行N点FFT变换temp_X=fft(temp_S,nff);%取一半STFT_X(:,i)=temp_X(1:rown)';%将索引后移index=index+(nsc-nov);
end%% matlab自带函数
spectrogram(S,hamming(nsc, 'periodic'),nov,nff,Fs);
title('spectrogram函数画图')
[spec_X,spec_f,spec_t]=spectrogram(S,hamming(nsc, 'periodic'),nov,nff,Fs);
%减法,看看差距
% plot(abs(spec_X)-abs(STFT_X))%% 画频谱图
% 海明窗口的均值
K = sum(hamming(nsc, 'periodic'))/nsc;
%获取幅值(除以采样长度即可,后面再决定哪几个乘以2),并依据窗口的海明系数缩放
STFT_X = abs(STFT_X)/nsc/K;
% correction of the DC & Nyquist component
if rem(nff, 2)                     % 如果是奇数,说明第一个是奈奎斯特点,只需对2:end的数据乘以2STFT_X(2:end, :) = STFT_X(2:end, :).*2;
else                                % 如果是偶数,说明首尾是奈奎斯特点,只需对2:end-1的数据乘以2STFT_X(2:end-1, :) = STFT_X(2:end-1, :).*2;
end% convert amplitude spectrum to dB (min = -120 dB)
%将幅值转换成分贝有函数是ydb=mag2db(y),这里直接算
STFT_X = 20*log10(STFT_X + 1e-6);
%时间轴
STFT_t=(nsc/2:(nsc-nov):nsc/2+(coln-1)*(nsc-nov))/Fs;
%频率轴
STFT_f=(0:rown-1)*Fs./nff;
% plot the spectrogram
figure
surf(STFT_f, STFT_t,  STFT_X')
shading interp
axis tight
box on
view(0, 90)
set(gca, 'FontName', 'Times New Roman', 'FontSize', 14)
xlabel('Frequency, Hz')
ylabel('Time, s')
title('Amplitude spectrogram of the signal')handl = colorbar;
set(handl, 'FontName', 'Times New Roman', 'FontSize', 14)
ylabel(handl, 'Magnitude, dB')

博客已同步更新到微信公众号中,有问题可以在微信公众号中私聊,或者在csdn博客下面评论。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【theano-windows】学习笔记十九——循环神经网络

前言 前面已经介绍了RBM和CNN了&#xff0c;就剩最后一个RNN了&#xff0c;抽了一天时间简单看了一下原理&#xff0c;但是没细推RNN的参数更新算法BPTT&#xff0c;全名是Backpropagation Through Time。 【注】严谨来说RNN有两个称呼&#xff1a;①结构上递归的recursive n…

【theano-windows】学习笔记二十——LSTM理论及实现

前言 上一篇学习了RNN&#xff0c;也知道了在沿着时间线对上下文权重求梯度的时候&#xff0c;可能会导致梯度消失或者梯度爆炸&#xff0c;然后我们就得学习一波比较常见的优化方法之LSTM 国际惯例&#xff0c;参考网址&#xff1a; LSTM Networks for Sentiment Analysis …

刚体运动学——欧拉角、四元数、旋转矩阵

前言 刚体运动旋转一般用&#xff1a;欧拉角、四元数、轴角对等表示&#xff0c;在对某个坐标旋转的时候&#xff0c;只需将欧拉角或四元数转换为旋转矩阵&#xff0c;并与原始坐标相乘&#xff0c;便可得到旋转以后的坐标。这里主要看看欧拉角、四元数和旋转矩阵。 国际惯例…

刚体运动学-四元数插值

前言 之前对写了一篇关于刚体运动学相关知识博客&#xff1a;刚体运动学——欧拉角、四元数、旋转矩阵&#xff0c;本篇博客就举例来说明&#xff0c;如何在运动捕捉数据中进行四元数插值。 国际惯例&#xff0c;参考博客&#xff1a; 探讨&#xff1a;向量&#xff08;方向…

【TensorFlow-windows】学习笔记一——基础理解

前言 因为Theano已经停止更新了&#xff0c;所以在前面学完Theano搭建RBM,CNN,RNN相关结构以后&#xff0c;还是得选择一个主流框架的&#xff0c;由于我自身的学习最终是向强化学习靠近&#xff0c;可能用到的仿真环境是openai gym&#xff0c;所以选择了继续学习TensorFlow&…

【TensorFlow-windows】学习笔记二——低级API

前言 上一篇博客初步了解了tensorflow中建立机器学习模型的方法&#xff1a;可以使用eager execution和graph execution两种模式&#xff0c;可以使用高级API estimator中已经封装好的模型&#xff0c;也可以自己创建estimator&#xff0c;更重要的是我们也可以使用低级API自行…

【TensorFlow-windows】学习笔记五——自编码器

前言 上一篇博客介绍的是构建简单的CNN去识别手写数字&#xff0c;这一篇博客折腾一下自编码&#xff0c;理论很简单&#xff0c;就是实现对输入数据的重构&#xff0c;具体理论可以看我前面的【theano-windows】学习笔记十三——去噪自编码器 国际惯例&#xff0c;参考博客&…

【TensorFlow-windows】学习笔记六——变分自编码器

#前言 对理论没兴趣的直接看代码吧&#xff0c;理论一堆&#xff0c;而且还有点复杂&#xff0c;我自己的描述也不一定准确&#xff0c;但是代码就两三句话搞定了。 国际惯例&#xff0c;参考博文 论文&#xff1a;Tutorial on Variational Autoencoders 【干货】一文读懂…

【TensorFlow-windows】学习笔记七——生成对抗网络

前言 既然学习了变分自编码(VAE)&#xff0c;那也必须来一波生成对抗网络(GAN)。 国际惯例&#xff0c;参考网址&#xff1a; 论文: Generative Adversarial Nets PPT:Generative Adversarial Networks (GANs) Generative Adversarial Nets in TensorFlow GAN原理学习笔记…

Openpose——windows编译(炒鸡简单)

前言 最近准备看看rtpose的代码&#xff0c;发现已经由openpose这个项目维护着了&#xff0c;由于经常在windows下调试代码&#xff0c;所以尝试了一下如何在windows下编译openpose源码&#xff0c;整体来说非常简单的。 国际惯例&#xff0c;参考博客&#xff1a; [OpenPos…

强化学习——Qlearning

前言 在控制决策领域里面强化学习还是占很重比例的&#xff0c;最近出了几篇角色控制的论文需要研究&#xff0c;其中部分涉及到强化学习&#xff0c;都有开源&#xff0c;有兴趣可以点开看看&#xff1a; A Deep Learning Framework For Character Motion Synthesis and Edit…

【TensorFlow-windows】keras接口学习——线性回归与简单的分类

前言 之前有写过几篇TensorFlow相关文章&#xff0c;但是用的比较底层的写法&#xff0c;比如tf.nn和tf.layers&#xff0c;也写了部分基本模型如自编码和对抗网络等&#xff0c;感觉写起来不太舒服&#xff0c;最近看官方文档发现它的教程基本都使用的keras API&#xff0c;这…

【TensorFlow-windows】keras接口——卷积手写数字识别,模型保存和调用

前言 上一节学习了以TensorFlow为底端的keras接口最简单的使用&#xff0c;这里就继续学习怎么写卷积分类模型和各种保存方法(仅保存权重、权重和网络结构同时保存) 国际惯例&#xff0c;参考博客&#xff1a; 官方教程 【注】其实不用看博客&#xff0c;直接翻到文末看我的c…

【TensorFlow-windows】keras接口——BatchNorm和ResNet

前言 之前学习利用Keras简单地堆叠卷积网络去构建分类模型的方法&#xff0c;但是对于很深的网络结构很难保证梯度在各层能够正常传播&#xff0c;经常发生梯度消失、梯度爆炸或者其它奇奇怪怪的问题。为了解决这类问题&#xff0c;大佬们想了各种办法&#xff0c;比如最原始的…

【TensorFlow-windows】keras接口——卷积核可视化

前言 在机器之心上看到了关于卷积核可视化相关理论&#xff0c;但是作者的源代码是基于fastai写的&#xff0c;而fastai的底层是pytorch&#xff0c;本来准备自己用Keras复现一遍的&#xff0c;但是尴尬地发现Keras还没玩熟练&#xff0c;随后发现了一个keras-vis包可以用于做…

【TensorFlow-windows】投影变换

前言 没什么重要的&#xff0c;就是想测试一下tensorflow的投影变换函数tf.contrib.image.transform中每个参数的含义 国际惯例&#xff0c;参考文档 官方文档 描述 调用方法与默认参数&#xff1a; tf.contrib.image.transform(images,transforms,interpolationNEAREST,…

【TensorFlow-windows】扩展层之STN

前言 读TensorFlow相关代码看到了STN的应用&#xff0c;搜索以后发现可替代池化&#xff0c;增强网络对图像变换(旋转、缩放、偏移等)的抗干扰能力&#xff0c;简单说就是提高卷积神经网络的空间不变性。 国际惯例&#xff0c;参考博客&#xff1a; 理解Spatial Transformer…

【TensorFlow-windows】MobileNet理论概览与实现

前言 轻量级神经网络中&#xff0c;比较重要的有MobileNet和ShuffleNet&#xff0c;其实还有其它的&#xff0c;比如SqueezeNet、Xception等。 本博客为MobileNet的前两个版本的理论简介与Keras中封装好的模块的对应实现方案。 国际惯例&#xff0c;参考博客&#xff1a; 纵…

【TensorFlow-windows】keras接口——ImageDataGenerator裁剪

前言 Keras中有一个图像数据处理器ImageDataGenerator&#xff0c;能够很方便地进行数据增强&#xff0c;并且从文件中批量加载图片&#xff0c;避免数据集过大时&#xff0c;一下子加载进内存会崩掉。但是从官方文档发现&#xff0c;并没有一个比较重要的图像增强方式&#x…

【TensorFlow-windows】TensorBoard可视化

前言 紧接上一篇博客&#xff0c;学习tensorboard可视化训练过程。 国际惯例&#xff0c;参考博客&#xff1a; MNIST机器学习入门 Tensorboard 详解&#xff08;上篇&#xff09; Tensorboard 可视化好帮手 2 tf-dev-summit-tensorboard-tutorial tensorflow官方mnist_…