我正在尝试找到最快的方法来将目录中的一堆图像读取到 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
- 附加到列表
%%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 毫秒。
- 沿第一个维度预分配一个 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
模块更快。
原文由 joelostblom 发布,翻译遵循 CC BY-SA 4.0 许可协议
A 部分:访问和分配 NumPy 数组
顺便说一句,对于 NumPy 数组,元素以行优先顺序存储,在每次迭代中沿着最后一个轴存储这些元素时,您做的是正确的事情。这些将占据连续的内存位置,因此对于访问和分配值来说是最有效的。因此像
np.ndarray((512*25,512), dtype='uint16')
或np.ndarray((25,512,512), dtype='uint16')
这样的初始化效果最好,正如评论中提到的那样。在将它们编译为用于测试时序的函数并输入随机数组而不是图像后 -
时间 -
这些时间确认了开始时提出的性能理论,尽管我预计最后一次设置的时间介于
app3
和app1
之间,但也许是去的效果从最后一个轴到第一个用于访问和分配的轴不是线性的。对此进行更多调查可能很有趣( 在这里跟进问题)。为了示意性地说明,考虑我们正在存储图像数组,由
x
(图像 1)和o
(图像 2)表示,我们将有:应用程序 1:
因此,在内存空间中,它将是:
[x,o,x,o,x,o..]
按照行优先顺序。应用程序2:
因此,在内存空间中,它将是:
[x,x,x,x,x,x...o,o,o,o,o..]
。应用程序 3:
因此,在内存空间中,它将与前一个相同。
B 部分:从磁盘读取图像作为数组
现在,关于读取图像的部分,我看到 OpenCV 的
imread
要快得多。作为测试,我从维基页面下载了蒙娜丽莎的图像并测试了图像读取的性能 -