对 numpy 数组进行向量化操作
Vectorizing operation on numpy array
我有一个包含许多三维 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 数组上的操作?
问题是仅仅通过使用 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)
为了解决这个问题,您可以使用 np.vectorize
的 excluded
选项,它说明在包装矢量化行为时要忽略哪些参数(位置或关键字):
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
的主要原因是获得一个表面上 "single-argumented" 但不起作用的新功能根据 partial
的工作方式按计划进行。那么,为什么不一起摆脱 partial
呢?
您可以保留 lambda
函数原样,即使有两个非数组位置参数,但仍然确保第三个参数 被 视为向量化的东西。为此,您只需像上面那样使用 excluded
,但您还需要告诉 vectorize
期望的输出结果。
原因是 vectorize
将尝试通过 运行 您的函数根据您提供的数据的第一个元素来确定输出形状应该是什么。在这种情况下(我不完全确定,值得进行更多调试)这似乎会造成您看到的 "src is not 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
的误导层,然而第三个参数仍然是矢量处理的。
我有一个包含许多三维 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 数组上的操作?
问题是仅仅通过使用 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)
为了解决这个问题,您可以使用 np.vectorize
的 excluded
选项,它说明在包装矢量化行为时要忽略哪些参数(位置或关键字):
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
的主要原因是获得一个表面上 "single-argumented" 但不起作用的新功能根据 partial
的工作方式按计划进行。那么,为什么不一起摆脱 partial
呢?
您可以保留 lambda
函数原样,即使有两个非数组位置参数,但仍然确保第三个参数 被 视为向量化的东西。为此,您只需像上面那样使用 excluded
,但您还需要告诉 vectorize
期望的输出结果。
原因是 vectorize
将尝试通过 运行 您的函数根据您提供的数据的第一个元素来确定输出形状应该是什么。在这种情况下(我不完全确定,值得进行更多调试)这似乎会造成您看到的 "src is not 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
的误导层,然而第三个参数仍然是矢量处理的。