【微实验】MATLAB频谱 三维可视化

这篇纯属是为了学习一下傅里叶变换过程然后更好看地呈现。

虽然看语谱图AU已经非常好用了,关于语谱图的介绍,可以去看这篇文章:【超详细】Cubase扒谱引出的语音学探讨-CSDN博客

关于频率分量的强度,语谱图是使用颜色亮暗来区分的,但是人类对颜色的感知毕竟不如空间维度更加直接,因此,扒谱机我考虑使用MATLAB编写代码,将语谱图进行三维可视化。

话不多说,开始AI。

(下文记录的是代码调试修改的过程,最终版的代码附在文章最后)

示范音频为截取自25高考英语听力的"现在是听力试音时间",它的频谱图是这样子的:

而到了三维,你会发现呈现效果并不是非常理想:
首先是这里,在音频末尾有幅度非常小的一系列数,拉长了坐标系显示范围

观察STFT的数组,发现确实这里有四行十分统一且都很小的数,如下图红框所示。

AI告诉我,这是因为:

  • STFT 使用滑动窗口处理信号,当窗口滑动到信号末尾时,窗口内的信号会变得不完整
  • 为了处理这种情况,MATLAB 的stft函数会自动进行零填充,导致末端出现小值

如果这些小值影响了可视化效果,可以通过以下几种方式处理:

方法 1:调整可视化范围

最简单的方法是在绘图时限制 z 轴(幅度)的显示范围:

Matlab 复制代码
% 在三维绘图代码中添加以下行
zlim([-100 100]);  % 设置幅度显示范围,根据实际情况调整

效果并不好,因为在频谱的较高频段存在幅值很低的区域,限制z轴显示也会把这些去掉

方法 2:修改 STFT 参数

可以通过调整 STFT 的参数来减少边界效应的影响:

复制代码
% 增加FFT点数或调整窗口大小
nfft = 4096;  % 增加FFT点数
window = hann(2048);  % 增大窗口大小
noverlap = 1536;  % 相应调整重叠样本数

似乎没什么本质区别,就是算得慢了一些。

方法 3:手动裁剪结果

如果确定末尾的小值都是无效数据,可以直接裁剪结果:

Matlab 复制代码
% 假设您想保留到3秒处
cutoffTime = 3.0;  % 设定裁剪时间点
idx = find(t <= cutoffTime);  % 找到小于等于该时间点的索引

% 裁剪数据
t = t(idx);
% f = f(idx);这句是AI生成有误,频率是另一个维度,无需裁剪,否则会报错
stftDB = stftDB(:, idx);

% 重新绘图
surf(t, f, stftDB, 'EdgeColor', 'none');

又稍微改了下代码,针对我音频文件的时长,掐头去尾,效果不错。

cutoffTime1 = 2.9722; % 结束时间点

cutoffTime2 = 0.0348; % 开始时间点

从这个视角上观察,可以发现,携带有效信息的幅值基本上都-50dB以上,它以下的,是不是可以才进行适当的变换裁剪掉?或者说进行适度地压缩,把第三维的对比度留给携带信息更多的幅值部分?

一开始直接把-50dB以下的压平了,所以考虑了非线性压缩,实验一下:
怎么说呢......一言难尽

低频处一大片都是红红的,有点难绷......

因为音频信号的低频能量通常远高于高频,这会导致三维图中低频部分 "挤压" 高频部分的显示空间。对数频率轴可以有效解决这个问题,同时还能更符合人耳的听觉感知特性(人耳对频率的感知本身就是对数的)。
频域上的分辨率是可以了,但是颜色方面更难绷了

另外这个时候看低频分得很开,看高频的信息并不方便,借鉴一下AU中的功能:

AU 中语谱图通过滚轮切换放大低频 / 高频的核心原理是动态调整频率轴的显示范围 (即 "聚焦" 不同频率区间),本质是通过改变频率轴的上下限(ylim)来放大特定频段,而非修改数据本身。这种交互本质是 "视野缩放",就像用放大镜在不同区域移动,数据不变但显示的局部范围变化。

AI生成代码报错遂放弃,毕竟本人是触控板党)笑死

最终为了照顾一下非常难绷的颜色,没有使用频率对数坐标。

把显示的颜色最大值加30,看起来就更清晰了。

另外发现了一个很有意思的现象------
红色箭头所指的频率带,有一条明显的沟

笔者怀疑,它和MP3的压缩有关。

MP3 压缩是一种 "感知编码",它基于人类听觉系统的特性,去除 "听不到" 的信息:

  1. 频率掩蔽效应

    • 强频率成分会掩盖相邻弱频率成分
    • MP3 会去除这些被掩盖的弱频率成分
  2. 临界频带

    • 人耳对 2-5kHz 频率最敏感
    • 对低频(<200Hz)和高频(>5kHz)敏感度较低
    • MP3 会优先保留敏感频段的信息
  3. 量化噪声

    • 对不重要的频率成分使用较粗糙的量化
    • 导致这些区域的频率信息丢失

实验用的音频本来就是mp3格式的,因此无法验证原音,也有可能是录音设备的问题(笔者的手机录音就有明显缺失的频带)

挖个坑,明天更新关于MP3压缩痕迹观测的实验。
用AU显示语谱图是这样子的 MATLAB却是这样子的

或许......只是聚焦范围不同而已,你看,把AU的频率聚焦范围狠狠往上调也是这个样子------

说好的后面附加代码:

Matlab 复制代码
% 音频短时傅里叶变换(STFT)的三维可视化
% 功能:读取音频文件,计算STFT,支持开关非线性压缩功能,自动裁剪前后边缘帧

clear; clc; close all;

%% 1. 读取音频文件
audioFile = 'audio_sample.wav';  % 替换为你的音频文件路径
[signal, fs] = audioread(audioFile);

% 转换为单声道
if size(signal, 2) > 1
    signal = mean(signal, 2);
end

%% 2. 设置STFT参数
window = hann(1024);  % 汉宁窗
noverlap = 768;       % 重叠样本数(75%)
nfft = 2048;          % FFT点数

%% 3. 计算短时傅里叶变换
[stftMagnitude, f, t] = stft(signal, fs, ...
    'Window', window, ...
    'OverlapLength', noverlap, ...
    'FFTLength', nfft);

% 转换为分贝值
stftDB = 20 * log10(abs(stftMagnitude) + eps);

%% 4. 非线性压缩(可开关)
enableNonlinearCompression = false; % true=启用,false=禁用
threshold = -50;      % 有效信息阈值
compress_strength = 7; % 压缩强度

if enableNonlinearCompression
    stft_compressed = stftDB;
    low_values = stftDB < threshold;
    % 非线性压缩公式
    stft_compressed(low_values) = threshold - compress_strength * ...
        log(1 + exp((threshold - stftDB(low_values))/compress_strength));
    compression_status = '启用'; % 状态文本
else
    stft_compressed = stftDB;
    compression_status = '禁用'; % 状态文本
end

%% 5. 时间裁剪(根据前后各4帧自动去除)
% 获取总帧数
total_frames = length(t);
% 设置要去除的前后帧数(可调整)
frames_to_remove = 4;

% 计算有效帧范围(避免索引越界)
start_frame = frames_to_remove + 1;
end_frame = total_frames - frames_to_remove;

% 确保索引有效
if start_frame >= end_frame
    warning('要去除的帧数过多,已自动调整为保留中间1帧');
    start_frame = floor(total_frames/2);
    end_frame = start_frame;
end

% 裁剪数据
t = t(start_frame:end_frame);
stft_compressed = stft_compressed(:, start_frame:end_frame);

% 显示裁剪信息
disp(['自动裁剪:去除前', num2str(frames_to_remove), '帧和后', num2str(frames_to_remove), '帧']);
disp(['裁剪后保留的帧数:', num2str(length(t))]);

%% 6. 三维可视化
figure('Name', '三维语谱图', 'Position', [100 100 1000 800]);
surf(t, f, stft_compressed, 'EdgeColor', 'none');
colormap('jet');
colorbar;
caxis([min(stft_compressed(:)) max(stft_compressed(:))+30]);

xlabel('时间 (s)', 'FontSize', 12);
ylabel('频率 (Hz)', 'FontSize', 12);
zlabel('幅度 (dB)', 'FontSize', 12);
title(['非线性压缩', compression_status, '后的三维语谱图'], 'FontSize', 14);
view(30, 60);
xlim([t(1) t(end)]);
ylim([0 fs/2]);
grid on;
box on;
set(gca, 'FontSize', 10);

%% 7. 二维语谱图
figure('Name', '二维语谱图', 'Position', [200 200 1000 600]);
imagesc(t, f, stft_compressed);
axis xy;
colormap('jet');
colorbar;
caxis([min(stft_compressed(:)) max(stft_compressed(:))]);

xlabel('时间 (s)', 'FontSize', 12);
ylabel('频率 (Hz)', 'FontSize', 12);
ylim([0 fs/2]);

title(['非线性压缩', compression_status, '后的二维语谱图'], 'FontSize', 14);
box on;
set(gca, 'FontSize', 10);

%% 8. 输出信息
disp('语谱图绘制完成!');
disp(['非线性压缩: ', compression_status]);
disp(['压缩阈值: ', num2str(threshold), ' dB']);
disp(['采样率: ', num2str(fs), ' Hz']);
相关推荐
2zcode24 分钟前
基于Matlab的聚类彩色图像分割系统
开发语言·matlab·聚类
freexyn1 小时前
Matlab算法编程示例4:数值解法求解常微分方程的代码实例
人工智能·算法·matlab·微分方程·数值解法·算法代码
没有梦想的咸鱼185-1037-16631 天前
MATLAB科研数据可视化技术
开发语言·机器学习·matlab·信息可视化·数据分析
民乐团扒谱机2 天前
【微实验】弦振动 MATLAB 物理模型 动画仿真
matlab·动画·仿真·信号·声学·振动·
Evand J2 天前
【matlab例程】无迹粒子滤波(UPF)的例程,用于三维环境下多雷达目标跟踪,非线性系统
开发语言·matlab·目标跟踪
琛:D3 天前
【MATLAB/Simulink】查看MATLAB以往版本的帮助文档
matlab
zzc9213 天前
如何用USRP捕获手机信号波形(下)协议分析
5g·信号·usrp·射频·pro·信令·cellular
程序员老冯头3 天前
第三十二节 MATLAB函数
数据结构·算法·matlab
民乐团扒谱机3 天前
【微实验】圆周运动的正弦信号发生仿真及其衍生(三)
经验分享·matlab·仿真·信号与系统·声音·微实验