在numpy数组上的矢量化操作

时间:2015-02-14 23:27:59

标签: python opencv numpy vectorization

我有一个包含许多三维numpy数组的numpy数组,其中每个子元素都是灰度图像。我想使用numpy的vectorize对数组中的每个图像应用仿射变换。

以下是重现问题的最小示例:

import cv2
import numpy as np
from functools import partial

# create four blank images
data = np.zeros((4, 1, 96, 96), dtype=np.uint8)

M = np.array([[1, 0, 0], [0, 1, 0]], dtype=np.float32) # dummy affine transformation matrix
size = (96, 96) # output image size

现在我想将数据中的每个图像传递给cv2.warpAffine(src, M, dsize)。在我进行矢量化之前,我首先创建一个绑定M和dsize的部分函数:

warpAffine = lambda M, size, img : cv2.warpAffine(img, M, size) # re-order function parameters
partialWarpAffine = partial(warpAffine, M, size)

vectorizedWarpAffine = np.vectorize(partialWarpAffine)
print data[:, 0].shape # prints (4, 96, 96)
vectorizedWarpAffine(data[:, 0])

但是这会输出:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/dist-packages/numpy/lib/function_base.py", line 1573, in __call__
    return self._vectorize_call(func=func, args=vargs)
  File "/usr/lib/python2.7/dist-packages/numpy/lib/function_base.py", line 1633, in _vectorize_call
    ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args)
  File "/usr/lib/python2.7/dist-packages/numpy/lib/function_base.py", line 1597, in _get_ufunc_and_otypes
    outputs = func(*inputs)
  File "<stdin>", line 1, in <lambda>
TypeError: src is not a numpy array, neither a scalar

我做错了什么 - 为什么我不能在numpy数组上进行操作?

1 个答案:

答案 0 :(得分:4)

问题在于,仅仅使用partial,就vectorize而言,它不会使其他参数的存在消失。 partial对象底层的函数将是vectorizedWarpAffine.pyfunc,它将跟踪调用vectorizedWarpAffine.pyfunc.func时您希望它使用的任何预绑定参数(这仍然是一个多论证的功能)。

你可以这样看到(在import inspect之后):

In [19]: inspect.getargspec(vectorizedWarpAffine.pyfunc.func)
Out[19]: ArgSpec(args=['M', 'size', 'img'], varargs=None, keywords=None, defaults=None)

要解决此问题,您可以使用excluded选项np.vectorize来说明在包装矢量化行为时要忽略哪些参数(正面或关键字):

vectorizedWarpAffine = np.vectorize(partialWarpAffine, 
                                    excluded=set((0, 1)))

当我进行此更改时,代码现在似乎实际上执行了矢量化函数,但它在imagewarp.cpp代码中遇到实际错误,可能是由于此测试数据上的一些错误数据假设:

In [21]: vectorizedWarpAffine(data[:, 0])
OpenCV Error: Assertion failed (cn <= 4 && ssize.area() > 0) in remapBilinear, file -------src-dir-------/opencv-2.4.6.1/modules/imgproc/src/imgwarp.cpp, line 2296
---------------------------------------------------------------------------
error                                     Traceback (most recent call last)
<ipython-input-21-3fb586393b75> in <module>()
----> 1 vectorizedWarpAffine(data[:, 0])

/home/ely/anaconda/lib/python2.7/site-packages/numpy/lib/function_base.pyc in __call__(self, *args, **kwargs)
   1570             vargs.extend([kwargs[_n] for _n in names])
   1571 
-> 1572         return self._vectorize_call(func=func, args=vargs)
   1573 
   1574     def _get_ufunc_and_otypes(self, func, args):

/home/ely/anaconda/lib/python2.7/site-packages/numpy/lib/function_base.pyc in _vectorize_call(self, func, args)
   1628         """Vectorized call to `func` over positional `args`."""
   1629         if not args:
-> 1630             _res = func()
   1631         else:
   1632             ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args)

/home/ely/anaconda/lib/python2.7/site-packages/numpy/lib/function_base.pyc in func(*vargs)
   1565                     the_args[_i] = vargs[_n]
   1566                 kwargs.update(zip(names, vargs[len(inds):]))
-> 1567                 return self.pyfunc(*the_args, **kwargs)
   1568 
   1569             vargs = [args[_i] for _i in inds]

/home/ely/programming/np_vect.py in <lambda>(M, size, img)
     10 size = (96, 96) # output image size
     11 
---> 12 warpAffine = lambda M, size, img : cv2.warpAffine(img, M, size) # re-order function parameters
     13 partialWarpAffine = partial(warpAffine, M, size)
     14 

error: -------src-dir-------/opencv-2.4.6.1/modules/imgproc/src/imgwarp.cpp:2296: error: (-215) cn <= 4 && ssize.area() > 0 in function remapBilinear

作为旁注:我看到您的数据的形状为(4, 96, 96)不是 (4, 10, 10)

另请注意,使用np.vectorize 并非一种改善功能的性能的技术。它只是轻轻地将你的函数调用包装在一个表面for - 循环中(虽然在NumPy级别)。这是一种编写自动遵循NumPy广播规则的函数的技术,并使您的API表面上与NumPy的API类似,因此函数调用可以在ndarray参数之上正常工作。

See this post for more details

已添加:在这种情况下,您使用partial的主要原因是获得一个表面上是“单一论证”的新功能,但是根据计划不能按计划运行partial的工作方式。那么为什么不一起摆脱partial呢?

即使使用两个非数组位置参数,您也可以完全保留lambda函数,但仍然要确保将第三个参数 视为向量化的内容。要执行此操作,您只需使用上面的excluded,但您还需要告诉vectorize输出结果。

原因是vectorize将尝试通过在您提供的数据的第一个元素上运行函数来确定输出形状应该是什么。在这种情况下(我不完全确定并且值得更多调试)这似乎创建了你所看到的“src is numpy array”错误。

因此,为了防止vectorize尝试它,您可以自己提供输出类型列表,如下所示:

vectorizedWarpAffine = np.vectorize(warpAffine, 
                                    excluded=(0, 1), 
                                    otypes=[np.ndarray])

它有效:

In [29]: vectorizedWarpAffine(M, size, data[:, 0])
Out[29]: 
array([[[ array([[ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       ..., 
       ...

我认为这样做要好得多,因为现在当你调用vectorizedWarpAffine时,你仍然明确地使用了其他位置参数,而不是那些与partial预先绑定的误导层,第三个论点仍然是矢量处理。