原理
什么熱度圖啊、頻譜圖啊,諸如此類的,其本質都是數值與顏色在一幅圖上的映射,我們稱其為 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萬!當然,算的少了多多少少會對圖像質量有影響,我們適當在復雜度和圖像效果上做一些均衡吧。
