绘制云图的三种算法(附C#代码)
我们要做什么呢?就是输入一个二维数组,TestData = new double[9, 6] {{ 26,28,29,32,28,27},{ 27,30,32,35,30,28},{ 24,27,30,27,25,24},{ 22,20,25,28,26,23},{ 19,15,20,26,25,24},{ 17,18,23,27,...
我们要做什么呢?就是输入一个二维数组,
TestData = new double[9, 6] {
{ 26,28,29,32,28,27},
{ 27,30,32,35,30,28},
{ 24,27,30,27,25,24},
{ 22,20,25,28,26,23},
{ 19,15,20,26,25,24},
{ 17,18,23,27,26,22},
{ 18,22,28,26,23,22},
{ 18,18,23,22,22,19},
{ 19,25,24,21,20,18}
};
然后输出类似下面的一个图:
其实云图跟等高线图有类似的地方,如果我们把图中的色彩减少,就会比较明显地看到线的轮廓:
绘制云图,其实最重要的就是插值问题。我们可以看到,原始的二维数组是9*6的,但我们显示的图片像素大致是600*500,甚至我们可能需要显示更大的图片。那除了9*6=54个点,其他的30万个点都是什么值呢?
为了简化问题,我们看下图:
我们假设全部格子就是图片的全部像素,而二维数组均匀地填在了某些位置。我们现在要做的,就是在空白区域填值。我们看格子A,它应该填什么值呢?有的人说,A直接根据10和12就能算出来。那我们不用考虑20和23对它的影响吗?假如二维数组代表了一些发热管的温度,那20和23肯定对A有影响。甚至,18、15这些点,对A都有影响。那么,我们现在就来看三种填值的算法。
算法一、点距离反比插值
这种算法比较适合绘制点源产生影响的云图。第一步,我们先把二维数组均匀地放到表格里面,正如上面的表格所示。我们把目前已经填上的数据称为定值。
然后我们看其他空白格子。平面上的任何一个点都受到了定值的影响,但肯定不是所有定值的影响都一样。我们很容易看出,距离越近,影响越大,距离越远,影响越小。一般我们认为,定值对点的影响,跟距离的平方成反比。那么,我们可以用下面的公式,求出每一个空白格子的值:
其中,Dij是点到每一个定值的距离平方的倒数,Vij是每一个定值。假设现在要求的点的坐标为(x,y),我们的二维数组是data(M,N),那么,上面的公式用代码写就是:
double SumD = 0;
double SumDV = 0;
for (int i = 0; i < M; i++)
{
for (int j = 0; j < N; j++)
{
double D = 1 / (Math.Pow(i - x, 2) + Math.Pow(j - y, 2));
SumD += D;
SumDV += D * data[i, j];
}
}
double value = SumDV / SumD;
上面的代码求出了一个点的值,我们可以看到,如果是9*6的二维数组,它一共循环了54次。然后总共是30万个点,那么总共要循环1620万次,而且每一次循环都有三个乘法和一个除法。
如果写到这里就运行程序的话,你必定会发现,这个算法很慢!
于是,我做了几个方面的优化:
(1)其实我们没必要计算所有定值对点的影响,远一些的定值,其影响微乎其微,可以忽略。所以,我先求出跟点最近的定值,然后计算周围3*3=9个定值对它的影响。
(2)我们可以减少循环里面的乘法次数。我们知道,第1个点到第3个点,和第3个点到第5个点,它们的距离是一样的。也就是说,计算它们距离的时候,必定会重复计算2的平方。其实我们可以用查表法代替乘法。假设我们的图形是600*500,我们可以建一个长600的数组,然后把0-599的平方都放进去。那么array[i]就代表了i的平方。寻址比乘法要快得多。
经过上面两个方法的优化,程序运行时间勉强可以接受。
下面是算法一的结果图:
使用算法一,比较明显的特点是原数组点比较突出。所以在目标源是点的场合,比较适用。另外,由于采用了优化方法一,我们可以看到图中有一些边界。暂时还没有很好的解决办法。
算法二、双线性插值
双线性插值是一种比较常见的图片放大算法。读者可以直接到百度搜索,查看它的详细原理,本文就不介绍了。
在应用上,我们每一步跟算法一是一致的,就是把二维数组均匀地放到表格里面。然后其他空白值,使用定值去插值。
如图所示:
V1、V2、V3、V4都是定值,而P是空白格子,根据上面的信息,我们在纸上面演算一下,很快就能得出公式,具体的代码是:
private double 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;
}
这就是P点的值。可以看到,每一个点,并不需要循环得出,所以我们只要30万次循环就能求出所有点的值(看上去好像挺多,但跟1620万比,已是两个数量级),时间上来说还是比较快的。
我们来看看算法二得出的结果:
可以看到,形状跟算法一基本一致,但很明显有一些十字交叉的图形。这一般来说并不是我们希望看到的。而且,算法二只考虑了一个点跟它周围四个定值的关系,完全忽略了其他定值的影响。
算法三、面距离反比+双线性插值
这是最复杂的一个算法,也是个人觉得效果最好的一个算法。可以看到,上面两种算法,都是把二维数组的数据作为点来看待的。但其实有的时候,二维数组的数据代表了一个区域的平均值。也就是说,原始的数据,看起来应该是这样的:
然后我们需要做的,就是对这个图进行平滑处理。
首先,我们用到算法一的方法。只是对于每一个点,我们不再借助54个定值求值了,我们用的,是整个图的数据。那么,你不用写代码,就应该意识到,每一个点的值,都要循环30万次求出来,然后,总共有30万个点要求,30万*30万……这个数我都不打算写出来了,打开程序算个几天都算不出来。
那怎么办!
我想到的办法是,原始数组不要扩大到600*500,而是先扩大几倍,例如说4倍,也就是说扩大到一个36*24的尺寸。但这样的尺寸实在太小,我们没办法实用啊!
(这是扩大6倍的图,4倍实在看不清)
但我们可以发现,上面的图除了比较小,基本轮廓是很不错的。我们再对这个图进行双线性插值不就行了吗!
我们根据需要展示的图尺寸确定XY各要增大的倍数,然后使用双线性插值,得到的结果如下:
使用此算法有一点需要注意,就是做面距离反比之后,表格里面可能一个原来数组的数都没有,而且范围变了。例如原来数组是15-35的数,做完面距离反比之后,可能表格里数据的范围是18-31。必须对数组进行扩展,然后再进行双线性插值。
三种算法的性能比较
三种算法使用的时间如下表所示:
算法一、点距离反比插值 | 1864ms |
算法二、双线性插值 | 18ms |
算法三、面距离反比+双线性插值 | 35ms |
可以看到,即使经过优化,算法一还是最耗时间。虽然算法二最快,但实用性不大。
数据渲染方法
我们可以看到,经过上面三种算法的插值计算之后,得到的无非是一个跟图形相同尺寸的二维数组,怎么把这个二维数组用颜色表示出来,变成云图呢?
其实方法很简单。我们先确定一个颜色列表,例如是#FA1207,#FAAD07,#ECFA07,#07FAA7,#07C4FA,#072FFA。这里六种颜色肯定不够用的,我们需要插值。对,颜色也要插值。可以按RGB分别计算。例如第一个颜色的G是0x12,第二个颜色G是0xAD,如果中间要插5个值,那每个值的增量就是(0xAD-0x12)/(5+1)了。
经过插值,我们可能得到一个有上百种颜色的列表。然后数组里的值需要跟这些颜色对应起来。我们可以求出数组的最小最大值,然后划分为跟颜色列表相同的数量,就能得到一个数据增量。借助此增量,可以算出每个值对应的颜色。
好,绘制云图的方法介绍到这里。源码下载
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)