【问题标题】:Fastest approach to read thousands of images into one big numpy array将数千张图像读入一个大 numpy 数组的最快方法
【发布时间】:2017-10-20 01:44:36
【问题描述】:

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

以下是 25 张 tiff 图像(512x512 像素)的几个示例时序。这些基准来自在 jupyter-notebook 中使用 %%timit。差异太小,仅对 25 张图像没有任何实际意义,但我打算在未来阅读数千张图像。

# Imports
import os
import skimage.io as io
import numpy as np
  1. 添加到列表

    %%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)
    
  2. 使用字典

    %%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 毫秒。

  1. 沿第一维预分配一个 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)
    
  2. 沿第三维预分配一个 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 毫秒。

我对此有三个问题:

  1. 为什么 numpy 预分配方法不比字典和列表解决方案快?
  2. 将数千张图像读入一个大型 numpy 数组的最快方法是什么?
  3. 我可以从 numpy 和 scikit-image 之外寻找更快的模块来读取图像吗?我试过plt.imread(),但scikit-image.io 模块更快。

【问题讨论】:

  • 您是否尝试过初始化(25, 512, 512) 数组?第一个维度是外部维度。第一个列表方法中的np.array(imgs) 会产生这种形状。这 33 毫秒的大部分时间是加载,而不是存储。为了测试这一点,尝试在不累积数组的情况下加载。
  • 谢谢@hpaulj!您关于外部尺寸的提示以及我花费的大部分时间都来自开销是有帮助的。我尝试了 300 个更高分辨率的 tiff(1024x1024 像素),而 numpy 外部维度方法([300, 1024, 1024] 或 [1024, 300, 1024])现在是最快的(~1s)。其次是列表和字典解决方案(~1.7s),numpy inner(?) 维度 [1024, 1024, 300] 是最后一个(~4.6s)。如果您添加答案,我可以添加这些基准并接受它。
  • @hpaulj 如果您可以添加一个链接来更详细地说明第一个(和第二个?)维度是外部维度的含义,我将不胜感激。我在搜索中找不到任何相关内容。
  • 查找order,如C order v F order
  • 完全跳过加载阶段怎么样?选项 1:numpy.memmap / numpy.load(..., mmap_mode="r") 选项 2:blaze

标签: python image performance numpy


【解决方案1】:


我认为您可以尝试使用 glob.glob,应该有什么帮助

image_list = []
with open('train_train_.csv', 'w') as csv_file:
    csv_writer = csv.writer(csv_file, delimiter ='-')

    for filename in glob.glob(r'C:\your path to\file*.png'):

        img = cv2.imread(filename)
        image_list.append(img)
        csv_writer.writerow(img)
        print(img)

干杯

【讨论】:

    【解决方案2】:

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

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

    在将它们编译为函数以测试时序并输入随机数组而不是图像之后 -

    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 之间,但可能是从最后一个到第一个的效果访问和分配的轴不是线性的。对此进行更多调查可能会很有趣 (follow up question here)。

    为了明确说明,假设我们正在存储图像数组,用x(图1)和o(图2)表示,我们会​​:

    应用程序1:

    [[[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..] 以下行主要顺序。

    应用程序2:

    [[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 页面下载了蒙娜丽莎的图像并测试了图像读取性能 -

    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
    

    【讨论】:

    • 感谢您的回复!你能解释一下为什么阅读[x,x,x,...o,o,o,...] 比阅读[x,o,x,o,x,o...] 更快吗? xo 将是相同类型的对象,并且是像素强度的整数,对吧?
    • 另外,您能否扩展“在每次迭代中沿最后一个轴存储这些元素时,您正在做正确的事情”?您的示例是否表明最快的替代方法是沿第一个轴/维度存储元素,即xnp.ndarray([x, y, z]) 中?还是“元素”不是指单独的图像?
    • @JoelOstblom 第一次查询-正如我在帖子中指出并再次重复:These would occupy contiguous memory locations and as such would be the most efficient for accessing and assigning values into. ,因为对于[x,x,x,...o,o,o,...],我们存储的是image1,即x's,然后是image2 ,即o's下一个。到第二个查询 - 使用 cmets 编辑代码。
    • 我明白了,我没有意识到 xo 指的是两个不同的图像,而不是同一个图像的 x、y 坐标。您与chapter 2.3 in Oliphant's "Guide to NumPy" 一起编辑,帮助我理解,谢谢!我最初认为单独的图像应该连续存储在内存中,但它实际上是每个图像的坐标,因为这是为了从内存中获取图像需要访问的内容。我用 Fortran 有序数组尝试了你的例子,结果与我期望的结果一致......
    • ...他们最快变化的索引是第一个而不是最后一个。但是,有一件事让我仍然感到困惑。 Oliphant 写道(对于 C 有序数组):“......要按顺序在计算机内存中移动,最后一个索引首先递增,然后是倒数第二个索引,依此类推”。但是,访问A = np.empty((512,25,512))B = np.empty((25,512,512)) 一样快。与B 中的倒数和倒数相比,访问A 的信息在倒数和倒数第三个索引中是否会更慢?
    【解决方案3】:

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

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

    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 数组,读取 1000 张图像(从内存中)也只需 310 毫秒。同样,开销将是磁盘读取。

    为什么 numpy 比较慢?

    这是 numpy 在内存中存储数组的方式。如果我们修改python列表函数,将列表转换为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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-16
      • 1970-01-01
      • 1970-01-01
      • 2014-04-17
      • 1970-01-01
      • 1970-01-01
      • 2022-01-09
      • 2012-11-27
      相关资源
      最近更新 更多