【Qt】绘制热度图、频谱图、地形图、colormap

原理

什么热度图啊、频谱图啊,诸如此类的,其本质都是数值与颜色在一幅图上的映射,我们称其为 colormap。

这里为简化描述,颜色统一采用RGBA模式,RGB就是红绿蓝,A代表透明度。

于是乎画出一张colormap,即遍历整幅图,为每个像素点设置一个RGBA值,函数可以表示为:

int colorMatrix[width][height];
void drawColorMap(){
    for(int i = 0; i < width; ++i){
        for(int j = 0; j < height; ++j){
            colormap[i][j] = colorMatrix[i][j];
        }
    }
}

最终得到如下这种形式的效果:

实现方法

  • 以Qt为UI框架
  • 采用第三方库QCustomplot, 因为这个库使用起来很方便,只需要导入 .h 和 .cpp 文件就行,无需编译成动态链接库。

首先,我们对QCustomplot所呈现出的图像有个基础的认识,要实现colormap需要用到的地方我在图中标注了下,主要包括。

  • 坐标轴的隐藏(看个人需求)
  • 创建一个QCPColorMap类,用于实现上边所说的 drawColorMap 功能
  • 创建一个图标尺,用来显示颜色的区间

上边的三步对应程序:

// configure axis rect:
    m_customPlot = new QCustomPlot(this);
    m_customPlot->setInteractions(QCP::iRangeDrag|QCP::iRangeZoom); // this will also allow rescaling the color scale by dragging/zooming
    m_customPlot->axisRect()->setupFullAxesBox(true);
    //隐藏坐标,上下左右
    m_customPlot->xAxis->setVisible(true);
    m_customPlot->yAxis->setVisible(true);
    m_customPlot->xAxis2->setVisible(true);
    m_customPlot->yAxis2->setVisible(true);

    // set up the QCPColorMap:
    m_colorMap = new QCPColorMap(m_customPlot->xAxis, m_customPlot->yAxis);
    // add a color scale:
    m_colorScale = new QCPColorScale(m_customPlot);
    m_customPlot->plotLayout()->addElement(0, 1, m_colorScale); // add it to the right of the main axis rect
    m_colorScale->setType(QCPAxis::atRight); // scale shall be vertical bar with tick/axis labels right (actually atRight is already the default)
    m_colorMap->setColorScale(m_colorScale); // associate the color map with the color scale
    // set the color gradient of the color map to one of the presets:
    m_colorMap->setGradient(QCPColorGradient::gpJet);

这里引入了一个 QCPColorGradient 的概念,我们不难想到这样一个问题:加入值为0时颜色是蓝色,值为10时颜色是红色,那值为5时颜色是什么?这就是 QCPColorGradient 要做的事了。

难道就这么简单?

假如我们要绘制的图像可以分为 400 * 300 个网格分布,按常理来说只需要遍历这些网格并设置颜色就行了,但是实际情况往往是我们获取到的数据很少,

比如只有64个位置,如何扩充到 400 * 300 这么大的格子里呢,该如何插值呢?

不难想象,离这些点越近的格子受到这个点影响越大,反之越小。

距离,关键词为距离,令距离为r,则r代表着插值方式是线性的,改为r2则是曲线的,效果更佳平滑,r3, r^4 以此类推

为了简化计算,采用距离的平方作为权重,只需累加已知的点在未知点的权重即可:

for(int i = 0; i < datax; ++i){
    for(int j = 0; j < datay; ++j){
        m_colorMap->data()->cellToCoord(i, j, &x, &y);
        //计算权重值
        double sum = 0.0;
        for(int k = 0; k < m_channelAxis.size(); ++k){
            //(x, y) 为 (i, j) 在 colormap坐标系下的映射
            auto& point = m_channelAxis[k];
            double rr = (point.x() - x) * (point.x() - x) + (point.y() - y) * (point.y() - y);
            sum += 1 / rr;
            m_channelWeight[i][j][k] = 1 / rr;
        }
        for(int k = 0; k < m_channelAxis.size(); ++k){
            m_channelWeight[i][j][k] /= sum;
        }
    }
}

这样我们就把所有网格对应的颜色值计算出来了,时间复杂度 O(width * height * points)。

等等,这个复杂度貌似很大?400 * 300 * 64 = 7,680,000。如果你看过根据数据范围推测算法复杂度这篇文章,

并且在leetcode刷了刷题,就知道700万这个量级是很大的啦,放leetcode跑肯定超时。

因此我们需要需要优化时间复杂度。

时间复杂度优化

如果搞过图像处理估计你已经知道要怎么优化了,上面的问题等价为 : 已知一幅小尺寸图,如何放大成大尺寸图。

没错,小尺寸。我们上面的网格400 * 300太大了,如果是算 40 * 30 呢?瞬间计算次数就小了有没有!算完之后我们再给他放大嘛。

这里采用双线性插值的方法:

for (int i = 0; i < datax - 1; i++)
    {
        for (int j = 0; j < datay - 1; j++)
        {
            double V1 = m_matrix1[i][j];
            double V2 = m_matrix1[i + 1][j];
            double V3 = m_matrix1[i + 1][j + 1];
            double V4 = m_matrix1[i][j + 1];
            for (int m = 0; m < ratex; m++)
            {
                for (int n = 0; n < ratey; n++)
                {
                    int x = i * ratex + m, y = j * ratey + n;
                    if(m_inCircle[x][y] == false) continue;
                    m_matrix[x][y] = doubleLinear(m, n, ratex, ratey, V1, V2, V3, V4);
                    m_colorMap->data()->setCell(x, y, m_matrix[x][y]);
                }
            }
        }
    }
double HotPlot::doubleLinear(int m, int n, int X, int Y, double V1, double V2, double V3, double V4)
{
    return (m * n * (V3 - V4 - V2 + V1) + X * n * (V4 - V1) + m * Y * (V2 - V1)) / (X * Y) + V1;
}

再来看看现在的计算量 :40 * 30 * 64 + 400 * 300 = 196800,由700万降到了20万!当然,算的少了多多少少会对图像质量有影响,我们适当在复杂度和图像效果上做一些均衡吧。

Demo

QColorMapDemo

更新,上述的插值方法为距离反比算法,其实还有很多插值算法,会有更好的效果,我写了个插值算法放在下面了

SimpleMultivariateInterpolation

posted @ 2021-04-25 18:25  miyanyan  阅读(7232)  评论(3编辑  收藏  举报