向量化循环操作

时间:2019-08-06 11:08:25

标签: python numpy vectorization numpy-ndarray

我有以下与for循环一起使用的操作。有人可以建议一种使用numpy将操作向量化的方法吗?

# rgb is a 3 channel image  
# points are computed using vector mult op (same size as rgb image)
# dtypes - rgb is uint8 and points is float
buffer = []
for v in range(rgb.shape[1]):
        for u in range(rgb.shape[0]):
            X,Y,Z = points[u,v,:]
            r,g,b = rgb[u,v,:] 
            buffer.append(struct.pack('ffffBBBBIII', X,Y,Z,0.0,
                                                     b,g,r,
                                                     255,0,0,0))

我想压缩以上操作并获取缓冲区。任何指针都会有所帮助

3 个答案:

答案 0 :(得分:1)

如果结构类型(C类型)和numpy数值类型之间有对应关系,这应该非常简单。 struct的文档为here,而numpy的文档为here。相关的转换为:

  • 'f'-> np.single(Python没有等效类型)
  • 'B'-> np.ubyte
  • 'I'-> np.uintc

您可以通过创建custom dtype来将输出创建为值的数组,就像struct可以做到的那样:

dt = np.dtype([(c, np.single) for c in 'XYZW'] +
              [(c, np.ubyte) for c in 'RGBA'] +
              [('', np.intc, 3)])

为每个通道(例如[('X', np.single), ('Y', np.single), ...)创建单独字段而不是为所有通道(例如[('XYZW', np.single, 4), ...)创建单个字段的原因是您希望能够以统一的步幅访问数组。您将不会分配给其的空白部分可以是每个元素中的单个块:('zeros', np.intc, 3)

您可以具有其他dtype,以提供所需的结果。例如,您可以命名您的字段,或将其拆分为各个通道。在视图中,我建议您在写完输出数组后这样做,以简化处理。

现在您有了dtype,用它创建一个数组:

output = np.zeros(rgb.shape[:2], dtype=dt)

现在,您可以将dt.fields属性与output.setfield结合使用来存储字段:

for name, plane in zip('XYZ', np.moveaxis(points, -1, 0)):
    tp, offset, *_ = dt.fields[name]
    output.setfield(plane, tp, offset)

for name, plane in zip('RGB', np.moveaxis(rgb, -1, 0)):
    tp, offset, *_ = dt.fields[name]
    output.setfield(plane, tp, offset)

tp, offset, *_ = dt.fields['A']
output.setfield(255, tp, offset)

您可以使用itertoools.chain缩短到一个循环:

from itertools import chain

for name, plane in zip('XYZRGBA', chain(np.moveaxis(points, -1, 0),
                                        np.moveaxis(rgb, -1, 0),
                                        [255])):
    tp, offset, *_ = dt.fields[name]
    output.setfield(plane, tp, offset)

请注意,循环在这里并不是很昂贵。它仅经历七个迭代。结果的每个元素都是结构调用所创建形式的正确缓冲区。您可以通过遍历output数组来丢弃形状信息。

结果是一个具有自定义dtype的数组,其结构格式规范与'ffffBBBBIII'等效。每个元素canis都是一个标量,可以通过字段名称进行索引:

>>> output[0, 0]['W']
0.0

您可以根据需要在数组中创建备用视图,例如,将值分组为类别或类似内容:

>>> dt2 = np.dtype([('p', np.single, 4), ('i', np.ubyte, 4), ('z', np.intc, 3)]
>>> output2 = output.view(dtype=dt2)
>>> output2[0, 0]['p']
array([0.501182 , 0.7935149, 0.9981835, 0.       ], dtype=float32)  # Random example data

此视图不复制数据,只是以不同的方式解释现有缓冲区。在内部,该表示形式仍然是您尝试使用struct实现的压缩版本。

答案 1 :(得分:0)

我可以这样想:

def pack(points, rgb):
    X, Y, Z = points.transpose(2, 0, 1)
    r, g, b = rgb.transpose(2, 0, 1)
    rec = np.rec.fromarrays([X,Y,Z,r,g,b])
    def fn(point):
        X, Y, Z, r, g, b = point
        return struct.pack('ffffBBBBIII', X, Y, Z, 0.0, b, g, r, 255, 0, 0, 0)
    return np.vectorize(fn)(rec)

答案 2 :(得分:-1)

您可以将structured arrray与dp一起使用

- app
   |
   |- Module 1
   |
   |- Module 2
   |
   |- Module 3

然后,您可以调用数组的方法ndarray.tobytes()来获取缓冲区。

有关初始化,请参见herehere