最快的方法可以将数千张图像读入一个大的numpy阵列

我试图找到最快的方法来从目录中读取一堆图像到一个numpy数组中。 我的最终目标是计算统计数据,例如来自所有这些图像的像素的最大,最小和第n百分位数。 当来自所有图像的像素都在一个大的numpy数组中时,这是简单而快速的,因为我可以使用诸如.max.min等内置数组方法以及np.percentile函数。

下面是几个具有25个tiff图像(512x512像素)的例子。 这些基准来自在jupyter笔记本中使用%%timit 。 这些差异太小,不足以对25幅图像产生任何实际影响,但我打算在将来阅读数千幅图像。

# Imports
import os
import skimage.io as io
import numpy as np
  • 追加到列表中

    %%timeit
    imgs = []    
    img_path = '/path/to/imgs/'
    for img in os.listdir(img_path):    
        imgs.append(io.imread(os.path.join(img_path, img)))    
    ## 32.2 ms ± 355 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
  • 使用字典

    %%timeit    
    imgs = {}    
    img_path = '/path/to/imgs/'    
    for img in os.listdir(img_path):    
        imgs[num] = io.imread(os.path.join(img_path, img))    
    ## 33.3 ms ± 402 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
  • 对于上面的列表和字典方法,我尝试用各自的理解替换循环,具有类似的时间上的结果。 我也尝试预先分配字典键,所用时间没有显着差异。 要从列表中获取图像到大数组,我会使用np.concatenate(imgs) ,这只需要〜1 ms。

  • 在第一维上预分配一个numpy数组

    %%timeit    
    imgs = np.ndarray((512*25,512), dtype='uint16')    
    img_path = '/path/to/imgs/'    
    for num, img in enumerate(os.listdir(img_path)):    
        imgs[num*512:(num+1)*512, :] = io.imread(os.path.join(img_path, img))    
    ## 33.5 ms ± 804 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
  • 预先分配第三个维度的numpy

    %%timeit    
    imgs = np.ndarray((512,512,25), dtype='uint16')    
    img_path = '/path/to/imgs/'    
    for num, img in enumerate(os.listdir(img_path)):    
        imgs[:, :, num] = io.imread(os.path.join(img_path, img))    
    ## 71.2 ms ± 2.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
  • 我最初认为numpy预分配方法会更快,因为循环中没有动态变量扩展,但似乎并非如此。 我觉得最直观的方法是最后一个,每个图像沿着阵列的第三个轴占据一个单独的尺寸,但这也是最慢的。 额外的时间不是由于预分配本身,这只需要约1毫秒。

    我有三个关于这个问题的问题:

  • 为什么numpy的预分配方法不会比字典和列表解决方案更快?
  • 在成千上万的图像中读入一个大的numpy数组的最快方式是哪一种?
  • 我能否从外部的numpy和scikit-image中获益,以获得更快速的图像读取模块? 我试过plt.imread() ,但scikit-image.io模块速度更快。

  • A部分:访问和分配NumPy数组

    顺便说一下,元素按NumPy数组的行 - 次序存储,因此在每次迭代中沿着最后一个轴存储这些元素时,您做得正确。 这些会占用连续的内存位置,因此对于访问和分配值来说是最有效的。 因此,像np.ndarray((512*25,512), dtype='uint16')np.ndarray((25,512,512), dtype='uint16') np.ndarray((512*25,512), dtype='uint16')这样的初始化将在评论中提到最好。

    在将它们编译为用于测试时间的函数并以随机数组而不是图像馈送之后 -

    N = 512
    n = 25
    a = np.random.randint(0,255,(N,N))
    
    def app1():
        imgs = np.empty((N,N,n), dtype='uint16')
        for i in range(n):
            imgs[:,:,i] = a
            # Storing along the first two axes
        return imgs
    
    def app2():
        imgs = np.empty((N*n,N), dtype='uint16')
        for num in range(n):    
            imgs[num*N:(num+1)*N, :] = a
            # Storing along the last axis
        return imgs
    
    def app3():
        imgs = np.empty((n,N,N), dtype='uint16')
        for num in range(n):    
            imgs[num,:,:] = a
            # Storing along the last two axes
        return imgs
    
    def app4():
        imgs = np.empty((N,n,N), dtype='uint16')
        for num in range(n):    
            imgs[:,num,:] = a
            # Storing along the first and last axes
        return imgs
    

    计时 -

    In [45]: %timeit app1()
        ...: %timeit app2()
        ...: %timeit app3()
        ...: %timeit app4()
        ...: 
    10 loops, best of 3: 28.2 ms per loop
    100 loops, best of 3: 2.04 ms per loop
    100 loops, best of 3: 2.02 ms per loop
    100 loops, best of 3: 2.36 ms per loop
    

    这些时机证实了一开始提出的性能理论,尽管我预计最后一次安装的时间安排在app3app1时间安排之间,但是也许从最后一个安排到第一个安全轴访问和分配的时间间隔isn'线性。 更多关于这个问题的调查可能会很有趣(在这里跟进问题)。

    要示意说明,考虑我们正在存储图像数组,用x (图像1)和o (图像2)表示,我们将会有:

    App1:

    [[[x 0]
      [x 0]
      [x 0]
      [x 0]
      [x 0]]
    
     [[x 0]
      [x 0]
      [x 0]
      [x 0]
      [x 0]]
    
     [[x 0]
      [x 0]
      [x 0]
      [x 0]
      [x 0]]]
    

    因此,在内存空间中,它将是: [x,o,x,o,x,o..]紧跟行优先顺序。

    App2:

    [[x x x x x]
     [x x x x x]
     [x x x x x]
     [o o o o o]
     [o o o o o]
     [o o o o o]]
    

    因此,在内存空间中,它将是: [x,x,x,x,x,x...o,o,o,o,o..]

    App3:

    [[[x x x x x]
      [x x x x x]
      [x x x x x]]
    
     [[o o o o o]
      [o o o o o]
      [o o o o o]]]
    

    因此,在内存空间中,它将与前一个相同。


    B部分:作为数组从磁盘读取图像

    现在,在读取图像的一部分,我见过的OpenCV的imread要快得多。

    作为一项测试,我从wiki页面下载了Mona Lisa的图像,并在图像阅读上测试了性能 -

    import cv2 # OpenCV
    
    In [521]: %timeit io.imread('monalisa.jpg')
    100 loops, best of 3: 3.24 ms per loop
    
    In [522]: %timeit cv2.imread('monalisa.jpg')
    100 loops, best of 3: 2.54 ms per loop
    

    在这种情况下,大部分时间将花在从磁盘读取文件上,我不会过多担心填充列表的时间。

    在任何情况下,这里都是一个比较四种方法的脚本,没有从磁盘读取实际图像的开销,而只是从内存中读取一个对象。

    import numpy as np
    import time
    from functools import wraps
    
    
    x, y = 512, 512
    img = np.random.randn(x, y)
    n = 1000
    
    
    def timethis(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            r = func(*args, **kwargs)
            end = time.perf_counter()
            print('{}.{} : {} milliseconds'.format(func.__module__, func.__name__, (end - start)*1e3))
            return r
        return wrapper
    
    
    @timethis
    def static_list(n):
        imgs = [None]*n
        for i in range(n):
            imgs[i] = img
        return imgs
    
    
    @timethis
    def dynamic_list(n):
        imgs = []
        for i in range(n):
            imgs.append(img)
        return imgs
    
    
    @timethis
    def list_comprehension(n):
        return [img for i in range(n)]
    
    
    @timethis
    def numpy_flat(n):
        imgs = np.ndarray((x*n, y))
        for i in range(n):
            imgs[x*i:(i+1)*x, :] = img
    
    static_list(n)
    dynamic_list(n)
    list_comprehension(n)
    numpy_flat(n)
    

    结果显示:

    __main__.static_list : 0.07004200006122119 milliseconds
    __main__.dynamic_list : 0.10294799994881032 milliseconds
    __main__.list_comprehension : 0.05021800006943522 milliseconds
    __main__.numpy_flat : 309.80870099983804 milliseconds
    

    显然你最好的选择是列表理解,但是即使填充了一个numpy数组,它的310毫秒读取1000张图像(从内存)。 所以再次,开销将是磁盘读取。

    为什么numpy比较慢?

    这是numpy存储阵列的方式。 如果我们修改python list函数将列表转换为numpy数组,那么时间是相似的。

    修改的函数返回值:

    @timethis
    def static_list(n):
        imgs = [None]*n
        for i in range(n):
            imgs[i] = img
        return np.array(imgs)
    
    
    @timethis
    def dynamic_list(n):
        imgs = []
        for i in range(n):
            imgs.append(img)
        return np.array(imgs)
    
    
    @timethis
    def list_comprehension(n):
        return np.array([img for i in range(n)])
    

    和时间结果:

    __main__.static_list : 303.32892100022946 milliseconds
    __main__.dynamic_list : 301.86925499992867 milliseconds
    __main__.list_comprehension : 300.76925699995627 milliseconds
    __main__.numpy_flat : 305.9309459999895 milliseconds
    

    所以它只是一个块状的东西,它需要更多的时间,并且它相对于数组大小是恒定的...

    链接地址: http://www.djcxy.com/p/96321.html

    上一篇: Fastest approach to read thousands of images into one big numpy array

    下一篇: React router 4 history.listen never fires