绘制云图的三种算法(附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,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都有影响。那么,我们现在就来看三种填值的算法。

算法一、点距离反比插值

这种算法比较适合绘制点源产生影响的云图。第一步,我们先把二维数组均匀地放到表格里面,正如上面的表格所示。我们把目前已经填上的数据称为定值。

然后我们看其他空白格子。平面上的任何一个点都受到了定值的影响,但肯定不是所有定值的影响都一样。我们很容易看出,距离越近,影响越大,距离越远,影响越小。一般我们认为,定值对点的影响,跟距离的平方成反比。那么,我们可以用下面的公式,求出每一个空白格子的值:

\frac{\sum DijVij}{\sum Dij}

其中,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)了。

经过插值,我们可能得到一个有上百种颜色的列表。然后数组里的值需要跟这些颜色对应起来。我们可以求出数组的最小最大值,然后划分为跟颜色列表相同的数量,就能得到一个数据增量。借助此增量,可以算出每个值对应的颜色。

好,绘制云图的方法介绍到这里。源码下载

  • 16
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值