如何在Numpy中为每个频道将图像列表加载到数组中?

时间:2016-05-22 21:09:41

标签: python numpy scikit-image

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

我试过了

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中的数组中,所以必须有一些东西我不见了?

3 个答案:

答案 0 :(得分:1)

numpy.fromiter不支持同时创建数组,然后将它们作为元组返回(要解压缩到X,y中)有可能在np中执行此操作但是据我所知,您可能需要将迭代器拆分为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

答案 1 :(得分:1)

np.fromiter要求您说明dtype。如果使用dtype=float,则iterable中的每个值都必须为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返回1D数组,因此您需要重新整形:

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:]

关于question in the comments

  

如果我想对L和ab做额外处理,我会在哪里做?

我相信将加载与数据处理分开。通过保持两个函数不同,您可以将来自不同源的不同数据传递给相同的处理函数。如果将数据的加载和处理(例如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)。

答案 2 :(得分:0)

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

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

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)

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

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'))

生成1d结构化数组:

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)

https://stackoverflow.com/a/12473478/901925

中也描述了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的优势之前,我必须将运行时间推到1秒。