在语音与音乐处理过程中,常用到短时傅里叶变换(Short Time Fourier Transformation, STFT)。在一些学习路径中,STFT也是学习小波之前的预备知识。本文简单实现了 Matlab 中 Spectrogram 函数,在没有小波知识支撑下,讨论了参数的选择,以及分辨率相关的问题。

参考博客:

  来源:CSDN    作者:风翼冰舟
  来源:知乎       作者:咚懂咚懂咚
  来源:CSDN    作者:水可马二    来源:CSDN    作者:沈子恒    来源:CSDN    作者:gent__chen

短时傅里叶变换简介


  自己写一下对STFT的认识,不一定对。

  短时傅里叶变换,其实还是傅里叶变换,只不过把一段长信号按信号长度(nsc)、重叠点数(nov)重新采样。原始信号的每一个数据点,都有一个电压值对应,即只有一个直流自由度。重新采样之后,数据点个数变少,每一个数据点由(nsc)个点组成,即得到了其他频率的自由度。但是这个点的频谱是有上下限的——上限受采样频率界定,下限受数据时长界定,和 FFT 一样。

  这么处理数据是有缘由的。语音与音乐信号中,信号频率常常变化,而且频率成分丰富,导致简单的傅里叶变换不能很好的描述信号。然而我们人耳处理声音的能力很强,可以对很短的一段声音进行精确的频率分析。所以在语音识别以及语音信号预处理过程中,STFT 是 FFT 的仿生改良版(个人理解)。当然,在其他声学相关方向中,STFT也是蛮有用的。没查过相关文献不敢乱说,模态分析、瞬态特性等应该能用上(个人猜测)。

上图是我用本文的例程实现的语音“XX大学”的 STFT 结果。语音是自己拿录音笔录的,时间域上没有做截断,但由于自己声线太低,频率域截到 2000 Hz。可以看到,我在开始之后等了一段时间才开始说话,后面有几段“嘎嘣脆”的噪声。还可以看到,我的元音发音还是很清晰的,但声线真心低。理论上能从这张图还原出原始信号,不在话下。

Spectrogram 函数函数实现


  简单编了一个 STFT 函数如下:

function [ S , W , T ] = mf_spectrogram...
( signal , nsc , nov , fs )
%MF_SPECTROGRAM Short-time Fourier transform realization
% Input
% signal - Signal vector
% nsc - Abb. of Number SeCtion
% nov - Abb. of Number OverLap
% fs - Abb. of Frequency of Sample
% Output
% S - A matrix that each colum is a FFT for time of nsc
% W - A vector labeling frequency
% T - A vector labeling time % Signal Preprocessing
h = hamming(nsc, 'periodic'); % Hamming weight function
L = length(signal); % Length of Signal
nst = nsc-nov; % Number of STep per colum
ncl = fix( (L-nsc)/nst ) + 1; % Number of CoLum
nff = 2^nextpow2(nsc); % Number of Fast Fourier Transformation
Ps = zeros(nsc,ncl);
for n = 1:ncl % Ps means Processed Signal
Ps(:,n) = signal( (n-1)*nst+1 : (n-1)*nst+nsc ).*h';
end % Ps is a matrix % Short-time Fourier transform
STFT0 = fft(Ps,nff); % Turn into DFT in dB
STFT1 = abs(STFT0/nff);
STFT2 = STFT1(1:nff/2+1,:); % Symmetric results of FFT
STFT2(2:end-1,:) = 2*STFT2(2:end-1,:); % Add the value of the other half
STFT3 = 20*log10(STFT2); % Turn sound pressure into dB level % Axis Generating
fax = fs*(0:(nff/2))/nff; % Frequency axis setting
tax = ( .5*nsc : nst : nst*(ncl-1)+.5*nsc ) / fs; % Time axis generating
[ffax,ttax] = meshgrid(tax,fax); % Generating grid of figure % Output
W = ffax;
T = ttax;
S = STFT3;
end

  为了节省代码量,我搞了一个绘图函数:

function [  ] = my_pcolor( f , t , s )
%MY_PCOLOR 绘图函数
% Input
% f - 频率轴矩阵
% t - 时间轴矩阵
% s - 幅度轴矩阵
gca = pcolor(f,t,s); % 绘制伪彩色图
set(gca, 'LineStyle','none'); % 取消网格,避免一片黑
handl = colorbar; % 彩图坐标尺
set(handl, 'FontName', 'Times New Roman', 'FontSize', 14)
ylabel(handl, 'Magnitude, dB') % 坐标尺名称
end

  下面是实现的例程:

clc
clear
close all % 基本参数
fa = [ 50 800 ]; % 扫描频率上下限
fs = 6400; % 采样频率 % 分辨率相关设定参数
te = 1; % [s] 扫频时间长度
fre = 8; % [s] 频率分辨率
tre = 0.002; % [Hz] 时间分辨率 % Chirp 信号生成
t = 0:1/fs:te; % [s] 时间序列
sc = chirp(t,fa(1),te,fa(2)); % 信号生成 % 分辨率相关输入参数
nsc = floor(fs/fre);
% nov = floor(nsc-(fs*tre));
nov = floor(nsc*0.9);
nff = max(256,2^nextpow2(nsc)); % 计算与绘制结果
subplot(1,3,1) % 绘制自编函数结果
[S,W,T] = mf_spectrogram(sc,nsc,nov,fs);
my_pcolor(W,T,S)
caxis([-130.86,-13.667]);
title('自编函数');
xlabel('时间 second');
ylabel('频率 Hz');
subplot(1,3,2) % 绘制 Matlab 函数结果
s = spectrogram(sc,hamming(nsc),nov,nff,fs,'yaxis');
% Turn into DFT in dB
s1 = abs(s/nff);
s2 = s1(1:nff/2+1,:); % Symmetric results of FFT
s2(2:end-1,:) = 2*s2(2:end-1,:); % Add the value of the other half
s3 = 20*log10(s2); % Turn sound pressure into dB level
my_pcolor(W,T,s3 )
caxis([-130.86,-13.667]);
title('Matlab 自带函数');
xlabel('时间 second');
ylabel('频率 Hz');
subplot(1,3,3) % 两者误差
my_pcolor(W,T,20*log10(abs(10.^(s3/20)-10.^(S/20))))
caxis([-180,-13.667]);
title('error');
ylabel('频率 Hz');
xlabel('时间 second');
suptitle('Spectrogram 实现与比较');

  跑的结果我就不贴了,Demo确定是没问题的。网上也有很多相关话题的,例程都比较简单,但我非常善于把问题复杂化:te(扫描长度)、fre(频率下限)、tre(时域分辨率)、nsc(单段数据长度)、nov(重叠数据点数)、nff(FFT点数) 等参数都是为了看设定什么样的参数能够使得 STFT 结果最优。我只是科普性地了解过小波,公式推导还没展开,所以在此只能“实验性”地讨论“尺度”相关的话题。

  观察频率上限受到采样频率限制,频率下限受到nsc限制。nsc越大,单段数据时间跨度越长,在该段时间内频率如果变化快,会导致频域分辨率降低(te = 1 ; fre = 2 ; nov = 99%)。另一方面,为了保持 FFT 频率分辨率,设置 nff 不低于256,这使得当nsc较小时,单段信号中,低频成分可能也就几个周期就结束了,等同于时域加了一个矩形窗,最终造成了频域产生旁瓣。一句话,单段信号过短,低频效果不好;单段信号过长,捕捉不到频率快速变化的信号。

数据长度、FFT 点数对结果的影响


  实验性地比较了一下不同数据长度、FFT点数对结果的影响:

%  This script is demonstrating zero effects
% Basic parameter
fs = 100; % 采样频率
Ndata = [ 30 60 500 ]; % 数据长度
Nff = [ 32 64 512 ]; % FFT的数据长度 % Signal Generating
t1 = ( 0:Ndata(1)-1 )/fs;
t2 = ( 0:Ndata(2)-1 )/fs;
t3 = ( 0:Ndata(3)-1 )/fs;
x1 = 0.5*sin(2*pi*15*t1)+2*sin(2*pi*40*t1); % 时间域信号
x2 = 0.5*sin(2*pi*15*t2)+2*sin(2*pi*40*t2); % 时间域信号
x3 = 0.5*sin(2*pi*15*t3)+2*sin(2*pi*40*t3); % 时间域信号 % FFT and Plot
for n = 1:3 % Ndata sweep
xname = ['x',num2str(n)];
x = eval(xname);
for m = 1:3 % Nff sweep
name = ['y',num2str(n),num2str(m)];
eval([name '=fft(x,Nff(m));'])
y = eval(name);
Y = abs(y);
f = (0:Nff(m)-1)*fs/Nff(m); % 真实频率 subplot(3,3,(n-1)*3+m)
plot(f(1:Nff(m)/2),[Y(1),Y(2:Nff(m)/2)*2]/min(Ndata(n),Nff(m)));
xlabel('频率/Hz');
ylabel('振幅');
ylim([0,2]);
title(['Ndata=',num2str(Ndata(n)),' Nfft=',num2str(Nff(m))]);
grid on;
end
end

为了节省代码,同时运行结束后保留计算数据,使用了 eval 函数对变量和字符串进行相互转化。绘制了9个图如下:

横向是提高 FFT 点数的比较,纵向是提高数据长度的比较。可以看到,提高 FFT 点数会使得频域分辨率提高;增长数据长度,能够减少旁瓣的生成。


END

Matlab_spectrogram_短时傅里叶分析_实现与讨论的更多相关文章

  1. spectrogram函数做短时傅里叶分析

    整理自:http://blog.sina.com.cn/s/blog_6163bdeb0102dwfw.html 今天偶人发现原来matlab自带了短时傅里叶变换的分析函数,老版本的matlab是sp ...

  2. (4)_结果与讨论Result and Discussion【论文写作】

  3. Atitit 语音识别的技术原理

    Atitit 语音识别的技术原理 1.1. 语音识别技术,也被称为自动语音识别Automatic Speech Recognition,(ASR),2 1.2. 模型目前,主流的大词汇量语音识别系统多 ...

  4. 数据结构--树(遍历,红黑,B树)

    平时接触树还比较少,写一篇博文来积累一下树的相关知识. 很早之前在数据结构里面学的树的遍历. 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍 ...

  5. 手写一个更好用的performSelector/msgSend(详细修改版)

    这其实是一个NSInvocation练习作业 GitHub源码 vk_msgSend 引子 工作中难免会遇到一些场景,开发的时候不想引入整个头文件,但是又想调用一些方法 动态创建,动态调用看起来比较酷 ...

  6. Atitit.木马 病毒 免杀 技术 360免杀 杀毒软件免杀 原理与原则 attilax 总结

    Atitit.木马 病毒 免杀 技术 360免杀 杀毒软件免杀 原理与原则 attilax 总结 1. ,免杀技术的用途2 1.1. 病毒木马的编写2 1.2. 软件保护所用的加密产品(比如壳)中,有 ...

  7. C语言ASM汇编内嵌语法【转】

    转自:http://www.cnblogs.com/latifrons/archive/2009/09/17/1568198.html GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作G ...

  8. 【DWT笔记】傅里叶变换与小波变换

    [DWT笔记]傅里叶变换与小波变换 一.前言 我们经常接触到的信号,正弦信号,余弦信号,甚至是复杂的心电图.脑电图.地震波信号都是时域上的信号,我们也成为原始信号,但是通常情况下,我们在原始信号中得到 ...

  9. 【codeforces 553E】 Kyoya and Train

    http://codeforces.com/problemset/problem/553/E (题目链接) 艹尼玛,CF还卡劳资常数w(゚Д゚)w!!系统complex被卡TLE了T_T,劳资写了一天 ...

随机推荐

  1. 使用POST请求实现页面的跳转

    项目情景: 当用户选择几个item之后,点击 查看 按钮之后, 页面跳转到展示items详情页面. 实现: 如果可以使用get请求, 直接在前端使用windows.loaction.href = &q ...

  2. SWUST OJ(1102)

    顺序表上数据的划分问题的实现 #include <iostream> #include <cstdlib> using namespace std; int main() { ...

  3. .NET+MySql 踩坑1

    换成MySql数据库后,遇到的问题: 已解决,但不理解的问题: var test = db.test; 报如下图错误: 加上DefaultIfEmpty()则解决. var test = db.Tes ...

  4. 【OS】Process & Thread

      Process Thread 定义 资源(CPU.内存等)分配的最小单元,是程序执行时的一个实例.程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时 ...

  5. 解决反射型XSS漏洞攻击

    对于程序员来说安全防御,无非从两个方面考虑,要么前端要么后台. 一.首先从前端考虑过滤一些非法字符. 前端的主控js中,在<textarea> 输入框标签中,找到点击发送按钮后,追加到聊天 ...

  6. mysql 没有全外连接

    真实测试过,没有测试过的别再坑人了.别随便乱写了.

  7. gevent模块学习(二)

    2. Queue类,常用用于Greenlet之间的异步共享 q = gevent.queue.Queue(maxsize=None, items=None) -> Queue 说明: 创建一个指 ...

  8. CouchDB客户端开发—Java版

    在Fedora上安装CouchDB: yum update yum install couchdb 修改/etc/couchdb下local.ini文件: port = 5984bind_addres ...

  9. 使用JS判断不同的终端设备

    const ua: string = window.navigator.userAgent; const isWeixin: boolean = /MicroMessenger/i.test(ua); ...

  10. Win10系列:C#应用控件进阶9

    RectangleGeometry 在使用RectangleGeometry控件绘制矩形时,矩形的位置和尺寸由Rect属性定义,该属性指定矩形的相对位置.高度和宽度.Rect有四个参数,前两个参数表示 ...