「C#」生成HeatMap(热度图)的实现

article/2025/10/30 23:51:04

1、什么是Heatmap

其实不用多言,需要这个的人自然知道这是什么。基于一系列点生成的热度图,放张图感受一下:

ma...大概就是这种样子。

2、生成(计算)原理

实现方式实际上是在每个点上叠加高斯矩阵。高斯矩阵就是在二维平面上的高斯(正态)分布。

高斯分布的计算公式如下:

在二维上

简单的理解就是像下图一样,离(u1,u2)越近的点的值越大,越远的值越小。

将这样的小矩阵,根据点的位置和权重进行叠加。然后根据叠加后的数值转换成对应的颜色就好。

3、实现源码

项目源码在github上有上传,有需要的可以直接去下载→https://github.com/RainkLH/HeatMapSharp

以下代码C#语言,通过对Bitmap类操作实现

要用到一下命名空间:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

 3.1、主类

public class HeatMapImage
{/// <summary>/// width of img/// </summary>private int w;/// <summary>/// height of img/// </summary>private int h;/// <summary>/// gaussian kernel size/// </summary>private int gSize;/// <summary>/// gaussian kernel sigma/// </summary>private double gSigma;/// <summary>/// radius/// </summary>private int r;/// <summary>/// Two dimensional matrix corresponding to data list/// </summary>private double[,] heatVals;/// <summary>/// Color map matrix/// </summary>private byte[] ColorArgbValues;/// <summary>/// gaussian kernel/// </summary>private double[,] kernel;/// <summary>/// color numbers/// </summary>private const int NUMCOLORS = 1000;/// <summary>/// width of img/// </summary>public int W { get => w; set => w = value; }/// <summary>/// height of img/// </summary>public int H { get => h; set => h = value; }/// <summary>/// gaussian kernel/// </summary>public double[,] Kernel { get => kernel; }/// <summary>/// Two dimensional matrix corresponding to data list/// </summary>public double[,] HeatVals { get => heatVals; }/// <summary>/// construction/// </summary>/// <param name="width">image width</param>/// <param name="height">image height</param>/// <param name="gSize">gaussian kernel size</param>/// <param name="gSigma">gaussian kernel sigma</param>public HeatMapImage(int width, int height, int gSize, double gSigma){this.w = width;this.h = height;CreateColorMap();//对高斯核尺寸进行判断if (gSize < 3 || gSize > 400){throw new Exception("Kernel size is invalid");}this.gSize = gSize % 2 == 0 ? gSize + 1 : gSize;//高斯的sigma值,计算半径rthis.r = this.gSize / 2;this.gSigma = gSigma;//计算高斯核kernel = new double[this.gSize, this.gSize];this.gaussiankernel();//初始化高斯累加图heatVals = new double[h, w];}/// <summary>/// 创建一个ColorMap,用于根据数值查询颜色/// </summary>private void CreateColorMap(){ColorBlend colorBlend = new ColorBlend(8);colorBlend.Colors = new Color[8]{Color.FromArgb(0, 255, 255, 255),Color.FromArgb(10, 128, 0, 128),Color.FromArgb(30, 128, 0, 255),Color.FromArgb(70, 0, 0, 255),Color.FromArgb(110, 0, 255, 0),Color.FromArgb(130, 255, 255, 0),Color.FromArgb(145, 255, 128, 0),Color.FromArgb(155, 255, 0, 0)};colorBlend.Positions = new float[8] { 0.0f, 0.1f, 0.25f, 0.4f, 0.65f, 0.75f, 0.9f, 1.0f };Color startColor = colorBlend.Colors[0];Color endColor = colorBlend.Colors[colorBlend.Colors.Length - 1];using (Bitmap colorMapBitmap = new Bitmap(NUMCOLORS, 1, PixelFormat.Format32bppArgb)){Rectangle colorRect = new Rectangle(0, 0, colorMapBitmap.Width, colorMapBitmap.Height);using (Graphics bitmapGraphics = Graphics.FromImage(colorMapBitmap)){using (LinearGradientBrush brush = new LinearGradientBrush(colorRect, startColor, endColor, LinearGradientMode.Horizontal)){brush.InterpolationColors = colorBlend;bitmapGraphics.FillRectangle(brush, colorRect);}}BitmapData colorMapData = colorMapBitmap.LockBits(colorRect, ImageLockMode.ReadOnly, colorMapBitmap.PixelFormat);IntPtr colorPtr = colorMapData.Scan0;int colorBytes = Math.Abs(colorMapData.Stride) * colorMapBitmap.Height;ColorArgbValues = new byte[colorBytes];System.Runtime.InteropServices.Marshal.Copy(colorPtr, ColorArgbValues, 0, colorBytes);colorMapBitmap.UnlockBits(colorMapData);}}/// <summary>/// 高斯核/// </summary>private void gaussiankernel(){for (int y = -r, i = 0; i < gSize; y++, i++){for (int x = -r, j = 0; j < gSize; x++, j++){kernel[i, j] = Math.Exp(((x * x) + (y * y)) / (-2 * gSigma * gSigma)) / (2 * Math.PI * gSigma * gSigma);}}}/// <summary>/// 高斯核乘上权重/// </summary>/// <param name="weight"></param>/// <returns></returns>private double[,] MultiplyKernel(double weight){double[,] wKernel = (double[,])kernel.Clone();for (int i = 0; i < gSize; i++){for (int j = 0; j < gSize; j++){wKernel[i, j] *= weight;}}return wKernel;}/// <summary>/// 对所有权重进行归一化,压缩到颜色数量以内的值/// </summary>private void RescaleArray(){float max = 0;foreach (float value in heatVals){if (value > max){max = value;}}for (int i = 0; i < heatVals.GetLength(0); i++){for (int j = 0; j < heatVals.GetLength(1); j++){heatVals[i, j] *= (NUMCOLORS - 1) / max;if (heatVals[i, j] > NUMCOLORS - 1){heatVals[i, j] = NUMCOLORS - 1;}}}}/// <summary>/// 一次性设置一组数据/// </summary>/// <param name="datas"></param>public void SetDatas(List<DataType> datas){foreach (DataType data in datas){int i, j, tx, ty, ir, jr;int radius = gSize >> 1;int x = data.X;int y = data.Y;double[,] kernelMultiplied = MultiplyKernel(data.Weight);for (i = 0; i < gSize; i++){ir = i - radius;ty = y + ir;if (ty < 0){continue;}if (ty >= h){break;}for (j = 0; j < gSize; j++){jr = j - radius;tx = x + jr;// skip columnif (tx < 0){continue;}if (tx < w){heatVals[ty, tx] += kernelMultiplied[i, j];}}}}}/// <summary>/// 逐个数据一个一个的存入/// </summary>/// <param name="data"></param>public void SetAData(DataType data){int i, j, tx, ty, ir, jr;int radius = gSize >> 1;int x = data.X;int y = data.Y;double[,] kernelMultiplied = MultiplyKernel(data.Weight);for (i = 0; i < gSize; i++){ir = i - radius;ty = y + ir;if (ty < 0){continue;}if (ty >= h){break;}for (j = 0; j < gSize; j++){jr = j - radius;tx = x + jr;if (tx < 0){continue;}if (tx < w){heatVals[ty, tx] += kernelMultiplied[i, j];}}}}/// <summary>/// 基于存储的数据进行计算heatMap/// </summary>/// <returns></returns>public Bitmap GetHeatMap(){RescaleArray();Bitmap heatMap = new Bitmap(W, H, PixelFormat.Format32bppArgb);Rectangle rect = new Rectangle(0, 0, heatMap.Width, heatMap.Height);BitmapData heatMapData = heatMap.LockBits(rect, ImageLockMode.WriteOnly, heatMap.PixelFormat);IntPtr ptrw = heatMapData.Scan0;int wbytes = Math.Abs(heatMapData.Stride) * heatMap.Height;byte[] argbValuesW = new byte[wbytes];System.Runtime.InteropServices.Marshal.Copy(ptrw, argbValuesW, 0, wbytes);for (int i = 0; i < h; i++){for (int j = 0; j < w; j++){int colorIndex = double.IsNaN(heatVals[i, j]) ? 0 : (int)heatVals[i, j];int index = (i * heatMap.Width + j) * 4;argbValuesW[index] = ColorArgbValues[4 * colorIndex];argbValuesW[index + 1] = ColorArgbValues[4 * colorIndex + 1];argbValuesW[index + 2] = ColorArgbValues[4 * colorIndex + 2];argbValuesW[index + 3] = ColorArgbValues[4 * colorIndex + 3];}}System.Runtime.InteropServices.Marshal.Copy(argbValuesW, 0, ptrw, wbytes);heatMap.UnlockBits(heatMapData);return heatMap;}/// <summary>/// 输出查看一下颜色图谱。/// </summary>/// <returns></returns>public Bitmap CheckColorMap(){Bitmap checkColor = new Bitmap(w, h, PixelFormat.Format32bppArgb);int step = (NUMCOLORS - w) / h + 1;for (int i = 0; i < h; i++){for (int j = 0; j < w; j++){if ((i * step) + j >= NUMCOLORS){return checkColor;}Color color = Color.FromArgb(ColorArgbValues[i * step * 4],ColorArgbValues[i * step * 4 + 1],ColorArgbValues[i * step * 4 + 2],ColorArgbValues[i * step * 4 + 3]);checkColor.SetPixel(j, i, color);}}return checkColor;}
}

4.2、数据类型

类中涉及的数据类型 DataType:

public class DataType
{public int X { get; set; }public int Y { get; set; }public double Weight { get; set; }
}

4、进行测试

4.1模拟数据生成

使用Numpy在C#中的wrapper: NumSharp生成一下随机数据

using System;
using System.Collections.Generic;
using NumSharp;
using HeatMap;//省略了namespace,如果需要,请理性复制class MockDatasGen
{private DataType tPt;private int W;private int H;public MockDatasGen(int w, int h){W = w;H = h;tPt = new DataType() { X = -1, Y = -1, Weight = -1};}public List<DataType> CreateMockDatas(int nums){List<DataType> datas = new List<DataType>();for (int i = 0; i < nums; i++){if (tPt.X > 0 && tPt.Y > 0 && tPt.Weight > 0){double c = np.random.rand();int x = 0, y = 0;if (c < 0.85){int l = np.random.randint(1, 50);double d = 2 * np.random.rand() * np.pi;x = (int)(l * Math.Cos(d));y = (int)(l * Math.Sin(d));x = tPt.X + x < 0 ? 0 - x : x;y = tPt.Y + y < 0 ? 0 - y : y;x = tPt.X + x >= W ? 0 - x : x;y = tPt.Y + y >= H ? 0 - y : y;tPt.X = tPt.X + x;tPt.Y = tPt.Y + y;tPt.Weight = np.random.rand() * 10;DataType data = new DataType() { X = tPt.X, Y = tPt.Y, Weight = tPt.Weight };datas.Add(data);}else{tPt.X = np.random.randint(1, W);tPt.Y = np.random.randint(1, H);tPt.Weight = np.random.rand() * 10;DataType data = new DataType() { X = tPt.X, Y = tPt.Y, Weight = tPt.Weight };datas.Add(data);}}else{tPt.X = np.random.randint(1, W);tPt.Y = np.random.randint(1, H);tPt.Weight = np.random.rand() * 10;DataType data = new DataType() { X = tPt.X, Y = tPt.Y, Weight = tPt.Weight };datas.Add(data);}}return datas;}
}

4.2 使用模拟数据测试

const int WIDTH = 800;
const int HEIGHT = 600;
static void Main(string[] args)
{MockDatasGen datasGen;Console.WriteLine("Create some mock datas");datasGen = new MockDatasGen(WIDTH, HEIGHT);List<DataType> datas = datasGen.CreateMockDatas(100);Console.WriteLine("Set datas");HeatMapImage heatMapImage = new HeatMapImage(WIDTH, HEIGHT, 200, 50);heatMapImage.SetDatas(datas);Console.WriteLine("Calculate and generate heatmap");Bitmap img = heatMapImage.GetHeatMap();img.Save("..\\..\\..\\..\\Images\\heatmap1.png");
}

最终效果就如文章开始时放的那张图了。


http://chatgpt.dhexx.cn/article/tuQbbBAq.shtml

相关文章

关键点检测的heatmap介绍

开始学关键点检测的时候&#xff0c;到处找找不到heatmap的解释。现在大概有些懂了&#xff0c;干脆自己写一个。部分转载。 关键点定位任务两种做法&#xff1a;heatmap和fully connected回归&#xff08;Heapmap-based和Regression-Based&#xff09; heatmap得到一张类似热…

python绘制热度图(heatmap)

1、简单的代码 from matplotlib import pyplot as plt import seaborn as sns import numpy as np import pandas as pd#练习的数据&#xff1a; datanp.arange(25).reshape(5,5) datapd.DataFrame(data)#绘制热度图&#xff1a; plotsns.heatmap(data)plt.show() 查看效果&a…

热图(Heatmap)绘制(matplotlib与seaborn)

热图是数据统计中经常使用的一种数据表示方法&#xff0c;它能够直观地反映数据特征&#xff0c;查看数据总体情况&#xff0c;在诸多领域具有广泛应用。 一&#xff1a;matplotlib绘制方法 1.基础绘制 热图用以表示的是矩阵数据&#xff0c;例如相关阵、协差阵等方阵&#…

‘0’ 和 '\0'

48是0对应的ascii值。

KEIL/MDK编译优化optimization选项注意事项

KEIL编译器C语言编译选项优化等级说明 -Onum Specifies the level of optimization to be used when compiling source files. Syntax -Onum Where num is one of the following: 0 Minimum optimization. Turns off most optimizations. When debugging is enabled, this opt…

0,'\0','0'

#include <iostream> using namespace std; int main(void) { cout<<__FILE__<<\t<<__LINE__<<endl;cout<<"内 容:\t"<<"0"<<\t<<"\\\0\"<<\t<<"\0\"<<…

Odoo

狭路相逢 勇者胜 Odoo 是用于经营公司的最好的管理软件。 数百万用户使用我们的集成应用可以更好地开展工作 现在开始。免费的。 重新定义可扩展性 一个需求&#xff0c;一个应用程式。整合从来没有那么顺畅 促进销售量 客户关系管理POS销售 整合您的服务 项目工时表帮助…

0 、 '0' 、 0 、 ’\0’ 区别

转载自&#xff1a;https://blog.csdn.net/qnavy123/article/details/93901631 ① ‘0’ 代表 字符0 &#xff0c;对应ASCII码值为 0x30 (也就是十进制 48) ② ‘\0’ 代表 空字符(转义字符)【输出为空】 &#xff0c;对应ASCII码值为 0x00(也就是十进制 0)&#xff0c; …

Linux的内核编译用O0是编译不过的

最近在ATF的升级过程中遇到了一个编译问题&#xff0c;最后是通过编译优化解决的&#xff0c;然后一百度这个优化全是在Linux中的。于是就借着Linux编译优化来学学。 内容来自 宋宝华老师&#xff1a; 关于Linux编译优化几个必须掌握的姿势 1、编译选项和内核编译 首先我们都…

alert uuid does not exits. Dropping to a shell!

ALERT&#xff01;UUID does not exit. Dropping to a shell&#xff01; 服务器系统ubuntu16.04server&#xff0c;非自然断电后开机进入initramfs模式&#xff0c;服务器磁盘阵列是raid1和raid5。初步分析是硬盘坏道或掉盘&#xff0c;进入raid卡里看到硬盘一切正常&#xf…

跟着团子学SAP PS:如何查询PS模块中的user exits以及相关BAdIs SE80/SMOD/CNEX006/CNEX007/CNEX008

在PS很多标准字段或功能无法满足客户需求的时候往往需要通过SAP标准的user exits或者BAdI进行开发以满足业务需要&#xff0c;所以今天介绍下如何查询PS模块中的用户出口以及BAdIs&#xff1a; &#xff08;1&#xff09;查询PS模块中的user exits: 执行SE80&#xff0c;在菜…

EXT

ext的核心是store&#xff0c;存储数据用的。调试时可以先把store这块先屏蔽掉&#xff0c;先看页面的&#xff0c;页面出来了再调试store。这样会调试起来很快。 init: function () { var view this.getView(), // var store Global.getStore(app.store.L…

IDEA|class path resource XXX cannot be opened because it does not exits

IDEA|class path resource XXX cannot be opened because it does not exits 问题截图&#xff1a; 原因&#xff1a;没有设置好各个文件夹。我的理解是&#xff0c;当把文件夹设置好具体的功能才能被IDEA自动识别。 解决方法&#xff1a; 转发链接&#xff1a;https://bl…

User Exits和Customer Exits

一、Extension of SAP functionality SAP makes different possibilities available to extend SAP functionality in the R/3 without modifying the delivered R/3-Standard. Thus these extensions are further present also after a R/3-Release-Wechsel. User exit Fi…

sql查询中使用in和exits比较和区别

首先&#xff0c;查询中涉及到的两个表&#xff0c;一个user和一个order表&#xff0c;具体表的内容如下&#xff1a; user表&#xff1a; order表&#xff1a; in 确定给定的值是否与子查询或列表中的值相匹配。in在查询的时候&#xff0c;首先查询子查询的表&#xff0c;然后…

SQL语句中exits和in的区别

一 表展示 查询中涉及到的两个表&#xff0c;一个user和一个order表&#xff0c;具体表的内容如下&#xff1a; user表&#xff1a; order表&#xff1a; 二 in 演示 确定给定的值是否与子查询或列表中的值相匹配。in在查询的时候&#xff0c;首先查询子查询的表&#xff0c…

E. Exits in Excess

题意&#xff1a; 移除最多一半的边使得图没有环。 将所有边分成两部分&#xff0c; 第一部分为 u < v u < v u<v&#xff0c; 第二部分为 v > u v > u v>u&#xff0c; 将小的边集合删去即可。 AC代码&#xff1a; int n, m; vector<int> v1, v2…

20220621 Dual Quaternion

文章目录 对偶数一、对偶数是什么&#xff1f;二、对偶矢量三、对偶四元数 对偶数 一、对偶数是什么&#xff1f; https://zhuanlan.zhihu.com/p/358146509 对偶数是一种特殊的自洽的运算&#xff0c;类似于常用的复数基本单位 i i i &#xff08; i 2 − 1 i^2-1 i2−1&a…

Quaternion

01:欧拉角 1.欧拉角Vector3(x,y,z)代表的是旋转物体&#xff08;若是标准旋转那么是旋转坐标轴x,y,z&#xff0c;转换为旋转物体则旋转角度取反顺序不变&#xff09;&#xff0c;且是将物体从物体坐标系旋转到惯性坐标系&#xff08;世界坐标系中为了渲染&#xff09;&#x…

Quaternion.Euler调整记录

Quaternion.Euler调整 1.运行unity 调整摄像头视角&#xff0c;找到需要的位置&#xff0c;记录下 摄像头的位置和旋转角度。 2.调整空物体的位置 使之位置与需要的位置一致 3.调整Quaternion.Euler 使Quaternion.Euler与旋转数值一致。