如何将图像列表加载到 Numpy 中每个通道的数组中?

How do I load a list of images into an array for each channel in Numpy?

我想要一个形状为 (n_samples、n_cols、n_rows、n_channels 的数组 X。我想要一个形状为 (n_sample,n_cols,n_rows,n_channels)

的数组 y

我试过了

import glob
from skimage import io, color
import numpy as np

def loadfunc(files)
    for fl in files:
        img = color.rgb2lab(io.imread(fl))
        L = img[:,:,:1]
        ab = img[:,:,1:]
        yield L,ab

X,y = np.fromiter(loadfunc(glob.glob('path/to/images/*.png')),float)

我收到此错误:ValueError:设置带有序列的数组元素。

我认为这一定是一个有点常见的操作 - 任何时候有人想将图像数据加载到 numpy 中的数组中,所以我一定缺少某些东西?

numpy.fromiter does not support simultaneous array creation and then return them as a tuple, (to be unpacked into X,y) It is possible there is a way to do this in np but not to my knowledge, you may need to split the iterator into a tee 改为

# the built in map in python 3 uses iteration,
# uncomment the added imap import if you are using python 2
from itertools import tee #, imap as map

from operator import itemgetter

iter_a, iter_b = tee(loadfunc(glob.glob('path/to/images/*.png')))

X = np.fromiter(map(itemgetter(0),iter_a), float) #array from the first elements
y = np.fromiter(map(itemgetter(1),iter_b), float) #array from the second elements

np.fromiter 要求您声明数据类型。如果您使用 dtype=float,那么可迭代对象中的每个值都必须是浮点数。如果您从 loadfunc 生成单个 NumPy 数组,您可以使用它们的 flat 属性来获取平展数组值的迭代器,这些值可以与 itertools.chain.from_iterable 连接,然后传递给 np.fromiter :

def loadfunc(files):
    for fl in files:
        img = skcolor.rgb2lab(skio.imread(fl)[..., :3])
        yield img

arrs = loadfunc(files)
Z = np.fromiter(IT.chain.from_iterable([arr.flat for arr in arrs]), dtype=float)

由于 np.fromiter returns 是一维数组,因此您需要对其进行整形:

Z = Z.reshape(len(files), h, w, n)

请注意,这依赖于每个具有相同形状的图像。 最后,将 L 值加载到 X 并将 ab 值加载到 y:

X = Z[..., :1]
y = Z[..., 1:]

import glob
import itertools as IT
import numpy as np
import skimage.io as skio
import skimage.color as skcolor

def loadfunc(files):
    for fl in files:
        img = skcolor.rgb2lab(skio.imread(fl)[..., :3])
        yield img

files = glob.glob('path/to/images/*.png')
arrs = loadfunc(files)
first = next(arrs)
h, w, n = first.shape

Z = np.fromiter(IT.chain.from_iterable(
    [first.flat] + [arr.flat for arr in arrs]), dtype=float)
Z = Z.reshape(len(files), h, w, n)
X = Z[..., :1]
y = Z[..., 1:]

关于 :

If I wanted to do extra processing to L and ab, where would I do that?

我相信将加载与数据处理分开。通过保持这两个功能的不同,您可以将来自不同来源的不同数据传递到相同的处理功能。如果您将数据的加载和处理(例如 ab 值的 KNN 分类)都放入 loadfunc 中,那么如果不从文件中加载数据,就无法重用 KNN 分类代码。


如果你允许我们改变坐标轴的顺序 (n_samples, n_cols, n_rows, n_channels)(n_cols, n_rows, n_channels, n_samples), 然后可以使用 np.stack:

简化代码
import glob
import numpy as np
import skimage.io as skio
import skimage.color as skcolor

def loadfunc(files):
    for fl in files:
        img = skcolor.rgb2lab(skio.imread(fl)[..., :3])
        yield img

files = glob.glob('path/to/images/*.png')
Z = np.stack(loadfunc(files), axis=-1)
X = Z[..., :1, :]
Y = Z[..., 1:, :]

此代码更简单,因此比上面的代码(使用 np.fromiter)更可取。

通常当我们用迭代创建一个数组时,我们要么收集列表中的值,然后从中创建数组。或者我们分配一个空列表并为插槽赋值。

这是一种进行赋值的方法,其中生成器 returns 一个数组元组:

def mk_array(N):
    for i in range(N):
        img=np.ones((2,3,3),int)
        L=img[:,:,:1]*i
        ab=img[:,:,1:].astype(float)*i/10
        yield L,ab

我把一个设为整数数组,另一个设为浮点数数组。这减少了将它们连接成一个的诱惑。

In [157]: g=mk_array(4)

In [158]: for i,v in enumerate(g):
    print(v[0].shape,v[1].shape)
   .....:     
(2, 3, 1) (2, 3, 2)
(2, 3, 1) (2, 3, 2)
(2, 3, 1) (2, 3, 2)
(2, 3, 1) (2, 3, 2)

让我们分配正确形状的目标数组;这里我把迭代轴放在第三位,但它可以在任何地方

In [159]: L, ab = np.empty((2,3,4,1),int), np.empty((2,3,4,2),float)

In [160]: for i,v in enumerate(g):
    L[...,i,:], ab[...,i,:] = v

我猜这与任何 fromiterstack 替代方案一样快。当通过读取文件生成组件时,该步骤必然是最昂贵的 - 比迭代机制或数组副本更昂贵。

================

如果迭代器返回一个标量元组,我们可以使用fromiter:

def mk_array1(N):
    for i in range(N):
        img=np.ones((2,3,3),int)
        L=img[:,:,:1]*i
        ab=img[:,:,1:].astype(float)*i/10
        for i,j in zip(L.ravel(),ab.ravel()):
            yield i,j

In [184]: g=mk_array1(2)

In [185]: V=np.fromiter(g,dtype=('i,f'))

生成一维结构化数组:

In [186]: V
Out[186]: 
array([(0, 0.0), (0, 0.0), (0, 0.0), (0, 0.0), (0, 0.0), (0, 0.0),
       (1, 0.10000000149011612), (1, 0.10000000149011612),
       (1, 0.10000000149011612), (1, 0.10000000149011612),
       (1, 0.10000000149011612), (1, 0.10000000149011612)], 
      dtype=[('f0', '<i4'), ('f1', '<f4')])

可重塑,数组以字段名分隔:

In [187]: V['f0']
Out[187]: array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], dtype=int32)

In [188]: V.reshape(2,2,3)['f0']
Out[188]: 
array([[[0, 0, 0],
        [0, 0, 0]],

       [[1, 1, 1],
        [1, 1, 1]]], dtype=int32)

In [189]: V.reshape(2,2,3)['f1']
Out[189]: 
array([[[ 0. ,  0. ,  0. ],
        [ 0. ,  0. ,  0. ]],

       [[ 0.1,  0.1,  0.1],
        [ 0.1,  0.1,  0.1]]], dtype=float32)

================

如果我定义一个更复杂的 dtype,每个字段都有一个数组会怎么样:

In [200]: dt=np.dtype([('f0',int,(2,3,1)),('f1',float,(2,3,2))])

In [201]: g=mk_array(2)   # the original generator

In [202]: V=np.fromiter(g,dtype=dt)

In [203]: V['f0']
Out[203]: 
array([[[[0],
         [0],
         [0]],
        ....

        [[1],
         [1],
         [1]]]])

In [204]: _.shape
Out[204]: (2, 2, 3, 1)

中也描述了 fromiter 对复合 dtype 的使用

实际上,这是从元组列表构建结构化数组的常用方法的变体。我不止一次使用表达式:

np.array([tuple(x)  for x in something], dtype=dt)

总而言之,我们可以使用两种创建 2 个数组的方法:

def foo1(N):
    g = mk_array(N)                                       
    L, ab = np.empty((N,2,3,1),int), np.empty((N,2,3,2),float)
    for i,v in enumerate(g):
        L[i,...], ab[i,...] = v
    return L, ab

def foo2(N):
    dt=np.dtype([('f0',int,(2,3,1)),('f1',float,(2,3,2))])
    g = mk_array(N)
    V=np.fromiter(g, dtype=dt)
    return V['f0'], V['f1']

对于范围广泛的N,这两个函数花费的时间几乎相同。在开始 foo1.

的优势之前,我必须将 运行 次推到 1s