# 2. 詳論

## 2.1. 數據準備

``````struct HPoint {
int x;
int y;
int value;
};

int width = 512;   //熱力圖寬
int height = 512;  //熱力圖高
int reach = 25;    //影響範圍
int valueRange = 100;

vector<HPoint> heatPoints;  //熱力點
vector<HRect> heatRects;    //熱力範圍

void GetHeatPoint() {
int num = 100;
heatPoints.resize(num);
heatRects.resize(num);

for (int i = 0; i < num; i++) {
heatPoints[i].x = rand() % width;
heatPoints[i].y = rand() % height;
heatPoints[i].value = rand() % valueRange;

heatRects[i].left = (std::max)(heatPoints[i].x - reach, 0);
heatRects[i].top = (std::max)(heatPoints[i].y - reach, 0);
heatRects[i].right = (std::min)(heatPoints[i].x + reach, width - 1);
heatRects[i].bottom = (std::min)(heatPoints[i].y + reach, height - 1);
}
}
``````

## 2.2. 準備繪製

``````Mat img(height, width, CV_8UC4);
int nBand = 4;

uchar *data = img.data;
size_t dataLength = (size_t)width * height * nBand;
memset(data, 0, dataLength);

for (size_t i = 0; i < heatPoints.size(); i++) {
//遍歷熱力點範圍
for (int hi = heatRects[i].top; hi <= heatRects[i].bottom; hi++) {
for (int wi = heatRects[i].left; wi <= heatRects[i].right; wi++) {
size_t m = (size_t)width * nBand * hi + wi * nBand;
data[m + 0] = data[m + 1] = data[m + 2] = data[m + 3] = 255;
}
}
}

imshow("熱力圖", img);

waitKey();
``````

## 2.3. 繪製熱力範圍

``````  for (size_t i = 0; i < heatPoints.size(); i++) {
//遍歷熱力點範圍
for (int hi = heatRects[i].top; hi <= heatRects[i].bottom; hi++) {
for (int wi = heatRects[i].left; wi <= heatRects[i].right; wi++) {
//判斷是否在熱力圈範圍
float length =
sqrt((float)(wi - heatPoints[i].x) * (wi - heatPoints[i].x) +
(hi - heatPoints[i].y) * (hi - heatPoints[i].y));
if (length <= reach) {
size_t m = (size_t)width * nBand * hi + wi * nBand;
data[m + 0] = data[m + 1] = data[m + 2] = data[m + 3] = 255;
}
}
}
}
``````

## 2.4. 繪製熱力圖

``````  for (size_t i = 0; i < heatPoints.size(); i++) {
//遍歷熱力點範圍
for (int hi = heatRects[i].top; hi <= heatRects[i].bottom; hi++) {
for (int wi = heatRects[i].left; wi <= heatRects[i].right; wi++) {
//判斷是否在熱力圈範圍
float length =
sqrt((float)(wi - heatPoints[i].x) * (wi - heatPoints[i].x) +
(hi - heatPoints[i].y) * (hi - heatPoints[i].y));
if (length <= reach) {
float alpha = ((reach - length) / reach);

size_t m = (size_t)width * nBand * hi + wi * nBand;
data[m + 0] = data[m + 1] = data[m + 2] = data[m + 3] = uchar(255 * alpha);
}
}
}
}
``````

``````  for (size_t i = 0; i < heatPoints.size(); i++) {
//遍歷熱力點範圍
for (int hi = heatRects[i].top; hi <= heatRects[i].bottom; hi++) {
for (int wi = heatRects[i].left; wi <= heatRects[i].right; wi++) {
//判斷是否在熱力圈範圍
float length =
sqrt((float)(wi - heatPoints[i].x) * (wi - heatPoints[i].x) +
(hi - heatPoints[i].y) * (hi - heatPoints[i].y));
if (length <= reach) {
float alpha = ((reach - length) / reach);

size_t m = (size_t)width * nBand * hi + wi * nBand;
float newAlpha = data[m + 3] / 255.0f + alpha;
newAlpha = std::min(std::max(newAlpha * 255, 0.0f), 255.0f);
data[m + 0] = data[m + 1] = data[m + 2] = data[m + 3] =
uchar(newAlpha);
}
}
}
}
``````

``````  for (size_t i = 0; i < heatPoints.size(); i++) {
//權值因子
float ratio = (float)heatPoints[i].value / valueRange;

//遍歷熱力點範圍
for (int hi = heatRects[i].top; hi <= heatRects[i].bottom; hi++) {
for (int wi = heatRects[i].left; wi <= heatRects[i].right; wi++) {
//判斷是否在熱力圈範圍
float length =
sqrt((float)(wi - heatPoints[i].x) * (wi - heatPoints[i].x) +
(hi - heatPoints[i].y) * (hi - heatPoints[i].y));
if (length <= reach) {
float alpha = ((reach - length) / reach) * ratio;

size_t m = (size_t)width * nBand * hi + wi * nBand;
float newAlpha = data[m + 3] / 255.0f + alpha;
newAlpha = std::min(std::max(newAlpha * 255, 0.0f), 255.0f);
data[m + 0] = data[m + 1] = data[m + 2] = data[m + 3] =
uchar(newAlpha);
}
}
}
}
``````

## 2.5. 配色方案

``````array<array<uchar, 3>, 256> bGRTable;  //顏色映射表

//生成漸變色
void Gradient(array<uchar, 3> &start, array<uchar, 3> &end,
vector<array<uchar, 3>> &RGBList) {
array<float, 3> dBgr;
for (int i = 0; i < 3; i++) {
dBgr[i] = (float)(end[i] - start[i]) / (RGBList.size() - 1);
}

for (size_t i = 0; i < RGBList.size(); i++) {
for (int j = 0; j < 3; j++) {
RGBList[i][j] = (uchar)(start[j] + dBgr[j] * i);
}
}
}

void InitAlpha2BGRTable() {
array<double, 7> boundaryValue = {0.2, 0.3, 0.4, 0.6, 0.8, 0.9, 1.0};
array<array<uchar, 3>, 7> boundaryBGR;
boundaryBGR[0] = {255, 0, 0};
boundaryBGR[1] = {231, 111, 43};
boundaryBGR[2] = {241, 192, 2};
boundaryBGR[3] = {148, 222, 44};
boundaryBGR[4] = {83, 237, 254};
boundaryBGR[5] = {50, 118, 253};
boundaryBGR[6] = {28, 64, 255};

double lastValue = 0;
array<uchar, 3> lastRGB = {0, 0, 0};
vector<array<uchar, 3>> RGBList;
int sumNum = 0;
for (size_t i = 0; i < boundaryValue.size(); i++) {
int num = 0;
if (i == boundaryValue.size() - 1) {
num = 256 - sumNum;
} else {
num = (int)((boundaryValue[i] - lastValue) * 256 + 0.5);
}

RGBList.resize(num);

for (int i = 0; i < num; i++) {
bGRTable[i + sumNum] = RGBList[i];
}
sumNum = sumNum + num;

lastValue = boundaryValue[i];
lastRGB = boundaryBGR[i];
}
}
``````

``````  for (size_t i = 0; i < heatPoints.size(); i++) {
//權值因子
float ratio = (float)heatPoints[i].value / valueRange;

//遍歷熱力點範圍
for (int hi = heatRects[i].top; hi <= heatRects[i].bottom; hi++) {
for (int wi = heatRects[i].left; wi <= heatRects[i].right; wi++) {
//判斷是否在熱力圈範圍
float length =
sqrt((float)(wi - heatPoints[i].x) * (wi - heatPoints[i].x) +
(hi - heatPoints[i].y) * (hi - heatPoints[i].y));
if (length <= reach) {
float alpha = ((reach - length) / reach) * ratio;

//計算Alpha
size_t m = (size_t)width * nBand * hi + wi * nBand;
float newAlpha = data[m + 3] / 255.0f + alpha;
newAlpha = std::min(std::max(newAlpha * 255, 0.0f), 255.0f);
data[m + 3] = (uchar)(newAlpha);

//顏色映射
for (int bi = 0; bi < 3; bi++) {
data[m + bi] = bGRTable[data[m + 3]][bi];
}
}
}
}
}
``````

# 3. 問題

1. OpenCV顯示的背景是黑色的，這是因爲其默認是按照RGB三波段來顯示的，其實最後的結果是個包含透明通道的圖像，可以將其疊加到任何圖層上：
2. 熱力點可以有權值，也可以沒有。沒有權值可以認爲所有點的權值是一樣的，可以適當調整熱力影響的範圍讓不同的熱力點連接，否則就是一個個獨立的圈。
3. 如果出現紅色的區域（熱力值高）過多，那麼原因可能是熱力點太密了。同一個區域內收到的熱力影響太多，計算的alpha超過1，映射到圖像像素值導致被截斷，無法區分熱力值高的區域。那麼一個合理的改進方案就是將計算的alpha緩存住，在計算所有的alpha的最大最小，將alpha再度映射到0到1之間，進而映射到像素值的0~255之間——就不會高位截斷的問題了。如果有機會，再實現一下這個問題的改進。