【问题标题】:Fast Interpolation / Resample of Numpy Array - PythonNumpy 数组的快速插值/重采样 - Python
【发布时间】:2020-12-16 21:57:58
【问题描述】:

目前,我已经编写了一些插入到管道中的 Python 代码。

传入的数据以形状为 (1,512,19,25) 的 numpy 数组的形式出现。我使用scipy.ndimage.interpolation.zoom 使阵列成形(1,512,38,50)。这可以通过一次调用函数来完成。基本上,它将每个 (19,25) 块的大小调整为 (38,50)。

在后面的代码中,当数据向另一个方向移动时,不同的数据再次在另一个方向 (38,50) 调整为 (19,25)。

一切都按照实施的方式进行,但是我发现这真的很慢。例如,我测试了 scipy.ndimage.interpolation.zoom 函数来调整图像文件的大小,它比 Matlab 的 imresize 函数慢得多。

在 Python 中有哪些更快的方法来做到这一点?

【问题讨论】:

  • 您对ndimage.zoom 使用什么样的插值(即order 参数是什么)?它只是最近邻还是您使用线性、双三次等?
  • 默认值。我没有改变它,所以我假设订单 3。
  • 双三次插值本质上是相当昂贵的。你能改用双线性或最近邻插值吗?
  • order=0 将为您提供最近邻,order=1 将为您提供双线性等。我希望其中任何一个都比order=3 快很多,但也可能更快方法。我主要是问最近邻或双线性是否适合您的需求。顺便说一句,我建议您编辑问题的标题 - 您所做的不仅仅是调整数组大小或重复数组,而是重新采样或插值。
  • 能否请您发布一个最简单的示例代码?我预计这将包括数组创建和缩放调用,以及您要使用的缩放参数。我想用那个确切的代码回答你的问题。

标签: python numpy scipy interpolation


【解决方案1】:

TLDR;查看 Skimage 的pyramid_gaussian。在 (512, 512) 的单个图像上,它显示了 0.3M 倍的加速。 (163 毫秒/471 纳秒 = 346072)。 Pillow-SIMD 执行 super-fast 重新采样/调整大小,但要求您在安装之前卸载 PIL、Pillow。它使用并行处理(单指令,多数据 - SIMD)和更好的算法,例如用顺序框替换基于卷积的高斯模糊。建议在设置单独的venv 后将其用于生产环境。


有多种方法可以对图像进行上采样和下采样。我将添加一些我使用过的方法的基准。当我遇到更多方法时,我会不断更新这个答案,以便可以作为其他人的参考。

#Utility function for plotting original, upsampled, and downsampled image

def plotit(img, up, down):
    fig, axes = plt.subplots(1,3, figsize=(10,15))
    axes[0].imshow(img)
    axes[1].imshow(up)
    axes[2].imshow(down)
    axes[0].title.set_text('Original')
    axes[1].title.set_text('Upsample')
    axes[2].title.set_text('Downsample')

IIUC,这有点像你的管道 -

from scipy.ndimage import zoom
from skimage.data import camera

img = camera() #(512,512)
up = zoom(img,2) #upsample image

#some code
...

down = zoom(up,0.5) #downsample the upsampled image

plotit(img, up, down)


方法和基准 (in no specific order)

1. Scipy zoom (order=3)

使用给定顺序的样条插值对数组进行缩放,在这种情况下,默认值为 order = 3。

%%timeit
#from scipy.ndimage import zoom
up = zoom(img,2)
down = zoom(up,0.5)

#163 ms ± 12.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

2. Scipy zoom (order=0)

使用给定阶数的样条插值对数组进行缩放,在本例中,阶数 = 0。

%%timeit
#from scipy.ndimage import zoom
up = zoom(img,2, order=0)
down = zoom(up,0.5, order=0)

#18.7 ms ± 950 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

3. Skimagepyramid_gaussian

在高斯金字塔中,后续图像使用高斯平均值(高斯模糊)加权并按比例缩小。对图像应用高斯模糊与使用高斯函数对图像进行卷积相同。模糊量取决于标准偏差大小 (sigma)。

%%timeit
#from skimage.transform import import pyramid_gaussian
up = pyramid_gaussian(img,2)
down = pyramid_gaussian(up,0.5)

#471 ns ± 30.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

4. Skimage pyramid_expandpyramid_reduce

图像金字塔是一种多尺度图像表示,其中图像经过反复平滑和二次采样。第一个函数对图像进行平滑然后上采样,而第二个函数做同样的事情,但不是下采样,这两个函数都默认使用 spline order=1。

%%timeit
#from skimage.transform import import pyramid_expand, pyramid_reduce
up = pyramid_expand(img,2)
down = pyramid_reduce(up,2)

#120 ms ± 3.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

5. Skimagerescale

按特定因子缩放图像。对放大或缩小 N 维图像执行样条插值(默认顺序为 1)。请注意,缩小图像尺寸时应启用抗锯齿功能以避免锯齿伪影。

%%timeit
#from skimage.transform import import rescale
up = rescale(img,2)
down = rescale(up,0.5)

#83 ms ± 3.69 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

6. PIL resize 与最近像素 filter 用于重采样

返回此图像的调整大小的副本。从输入图像中选择一个最近的像素。忽略所有其他输入像素。

%%timeit
#from PIL import Image
im = Image.fromarray(img)
up = im.resize((im.width*2, im.height*2),resample=Image.NEAREST)
down = up.resize((up.width//2, up.height//2),resample=Image.NEAREST)

#704 µs ± 29.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

7. PIL resize 和 BILINEAR filter 用于重采样

返回此图像的调整大小的副本。对于调整大小,使用线性插值对可能对输出值有贡献的所有像素计算输出像素值。对于其他变换,使用输入图像中 2x2 环境上的线性插值。

%%timeit
#from PIL import Image
im = Image.fromarray(img)
up = im.resize((im.width*2, im.height*2),resample=Image.BILINEAR)
down = up.resize((up.width//2, up.height//2),resample=Image.BILINEAR)

#10.2 ms ± 877 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

8. PIL resize 和 BICUBIC filter 用于重采样

返回此图像的调整大小的副本。对于调整大小,使用三次插值对可能对输出值有贡献的所有像素计算输出像素值。对于其他变换,使用输入图像中 4x4 环境上的三次插值。

%%timeit
#from PIL import Image
im = Image.fromarray(img)
up = im.resize((im.width*2, im.height*2),resample=Image.BICUBIC)
down = up.resize((up.width//2, up.height//2),resample=Image.BICUBIC)

#12.3 ms ± 326 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

9. PIL resize 和 Lanczos filter 进行重采样

返回此图像的调整大小的副本。使用高质量的 Lanczos 滤波器(截断的 sinc)对可能对输出值有贡献的所有像素计算输出像素值。

%%timeit
#from PIL import Image
im = Image.fromarray(img)
up = im.resize((im.width*2, im.height*2),resample=Image.LANCZOS)
down = up.resize((up.width//2, up.height//2),resample=Image.LANCZOS)

#15.7 ms ± 184 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

【讨论】:

  • 你真的检查过结果吗? pyramid_gaussian 只返回一个生成器,而不是重新缩放的图像。您需要实际迭代该生成器才能真正获得重新缩放的图像。然后我得到与 Scipy zoom order=0 相似的速度。
【解决方案2】:

检查https://python-pillow.org/pillow-perf/中的基准

TL;DR:对于大多数情况,Python 中最快的方法是使用pillow-simd

【讨论】:

    猜你喜欢
    • 2020-05-22
    • 2019-04-12
    • 2017-06-12
    • 2015-05-19
    • 1970-01-01
    • 2023-03-24
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多