Qt 三种实时时频控件的简易方案

Qt尽管非常强大,但对时频分析的控件支持不是很好。以前主要靠Qwt的Spectrogram来做,但眼瞅着Qt Charts 开源后,Qwt的更新越来越少,真的怕那天它凉凉了。Qt Charts 美工要比Qwt更加摩登,可是显然背后的行业背景不是信号处理,其距离数据分析更进一步(股票啦、人口啦等等),支持二维时频不是很好。经过一段时间尝试,找到了至少三种办法,这里做一个记录。

1 使用 Qt Data Visualization

这是最简单的方法,参照例子 Qt Quick 2 Spectrogram Example。其主要原理是利用 Surface 例子中的框架,用正摄投影(不是透视投影)把照相机放在在正上方,显示出一个二维的着色图形。
Qt 三种实时时频控件的简易方案_第1张图片
这个方法显然有些小题大做,原因:

  • Qt Data Visualization 需要较好的显卡支持。在笔者上网本上压根跑不起来。
  • 3D模型的消费太高,不利于节约资源以及实时刷新。
  • 与波形控件的联动不好弄。

2 从开源SDR项目中抽取控件

GNU-Radio下的gqrx是一款基于Qt的SDR接收机,对时频分析做的很棒哦!首先,从GitHub 里checkout,找到文件夹 qtgui,里面的plotter就是时频图。非常值得肯定的是,这个开源项目中,时频图没有和特定功能耦合起来,可以直接提取出使用。
把时频图相关控件提取出来后,可以方便的进行测试。这个控件做的非常棒,功能很多,接口非常清晰,可以慢慢研究。提取出的纯控件参考我的资源,这里给出提取后的界面。
Qt 三种实时时频控件的简易方案_第2张图片也使用Udp封装了一个接口,参考
我的临时Git仓库

3 利用Qt Charts 和 QImage 就地撸一个

上述提取的控件虽然强大,但是还是带有很多不必要的资源以及结构定义,前前后后拖了一堆小h,cpp,rc,icon文件,甚是烦恼。既然Qt Charts显示波形已经很棒了,自带橡皮筋缩放等功能,何不结合它撸一个呢?
主要的思路很简单,每刷新一次Qt-Charts,就把当前的波形作为像素,增补到一个QImage中,实现时频图显示。

3.1 色彩映射

FFT后的幅度谱结果一般是dB或者dBm,取决于归一化的方法。假设AD时量化范围为正负1伏特映射到正负2048整形,而后做16384点FFT,则可以在FFT后,把16384、2048这些量化值除掉(或对数减去),换算成浮点的分贝数。正常情况下,该值都是一个负数,范围在-200~0之间。

时频显示控件都会有一个动态范围,我们可以通过线性映射,把dB数映射为RGB

const double vii = (v-m_minBound)/(m_maxBound-m_minBound);
int red = 0,green = 0,blue = 0;
if (vii>=0 && vii<0.25)
        blue = vii/0.25*255;
else if (vii>=0.25 && vii<0.5 )
{
        blue = (0.5-vii)/0.25*255;
        red =  (vii-0.25)/0.25*255;
}
else if (vii>=0.5 && vii<0.75 )
{
        red =  255;
        green = (vii-0.5)/0.25*255;
}
else if (vii>=0.75 && vii<1)
{
        red = 255;
        green = 255;
        blue =  (vii-0.75)/0.25*255;
}
else if (vii>=1)
        red = 255, blue = 255, green = 255;

取不同的映射色彩,即可得到不同的风格,上述风格只是例子。

3.2 QtCharts 视图跟踪

当QtCharts的视图被用户缩放后,波形的显示范围也会发生变化。此时,要对数据的范围进行调整。比如,16384点FFT,由于QtCharts波形控件的范围目前只显示3451~4956点,我们的时频图也要同步上这个变化。
同步变化的方法就是获取QtChart当前的视图参数:

//...QChart * ca;
//...QValueAxis * axx;
//...QVector data;//16384 fft
//获得当前视图的X坐标范围
double dmin = axx->min();
double dmax = axx->max();
//获得当前视图的屏幕坐标范围
QRectF r = ca->plotArea();
//推入一行像素。
apectroWidget->append_data(
	 data,
	 r.left(),
	 r.right(),
	 dmin,
	 dmax
	);
//...
class SpectroWidget{...
//...
 QImage m_image;
};
//...
void SpectroWidget::append_data(QVector<double> vec_data,
        double left,
        double right,
        double left_x,
        double right_x)
{
	 const int width = m_image.width();
	 const int height = m_image.height();
	 const int scsize = m_image.bytesPerLine();
	 //当前视图下走一行
	 for (int l = height-1;l>0;--l)
  		memcpy(m_image.scanLine(l),m_image.scanLine(l-1),scsize);
 	const int datasz = vec_data.size();
 	for (int r = 0;r<width;++r)
 	{
  		if (r<left || r>right)
   			continue;
  		//计算当前点对应的原始数据位置
  		int from = (r-left)/(right-left)*(right_x-left_x) + left_x+.5;
  		//归一化幅度到0-1
  		const double v = (from>=0 && from <datasz)?vec_data[from]:m_minBound;
 		const double vii = (v-m_minBound)/(m_maxBound-m_minBound);
  		//从0-1归一化幅度到彩色
  		qRgb pixcolor = colorMap(style,vii);
  		//设置像素
  		m_image.setPixel(r,0,pixcolor);
 	}
	update();
 }

上述步骤比较简化,X坐标直接采用的是点自然下标0-16383。若根据采样率和中心频率映射到实际Hz单位,还要加上一个线性变换。通过上述步骤,即可实现简易的实时时频控件,且刷新性能很棒。

例子参考这里。

Qt 三种实时时频控件的简易方案_第3张图片

4 其他选项

4.1 Qwt仍旧是最佳选择之一

如果您正在以linux或者msys2为开发环境,继续使用Qwt也是很棒的选择。在类Unix环境中,大部分库的安装都是非常方便的,有各种apt\pacman\yum之类的可用。但由于Qwt是第三方控件,要考虑到windows下的兼容性——为每个Qt版本(其实是Qt主次版本+编译器)都构建一次Qwt是非常繁琐的。

4.2 离线分析控件

离线分析控件需要基于文件进行更加细致的控制,如时间尺度的改变、回溯。目前尚未有非常棒的开源离线时频分析控件,如果哪位大神知道的,恳请分享!

你可能感兴趣的:(现代C/C++工具链,QT,C++)