【问题标题】:Passing/Returning Cython Memoryviews vs NumPy Arrays传递/返回 Cython Memoryviews 与 NumPy 数组
【发布时间】:2018-04-12 18:48:14
【问题描述】:

我正在编写 Python 代码来加速二进制图像中标记对象的区域属性函数。以下代码将在给定对象索引的情况下计算二值图像中标记对象的边界像素数。 main() 函数将循环遍历二进制图像“掩码”中的所有标记对象,并计算每个对象的边界像素数。

我想知道在这个 Cython 代码中传递或返回我的变量的最佳方法是什么。这些变量要么在 NumPy 数组中,要么在类型化的 Memoryviews 中。我已经搞砸了以不同格式传递/返回变量,但无法推断出最好/最有效的方法是什么。我是 Cython 的新手,所以 Memoryviews 对我来说仍然相当抽象,两种方法之间是否存在差异仍然是个谜。我正在使用的图像包含 100,000 多个标记对象,因此此类操作需要相当高效。

总结一下:

何时/应该将变量作为类型化的 Memoryviews 而不是 NumPy 数组传递/返回以进行非常重复的计算?有没有最好的方法或者它们完全相同?

%%cython --annotate

import numpy as np
import cython
cimport numpy as np

DTYPE = np.intp
ctypedef np.intp_t DTYPE_t

@cython.boundscheck(False)
@cython.wraparound(False)
def erode(DTYPE_t [:,:] img):

    # Image dimensions
    cdef int height, width, local_min
    height = img.shape[0]
    width = img.shape[1]

    # Padded Array
    padded_np = np.zeros((height+2, width+2), dtype = DTYPE)
    cdef DTYPE_t[:,:] padded = padded_np
    padded[1:height+1,1:width+1] = img

    # Eroded image
    eroded_np = np.zeros((height,width),dtype=DTYPE)
    cdef DTYPE_t[:,:] eroded = eroded_np

    cdef DTYPE_t i,j
    for i in range(height):
        for j in range(width):
            local_min = min(padded[i+1,j+1], padded[i,j+1], padded[i+1,j],padded[i+1,j+2],padded[i+2,j+1])
            eroded[i,j] = local_min
    return eroded_np


@cython.boundscheck(False)
@cython.wraparound(False)
def border_image(slice_np):

    # Memoryview of slice_np
    cdef DTYPE_t [:,:] slice = slice_np

    # Image dimensions
    cdef Py_ssize_t ymax, xmax, y, x
    ymax = slice.shape[0]
    xmax = slice.shape[1]

    # Erode image
    eroded_image_np = erode(slice_np)
    cdef DTYPE_t[:,:] eroded_image = eroded_image_np

    # Border image
    border_image_np = np.zeros((ymax,xmax),dtype=DTYPE)
    cdef DTYPE_t[:,:] border_image = border_image_np
    for y in range(ymax):
        for x in range(xmax):
            border_image[y,x] = slice[y,x]-eroded_image[y,x]
    return border_image_np.sum()


@cython.boundscheck(False)
@cython.wraparound(False)
def main(DTYPE_t[:,:] mask, int numobjects, Py_ssize_t[:,:] indices):

    # Memoryview of boundary pixels
    boundary_pixels_np = np.zeros(numobjects,dtype=DTYPE)
    cdef DTYPE_t[:] boundary_pixels = boundary_pixels_np

    # Loop through each object
    cdef Py_ssize_t y_from, y_to, x_from, x_to, i
    cdef DTYPE_t[:,:] slice
    for i in range(numobjects):
        y_from = indices[i,0]
        y_to = indices[i,1]
        x_from = indices[i,2]
        x_to = indices[i,3]
        slice = mask[y_from:y_to, x_from:x_to]
        boundary_pixels[i] = border_image(slice)

    return boundary_pixels_np

【问题讨论】:

  • 这不是你直接问的,但我猜你代码的慢点是min 行,它将调用 Python 内置函数。如果if 声明,也许将其更改为系列?

标签: python numpy image-processing cython memoryview


【解决方案1】:

Memoryviews 是 Cython 的最新添加,旨在与原始 np.ndarray 语法相比进行改进。出于这个原因,它们略受欢迎。不过,它通常不会对您使用产生太大影响。以下是一些注意事项:

速度

就速度而言,它非常几乎没有区别 - 我的经验是,作为函数参数的内存视图稍微慢一些,但几乎不值得担心。

一般性

Memoryviews 设计用于任何具有 Python 缓冲区接口的类型(例如标准库 array 模块)。键入为 np.ndarray 仅适用于 numpy 数组。原则上,memorviews 可以支持偶数 wider range of memory layouts,这可以使与 C 代码的接口更容易(实际上我从未真正看到这很有用)。

作为返回值

当从 Cython 返回一个数组以编写 Python 代码时,用户可能会更喜欢 numpy 数组而不是 memoryview。如果您正在使用 memoryviews,您可以执行以下任一操作:

return np.asarray(mview)
return mview.base

易于编译

如果您使用的是np.ndarray,则必须在setup.py 文件中使用np.get_include() 设置包含目录。您不必对 memoryviews 执行此操作,这通常意味着您可以跳过 setup.py,而只需使用 cythonize 命令行命令或 pyximport 进行更简单的项目。

并行化

这是 memoryviews 与 numpy 数组相比的优势(如果你想使用它)。它不需要全局解释器锁来获取内存视图的切片,但它适用于 numpy 数组。这意味着以下代码大纲可以与 memoryview 并行工作:

cdef void somefunc(double[:] x) nogil:
     # implementation goes here

cdef double[:,:] 2d_array = np.array(...)
for i in prange(2d_array.shape[0]):
    somefunc(2d_array[i,:])

如果您不使用 Cython 的并行功能,则不适用。

cdef

您可以将内存视图用作cdef 类的属性,但不能使用np.ndarrays。您可以(当然)使用 numpy 数组作为无类型的 object 属性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多