首页 > 解决方案 > Numpy Array Math 复制像素,而不是通过 Python 中的 For 循环进行迭代

问题描述

我正在提取 4000 宽 x 2000 高的地球平面地图 ( src_filename) 并预先计算了存储在numpy.ndarray( transform_array, 2000, 2000, 2) 中的像素重定位 x、y 值。

重新定位的像素应该从 a 复制srcimg_array到 2000x2000 polarimg_array,然后保存到图像中。

我有一个 for 循环,它遍历transform_array,读取每个位置的位置值,然后将像素 RGB 值从源复制到极坐标阵列 - 即:transform_array[0, 0] 包含 [1414, 1500] 表示源图像[0, 0] 处的颜色值被复制到 [1414, 1500] 中polarimg_array,[0, 1] 值被复制到 [1413, 1500] 等。

这个过程每张图像大约需要 30 秒,我正在努力加快这个过程。

如何使用 Python 实现这一点?(即:不是 C 或 Cython 等)。

import numpy as np
import imageio

global transform_array
transform_array = np.zeros((2000, 2000, 2), dtype='i')
# transform_array values are pre-calculated already

srcimg_array = np.array(imageio.imread(src_filename))

polarimg_array = np.zeros((outputHeight, outputWidth, 3), dtype='uint8')

for item in np.ndindex((2000, 2000)):
    XYgrid = transform_array[item[0], item[1]]
    polarimg_array[[item[1]], [outputHeight - item[0]]] = srcimg_array[XYgrid[0], XYgrid[1]]

标签: pythonarraysnumpy

解决方案


首先,就像sai在他的评论中提到的,如果你想根据你在问题中解释的那样将像素从 重新定位srcimg_array到:polarimg_arraytransform_array

我有一个 for 循环遍历 transform_array,读取每个位置的位置值,然后将像素 RGB 值从源复制到极坐标数组 - 即: transform_array [0, 0] 包含 [1414, 1500] 这意味着[0, 0] 处的源图像颜色值被复制到 polarimg_array 中的 [1414, 1500],[0, 1] 值被复制到 [1413, 1500] 等。

然后你会像这样使用 for 循环:

for item in np.ndindex((2000, 2000)):
    XYgrid = transform_array[item[0], item[1]]
    polarimg_array[item[0], item[1]] = srcimg_array[XYgrid[0], XYgrid[1]]

我不明白你想用你的 for 循环做什么:

for item in np.ndindex((2000, 2000)):
    XYgrid = transform_array[item[0], item[1]]
    polarimg_array[[item[1]], [outputHeight - item[0]]] = srcimg_array[XYgrid[0], XYgrid[1]]

似乎您正在尝试进行某种额外的转换。如果是这种情况,那么您应该应用该转换,transform_array以便transform_array包含您需要的所有转换,然后根据transform_array使用上面编写的 for 循环重新定位像素(我的回答中提到的第一个 for 循环)。

此外,在这一行中:

polarimg_array[[item[1]], [outputHeight - item[0]]] = srcimg_array[XYgrid[0], XYgrid[1]]

你已经把索引item[1]outputHeight - item[0]放在一个数组里面(所以你有[item[1]][outputHeight - item[0]]作为索引),这不仅是不必要的,而且(出于某种奇怪的原因)它显着减慢了 for 循环的执行。(在我的计算机上,当我删除不必要的方括号时,它的速度大约快了 5 倍)。所以,这一行应该是这样的:

polarimg_array[item[1], outputHeight - item[0]] = srcimg_array[XYgrid[0], XYgrid[1]]

最后回到主要问题,在这个 for 循环中复制像素可以更快:

for item in np.ndindex((2000, 2000)):
    XYgrid = transform_array[item[0], item[1]]
    polarimg_array[item[0], item[1]] = srcimg_array[XYgrid[0], XYgrid[1]]

您可以在 NumPy 中使用数组索引( sai他的评论中也提到过)。您可以在此处找到 NumPy 中数组索引的详细信息和示例。简而言之:

整数数组索引允许基于它们的 N 维索引选择数组中的任意项。每个整数数组表示该维度的多个索引。

示例

>>> x = np.array([[ 0,  1,  2],
                  [ 3,  4,  5], 
                  [ 6,  7,  8], 
                  [ 9, 10, 11]])
>>> rows = np.array([[0, 0],
                     [3, 3]], dtype=np.intp) 
>>> columns = np.array([[0, 2],
                        [0, 2]], dtype=np.intp)
>>> x[rows, columns]
array([[ 0,  2],
       [ 9, 11]])

下一行将做我们想要的:

polarimg_array = srcimg_array[transform_array[:,:,0], transform_array[:,:,1]]

我将在一个只有 6 个像素的图像上的简单示例中解释它是如何工作的。

6 像素的示例图像

对于此图像srcimg_array将是:

array([[[200,   0,   0],
        [150,   0,   0],
        [100,   0,   0]],

       [[  0,   0, 200],
        [  0,   0, 150],
        [  0,   0, 100]]], dtype=uint8)

如果我们想水平翻转图像,那么transform_array将是:

>>> transform_array = np.array([[[j, 2 - i] for i in range(3)] for j in range(2)])
>>> transform_array
array([[[0, 2],
        [0, 1],
        [0, 0]],

       [[1, 2],
        [1, 1],
        [1, 0]]])

变量transform_array[:,:,0]将包含转换位置的高度,transform_array[:,:,1]并将包含转换位置的宽度。(:,:,0意味着我们从第一个维度选择所有内容(:),:从第二个维度选择所有内容(),以及在第三个维度中的位置元素0。)

>>> transform_array[:,:,0]
array([[0, 0, 0],
       [1, 1, 1]])

>>> transform_array[:,:,1]
array([[2, 1, 0],
       [2, 1, 0]])

最后:

>>> polarimg_array = srcimg_array[transform_array[:,:,0], transform_array[:,:,1]]
>>> polarimg_array
array([[[100,   0,   0],
        [150,   0,   0],
        [200,   0,   0]],

       [[  0,   0, 100],
        [  0,   0, 150],
        [  0,   0, 200]]], dtype=uint8)

例如,polarimg_array[1, 2]is [ 0, 0, 200]because transform_array[:,:,0][1, 2]is1transform_array[:,:,1][1, 2]is 0sopolarimg_array[1, 2]等于srcimg_array[1, 0]

生成的图像是:

生成的图像

这是完整的代码示例:

import numpy as np
import imageio

transform_array = np.zeros((2000, 2000, 2), dtype='i')
# transform_array values are pre-calculated already

srcimg_array = np.array(imageio.imread(src_filename))

polarimg_array = srcimg_array[transform_array[:,:,0], transform_array[:,:,1]]

在我的电脑上,一张大小为 4000x2000 的图像,创建所需的时间polarimg_array约为 0.1 秒。

编辑(在cpuguru的评论之后):

我查看了您的代码并设法创建了您需要的转换。(我编写getTransformation了创建函数,transform_array因此我们可以使用它srcimg_array[transform_array[:,:,0], transform_array[:,:,1]]来获取polarimg_array。)这是完整的简化代码示例。(我使用 numba 删除了参数解析、日志记录、多处理和性能改进):

import os
import time
import numpy as np
import imageio

def getTransformation(height, outputHeight, outputWidth):
    ts_rows, ts_columns = np.indices((outputHeight, outputHeight))
    ts_rows = ts_rows - outputHeight / 2
    ts_columns = ts_columns - outputHeight / 2
    transform_src = np.zeros((outputHeight, outputHeight, 2), dtype='int')
    transform_src[:,:,0] = np.arctan2(ts_columns, ts_rows) * height / np.pi
    transform_src[:,:,1] = np.sqrt(ts_rows*ts_rows + ts_columns*ts_columns) \
                           * height / outputHeight
        
    tn_columns, tn_rows = np.indices((outputHeight, outputHeight))
    tn_rows = outputHeight - 1 - tn_rows
    transform_north = transform_src[tn_rows, tn_columns]    
    transform_north = transform_north[:,:,::-1]

    ts_columns, ts_rows = np.indices((outputHeight, outputHeight))
    ts_rows = ts_rows - outputHeight
    transform_south = transform_src[ts_rows, ts_columns]
    transform_south[:,:,1] = height - transform_south[:,:,1] - 1
    transform_south = transform_south[:,:,::-1]

    transform_array = np.zeros((outputHeight, outputWidth, 2), dtype='int')
    transform_array[:,:outputHeight,:] = transform_north
    transform_array[:,outputHeight-4:,:] = transform_south
    return transform_array
        

def EquirectangularToPolar(src_filename, transform_array):
    srcimg_array = np.array(imageio.imread(src_filename))

    polarimg_array = srcimg_array[transform_array[:,:,0], transform_array[:,:,1]]

    name = os.path.splitext(os.path.basename(src_filename))[0]
    dst_filename = ''.join([name, '_pp.', 'jpg'])
    imageio.imsave(dst_filename, polarimg_array)
    print("Saving " + dst_filename)
    return


def main():
    starttime = time.time()
    filename = 'wind_waves_0001.jpg'

    height = 2000
    width = 4000
    outputHeight = height
    outputWidth = width - 4

    starttime = time.time()
    
    transform_array = getTransformation(height, outputHeight, outputWidth)

    EquirectangularToPolar(filename, transform_array)

    print('File conversion took {0:.2f} seconds'.format(time.time() - starttime))

if __name__ == '__main__':
    main()

函数getTransformation创建transform_array的不是函数中使用的,EquirectangularToPolar以将转换应用于每个图像。在上面的代码中,我假设您的所有图像都具有相同的大小,因此您可以transform_array在所有图像上使用相同的大小(或至少在transform_array相同大小的图像上使用相同的大小),从而为您带来额外的性能提升。

函数如何getTransformation工作的解释:

  • 第一部分创建transform_src. transform_src是一个大小(outputHeight, outputHeight, 2)等于transform_src[x][y]的数组findSrcXY(x, y, height, outputHeight)。我们不是findSrcXY为每个坐标(x, y)计算它,而是为所有 x 坐标一起计算它,并为所有 y 坐标一起计算它。当对整个数组应用操作而不是对数组的每个元素单独应用该操作时,NumPy 非常快。在第一行:

    ts_rows, ts_columns = np.indices((outputHeight, outputHeight))
    

    ts_rows并且ts_columns被启动,以便ts_rows包含 x 坐标并ts_columns包含大小数组中所有位置的 y 坐标 (outputHeight, outputHeight)。

    接下来两行:

    ts_rows = ts_rows - outputHeight / 2
    ts_columns = ts_columns - outputHeight / 2
    

    替换此代码:

    x = x - outputHeight / 2
    y = y - outputHeight / 2
    

    最后两行:

    transform_src[:,:,0] = np.arctan2(ts_columns, ts_rows) * height / np.pi
    transform_src[:,:,1] = np.sqrt(ts_rows*ts_rows + ts_columns*ts_columns) * height / outputHeight
    

    findSrcX正在做与函数和相同的事情findSrcY

  • 在 function 的第二和第三部分,创建了getTransformation数组transform_northtransform_southtransform_north是生成图像左侧所需的变换,并且transform_south是图像右侧所需的变换。数组transform_north替换这一行:

    polarimg_array[coord_target[1], outputHeight - coord_target[0]] = srcimg_array[coord_src[1], coord_src[0]]
    

    再次在第一行ts_rowsts_columns启动:

    tn_columns, tn_rows = np.indices((outputHeight, outputHeight))
    

    这次ts_rowsandts_columns被颠倒了,因为coord_target被颠倒了polarimg_array[coord_target[1], outputHeight - coord_target[0]](在第一个索引上是coord_target[1],在第二个索引上是coord_target[0]。)

    然后下一行:

    tn_rows = outputHeight - 1 - tn_rows
    

    替换. outputHeight - coord_target[0]_ polarimg_array[coord_target[1], outputHeight - coord_target[0]](我添加-1了结果图像从第一列(索引为零)开始,如果您查看图像(以 png 格式),您会看到第一列只是黑色,我认为这不是理想的结果.)

    在下一行:

    transform_north = transform_src[tn_rows, tn_columns]
    

    我们从transform_src正确的位置获得价值。

    在最后一行:

    transform_north = transform_north[:,:,::-1]
    

    我们反转位置(从 (x, y) 到 (y, x)),因为coord_srcinsrcimg_array[coord_src[1], coord_src[0]]的位置是反转的。

    数组transform_south的创建类似于transform_north.

  • 函数的最后一部分getTransformationtransform_northand组合transform_south在一个数组transform_array中。

就像我说的那样,函数getTransformation比以前的解决方案快得多,因为我们将所有操作应用于整个数组。这些是我电脑上部分程序所花费的(大约)时间:

  • 0.8秒创建transform_array(带getTransformation功能)
  • 0.2 秒创建srcimg_array(打开图像)
  • 0.3 秒创建polarimg_array(应用转换)
  • 函数0.2 秒imageio.imsave(保存结果图像)

使用您的代码(具有性能改进)转换大约需要 1 分钟。您可以尝试使用多处理和 numba 改进我的解决方案,但如果您的所有图像都具有相同的大小,那么您只创建transform_array(call getTransformation) 一次并EquirectangularToPolar为每个图像调用一次函数(大约需要 0.7 秒)。鉴于打开和保存图像大约需要 0.4 秒,只有当您可以加快打开和保存图像的过程时,性能改进才有意义。


推荐阅读