如何向量化此Numpy旋转代码?

时间:2018-06-20 22:37:23

标签: python numpy

目标:

我想向量化(或加快)此代码。它绕其中心点旋转3d numpy模型(用x,y,z表示尺寸;然后我们要绕z轴旋转)。 np模型是“开”或“关”的二进制体素

我敢打赌,一些基本的矩阵运算可以做到这一点,例如取一层并将旋转矩阵应用于每个元素。唯一的问题是小数;自cos(pi / 6) == sqrt(3) / 2起,我应该在哪里拥有新的价值之地?

代码:

def rotate_model(m, theta):
    '''
        theta in degrees
    '''
    n           =np.zeros(m.shape)
    for i,layer in enumerate(m):
        rotated = rotate(layer,theta)
        n[i]    = rotated
    return n

其中rotate()是:

def rotate(arr, theta):
    '''
        Rotates theta clockwise
        rotated.shape == arr.shape, unlike scipy.ndimage.rotate(), which inflates size and also does some strange mixing
    '''
    if theta == int(theta):
        theta *= pi / 180
    theta      = -theta
  # theta=-theta b/c clockwise.  Otherwise would default to counterclockwise
    rotated    =np.zeros(arr.shape)
    #print rotated.shape[0], rotated.shape[1]
    y_mid      = arr.shape[0]//2
    x_mid      = arr.shape[1]//2
    val        = 0
    for x_new in range(rotated.shape[1]):
        for y_new in range(rotated.shape[0]):
            x_centered = x_new - x_mid
            y_centered = y_new - y_mid
            x          = x_centered*cos(theta) - y_centered*sin(theta)
            y          = x_centered*sin(theta) + y_centered*cos(theta)
            x         += x_mid
            y         += y_mid
            x          = int(round(x)); y = int(round(y)) # cast so range() picks it up
            # lossy rotation
            if x in range(arr.shape[1]) and y in range(arr.shape[0]):
                val                   = arr[y,x]
                rotated[y_new,x_new]  = val
                #print val
                #print x,y
    return rotated

2 个答案:

答案 0 :(得分:1)

您的代码中有几个问题。首先,如果要将原始图像拟合到旋转的网格上,则需要一个更大的网格(通常)。或者,想象一个规则的网格,但是对象的形状-一个矩形-旋转了,因此变成了“菱形”。很明显,如果要适合整个菱形,则需要更大的输出网格(数组)。另一方面,您在代码rotated.shape == arr.shape中说,与scipy.ndimage.rotate()不同,后者会夸大大小” 。如果是这样,也许您不适合整个对象?因此,也许可以这样做:rotated=np.zeros(arr.shape)。但是,总的来说,是的,必须有一个较大的网格,以便在旋转输入图像后使其适合整个输入图像。

另一个问题是您正在执行的角度转换:

if theta == int(theta):
    theta *= pi / 180
theta = -theta

为什么?我想将图像旋转1个弧度会怎样?还是2个弧度?我禁止使用整数弧度吗?我认为您正在尝试在此功能中做太多事情,因此使用它会非常令人困惑。只需要求呼叫者将角度转换为弧度即可。或者,如果输入theta总是 度,则可以在此函数内执行此操作。或者,您可以添加另一个参数,例如units,调用者可以将其设置为radiansdegrees。不要试图根据输入的“整数”来猜测它!

现在,让我们重写一下代码:

rotated = np.zeros_like(arr) # instead of np.zero(arr.shape)
y_mid = arr.shape[0] // 2
x_mid = arr.shape[1] // 2
# val = 0 <- this is unnecessary
# pre-compute cos(theta) and sin(theta):
cs = cos(theta)
sn = sin(theta)
for x_new in range(rotated.shape[1]):
    for y_new in range(rotated.shape[0]):
        x = int(round((x_new - x_mid) * cs - (y_new - y_mid) * sn + x_mid)
        y = int(round((x_new - x_mid) * sn - (y_new - y_mid) * cs + y_mid)
        # just use comparisons, don't search through many values!
        if 0 <= x < arr.shape[1] and 0 <= y < arr.shape[0]:
            rotated[y_new, x_new] = arr[y, x]

因此,现在,我可以(更容易地)看到,对于 output 数组中的每个像素,它们都映射到 input 数组中的某个位置。是的,您可以对此向量化。


import numpy as np

def rotate(arr, theta, unit='rad'):
    # deal with theta units:
    if unit.startswith('deg'):
        theta = np.deg2rad(theta)

    # for convenience, store array size:
    ny, nx = arr.shape

    # generate arrays of indices and flatten them:
    y_new, x_new = np.indices(arr.shape)
    x_new = x_new.ravel()
    y_new = y_new.ravel()

    # compute center of the array:
    x0 = nx // 2
    y0 = ny // 2

    # compute old coordinates
    xc = x_new - x0
    yc = y_new - y0
    x = np.round(np.cos(theta) * xc - np.sin(theta) * yc + x0).astype(np.int)
    y = np.round(np.sin(theta) * xc - np.cos(theta) * yc + y0).astype(np.int)

    # main idea to deal with indices is to create a mask:
    mask = (x >= 0) & (x < nx) & (y >= 0) & (y < ny)

    # ... and then select only those coordinates (both in
    # input and "new" coordinates) that satisfy the above condition:
    x = x[mask]
    y = y[mask]
    x_new = x_new[mask]
    y_new = y_new[mask]

    # map input values to output pixels *only* for selected "good" pixels:
    rotated = np.zeros_like(arr)
    rotated[y_new, x_new] = arr[y, x]

    return rotated

答案 1 :(得分:-1)

这里有一些代码也适合进行3D建模的任何人。它很好地解决了我的特定用例。仍在寻找如何在适当的平面内旋转。希望它对您也有帮助:

def rotate_model(m, theta):
    '''
        Redefines the prev 'rotate_model()' method
        theta has to be in degrees
    '''
    rotated = scipy.ndimage.rotate(m, theta, axes=(1,2))
    #       have tried (1,0), (2,0), and now (1,2)

    #                                               ^ z is "up" and "2"
    # scipy.ndimage.rotate() shrinks the model
    # TODO: regrow it back
    x_r     = rotated.shape[1]
    y_r     = rotated.shape[0]
    x_m     = m.shape[1]
    y_m     = m.shape[0]
    x_diff  = abs(x_r - x_m)
    y_diff  = abs(y_r - y_m)
    if   x_diff%2==0 and y_diff%2==0:
        return rotated[
            x_diff//2   : x_r-x_diff//2,
            y_diff//2   : y_r-y_diff//2,
                        :
        ]
    elif x_diff%2==0 and y_diff%2==1:
        # if this shift ends up turning the model to shit in a few iterations,
        # change the following lines to include a flag that alternates cutting off the top and bottom bits of the array
        return rotated[
            x_diff//2   : x_r-x_diff//2,
            y_diff//2+1 : y_r-y_diff//2,
                        :
        ]
    elif x_diff%2==1 and y_diff%2==0:
        return rotated[
            x_diff//2+1 : x_r-x_diff//2,
            y_diff//2   : y_r-y_diff//2,
                        :
        ]
    else:
        # x_diff%2==1 and y_diff%2==1:
        return rotated[
            x_diff//2+1 : x_r-x_diff//2,
            y_diff//2+1 : y_r-y_diff//2,
                        :
        ]