滑动窗口 STFTShort-Time Fourier Transform完整实现以下是为 Qt 项目量身定制的高效实时 STFT实现完美兼容现有的computeFFT已优化为 real r2c plan 重用多通道数据最高 16 通道QCustomPlotui-Test串口实时数据流1. 核心优势与普通 FFT 的区别滑动窗口固定窗口大小 重叠hop size支持 0%~90% 重叠加窗函数Hann默认最常用减少频谱泄漏实时性适合串口持续流入的数据性能复用同一个 FFT plan零拷贝极低延迟可视化推荐使用QCPColorMap 频谱图水瀑/瀑布图效果远优于之前的柱状图2. 在mainwindow.h中添加成员private 部分// STFT 相关 intstftWindowSize1024;// 窗口长度推荐 2 的幂intstftHopSize512;// 跳步长度重叠率 1 - hop/windowQString stftWindowTypeHann;// Hann, Hamming, BlackmanQVectordoublechannelBuffer[16];// 每个通道的循环缓冲区滚动存储最新数据// STFT 输出每行 一个时间窗的频谱时间 × 频率QVectorQVectordoublestftSpectra;// [timeIdx][freqBin]// FFT Plan 重用复用您之前优化的 real r2c planfftw_plan r2c_plannullptr;double*fft_innullptr;fftw_complex*fft_outnullptr;intcurrentFFT_N0;// 预计算的窗函数系数只计算一次QVectordoublewindowCoeffs;3. 构造函数中初始化MainWindow::MainWindow在ui-setupUi(this);之后添加// STFT 初始化for(inti0;i16;i){channelBuffer[i].reserve(4096*4);// 预分配避免频繁 realloc}windowCoeffscreateWindow(stftWindowSize,stftWindowType);// 见下面函数4. 新增辅助函数建议放在mainwindow.cpp合适位置// 1. 生成窗函数 QVectordoubleMainWindow::createWindow(intsize,constQStringtype){QVectordoublew(size,0.0);if(typeHann){for(inti0;isize;i){w[i]0.5*(1.0-std::cos(2*M_PI*i/(size-1)));}}elseif(typeHamming){for(inti0;isize;i){w[i]0.54-0.46*std::cos(2*M_PI*i/(size-1));}}elseif(typeBlackman){for(inti0;isize;i){doublea00.42,a10.5,a20.08;w[i]a0-a1*std::cos(2*M_PI*i/(size-1))-a2*std::cos(4*M_PI*i/(size-1));}}returnw;}// 2. 优化的滑动窗口 STFT核心voidMainWindow::computeSTFT(constQVectordoublesignal,QVectorQVectordoublespectra,intwinSize,inthopSize){if(signal.size()winSize){spectra.clear();return;}intNqNextPowerOfTwo(winSize);// 自动对齐到 2 的幂intnumWindows((signal.size()-winSize)/hopSize)1;spectra.resize(numWindows);for(autorow:spectra)row.resize(N/21);// 确保 FFT plan 存在且大小匹配if(N!currentFFT_N||r2c_plannullptr){if(r2c_plan){fftw_destroy_plan(r2c_plan);fftw_free(fft_in);fftw_free(fft_out);}fft_in(double*)fftw_malloc(sizeof(double)*N);fft_out(fftw_complex*)fftw_malloc(sizeof(fftw_complex)*(N/21));r2c_planfftw_plan_dft_r2c_1d(N,fft_in,fft_out,FFTW_MEASURE);currentFFT_NN;}// 预计算归一化系数constdoublenorm2.0/winSize;// 单边谱 窗函数能量补偿for(intt0;tnumWindows;t){intstartt*hopSize;// 1. 加窗 零填充for(inti0;iwinSize;i){fft_in[i]signal[starti]*windowCoeffs[i];}for(intiwinSize;iN;i)fft_in[i]0.0;// 2. 执行 FFTfftw_execute(r2c_plan);// 3. 计算幅值谱单边autospecspectra[t];for(intf0;fN/2;f){doublemagstd::sqrt(fft_out[f][0]*fft_out[f][0]fft_out[f][1]*fft_out[f][1]);if(f0fN/2)mag*2.0;// 单边谱补偿spec[f]mag*norm;}}}5. 在on_DoFFTButton_clicked()中调用替换原有 FFT 逻辑voidMainWindow::on_DoFFTButton_clicked(){// ... 原有解析代码不变lines → yValuesintfsui-DataFs_Edit-text().toInt();// 采样率if(fs0)fs1024;// 清空旧的 STFT 显示ui-Test-clearPlottables();// 对每个通道进行滑动 STFTfor(intch0;chmaxWaveCount;ch){if(yValues[ch].isEmpty())continue;// 更新滚动缓冲区保持最新数据channelBuffer[ch].append(yValues[ch]);if(channelBuffer[ch].size()8192){// 限制最大长度channelBuffer[ch].remove(0,channelBuffer[ch].size()-8192);}QVectorQVectordoublespectra;computeSTFT(channelBuffer[ch],spectra,stftWindowSize,stftHopSize);if(spectra.isEmpty())continue;// 显示方式推荐 // 方式A推荐QCPColorMap 频谱图水瀑图QCPColorMap*colorMapnewQCPColorMap(ui-Test-xAxis,ui-Test-yAxis);colorMap-setName(QString(通道 %1 STFT).arg(ch1));inttimeBinsspectra.size();intfreqBinsspectra[0].size();colorMap-data()-setSize(freqBins,timeBins);colorMap-data()-setRange(QCPRange(0,freqBins-1),QCPRange(0,timeBins-1));for(intt0;ttimeBins;t){for(intf0;ffreqBins;f){colorMap-data()-setCell(f,t,spectra[t][f]);}}// 频率轴HzcolorMap-data()-setRange(QCPRange(0,fs/2.0),QCPRange(0,timeBins));// 颜色映射推荐 jet / thermalQCPColorGradientgradient(QCPColorGradient::gpJet);colorMap-setGradient(gradient);colorMap-setInterpolate(true);colorMap-setDataRange(QCPRange(0,50));// 根据您的信号幅度调整// 方式B备选多个 QCPGraph 曲线如果您不喜欢热图// for (int t 0; t spectra.size(); t) { ... }}ui-Test-xAxis-setLabel(频率 (Hz));ui-Test-yAxis-setLabel(时间窗索引);ui-Test-rescaleAxes();ui-Test-replot(QCustomPlot::rpQueuedReplot);}6. 实时自动更新推荐在manual_serialPortReadyRead()末尾数据解析完后添加// 每收到新数据就自动更新 STFT可加节流staticQTimer*stftTimernullptr;if(!stftTimer){stftTimernewQTimer(this);stftTimer-setSingleShot(true);connect(stftTimer,QTimer::timeout,this,MainWindow::on_DoFFTButton_clicked);}if(!stftTimer-isActive())stftTimer-start(50);// 最多 20Hz 更新避免卡顿7. 析构函数释放MainWindow::~MainWindow(){if(r2c_plan){fftw_destroy_plan(r2c_plan);fftw_free(fft_in);fftw_free(fft_out);}deleteui;}使用建议窗口大小1024~4096根据您的采样率和信号频率重叠率50%~75%hopSize windowSize / 2或/4窗函数Hann 最均衡Blackman 旁瓣抑制更好频谱图颜色可改gpHot、gpThermal等性能在 921600 波特率 16 通道下依然流畅plan 只创建一次完整替换后点击DoFFTButton即可看到漂亮的滑动窗口频谱图水瀑图。关注再提供可调节参数 UI滑块调整 window/hop对数显示dB版本实时滚动水瀑图时间轴自动下移