高阶 numpy 数组快速插值(高阶快插)算法探讨

在科学计算和数据处理领域,数据插值是我们经常面对的问题。尽管 numpy 自身提供了 numpy.interp 插值函数,但只能做一维线性插值,因此,在实际工作中,我们更多地使用 scipy 的 interpolate 子模块。关于 numpy 和 scipy 的关系,有兴趣的话,可以参考拙作《数学建模三剑客MSN》

遗憾的是,scipy.interpolate 只提供了一维和二维的插值算法,而大名鼎鼎的商业软件 Matlab 则有三维插值函数可用。事实上,三维乃至更高阶的插值需求还是挺普遍的。比如,三维体数据绘制时,为了增强显示效果,让数据体看起来更细腻,三维插值是必不可少的。下图是三维数据插值前后的3D显示效果对比(使用 pyopengl 绘制)。
在这里插入图片描述三维乃至更高阶的数据插值,简称高阶快插,通常都是线性插值。最自然的想法,是循环调用 scipy.interpolate 的 interp1d 或 interp2d 函数实现三维插值,但是循环调用的效率低得无法忍受,完全体现不出 numpy 的广播和矢量化的特点。

网上有人提出在_fitpack模块中使用_spline属性和低级别的_bspleval()函数,以实现高阶快插。详情见《python – 3D阵列上的快速插值》。文中的代码我费了九牛二虎之力仍然没有跑通。

那么,能否借助 numpy 的广播和矢量化的特点,实现高阶快插呢?经过验证,这个思路是完全可行的。我们先从一维线型插值开始讨论。

  1. 使用元素重复函数 repeat() 将长度为 n 的一维数组扩展成长度为 2n-1 的数组,排在偶数位置的元素是前面元素的重复;
  2. 所有偶数位置的元素减去前后元素差的一半。

我们来验证一下:

import numpy as np
>>> a = np.arange(5, dtype=np.float)
>>> a
array([0., 1., 2., 3., 4.])
>>> a = a.repeat(2)[:-1]
>>> a
array([0., 0., 1., 1., 2., 2., 3., 3., 4.])
>>> a[1::2] += (a[2::2]-a[1::2])/2
>>> a
array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. ])

结果符合预期,完全矢量化!进一步验证,还可以证明该方法对 numpy.nan 类型的数据处理方式符合我们的期望。

>>> a = np.arange(5, dtype=np.float)
>>> a[2] = np.nan
>>> a
array([ 0.,  1., nan,  3.,  4.])
>>> a = a.repeat(2)[:-1]
>>> a
array([ 0.,  0.,  1.,  1., nan, nan,  3.,  3.,  4.])
>>> a[1::2] += (a[2::2]-a[1::2])/2
>>> a
array([0. , 0.5, 1. , nan, nan, nan, 3. , 3.5, 4. ])

接下来,我们就可以尝试用这个思路实现三维数组的线性插值了。代码如下:

import numpy as np

def interp3d(arr_3d):
    """三维数组线性插值
    
    arr_3d      - numpyp.ndarray类型的三维数组
    """
    
    layers, rows, cols = arr_3d.shape
    
    arr_3d = arr_3d.repeat(2).reshape((layers*rows, -1))
    arr_3d = arr_3d.repeat(2, axis=0).reshape((layers, -1))
    arr_3d = arr_3d.repeat(2, axis=0).reshape((layers*2, rows*2, cols*2))[:-1, :-1, :-1]
    
    
    arr_3d[:,:,1::2] += (arr_3d[:,:,2::2]-arr_3d[:,:,1::2])/2
    arr_3d[:,1::2,:] += (arr_3d[:,2::2,:]-arr_3d[:,1::2,:])/2
    arr_3d[1::2,:,:] += (arr_3d[2::2,:,:]-arr_3d[1::2,:,:])/2
    
    return arr_3d

if __name__ == '__main__':
    import time
    
    arr_3d = np.random.randn(100, 200, 300)
    print(u'插值前数组的shape:', arr_3d.shape)
    
    t0 = time.time()
    arr_3d = interp3d(arr_3d)
    t1 = time.time()
    
    print(u'插值后数组的shape:', arr_3d.shape)
    print(u'耗时%.03f秒'%(t1-t0,))

运行结果如下:

PS D:\XufiveGit\interp3d> py -3 .\test.py
插值前数组的shape: (100, 200, 300)
插值后数组的shape: (199, 399, 599)
耗时1.281秒

在我的认知范围内,这应该是目前最快的高阶快插算法了。虽殚精竭虑而后得,亦弗敢专也,必以分享于同好。

可以使用`scipy.interpolate`库中的`NearestNDInterpolator`进行最近邻插值。具体步骤如下: 1. 导入库 `import numpy as np, scipy.interpolate` 2. 定义原始数组 `arr` 和目标大小 `new_size`,假设原始数组是二维的。 ``` arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) new_size = (4, 4) ``` 3. 计算每个像素点在新图像中的坐标。 ``` x, y = np.meshgrid(np.arange(arr.shape[1]), np.arange(arr.shape[0])) new_x = np.linspace(0, arr.shape[1] - 1, new_size[1]) new_y = np.linspace(0, arr.shape[0] - 1, new_size[0]) new_x, new_y = np.meshgrid(new_x, new_y) ``` 4. 将坐标和像素值打包成一维数组。 ``` points = np.column_stack((x.ravel(), y.ravel())) values = arr.ravel() ``` 5. 创建最近邻插值函数。 ``` interpolator = scipy.interpolate.NearestNDInterpolator(points, values) ``` 6. 通过插值函数计算新图像的像素值。 ``` new_values = interpolator(new_x.ravel(), new_y.ravel()) new_arr = new_values.reshape(new_size) ``` 完整代码如下: ```python import numpy as np import scipy.interpolate # 定义原始数组和目标大小 arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) new_size = (4, 4) # 计算每个像素点在新图像中的坐标 x, y = np.meshgrid(np.arange(arr.shape[1]), np.arange(arr.shape[0])) new_x = np.linspace(0, arr.shape[1] - 1, new_size[1]) new_y = np.linspace(0, arr.shape[0] - 1, new_size[0]) new_x, new_y = np.meshgrid(new_x, new_y) # 将坐标和像素值打包成一维数组 points = np.column_stack((x.ravel(), y.ravel())) values = arr.ravel() # 创建最近邻插值函数 interpolator = scipy.interpolate.NearestNDInterpolator(points, values) # 通过插值函数计算新图像的像素值 new_values = interpolator(new_x.ravel(), new_y.ravel()) new_arr = new_values.reshape(new_size) print(new_arr) ``` 输出结果: ``` [[1 2 2 3] [4 5 5 6] [4 5 5 6] [7 8 8 9]] ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天元浪子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值