使用 GPU 将立方体贴图转换为等距柱状全景图

问题描述 投票:0回答:1

给定六个 FOV 为 90° 的二次立方体贴图(纵横比 1:1,分辨率 1000x1000),我尝试将它们转换为等距柱状全景图(纵横比 2:1,分辨率 4000x2000)。我当前的方法是计算等距柱状全景图每个像素的球面坐标,从球面坐标计算笛卡尔坐标,估计像素位于哪个立方体贴图上,并估计该立方体上对应的像素,如下所示(字母代表North、South、East、West、Up、 D自己)。

def uv_pano_to_uv_cubes(u_pano, v_pano, cubemap_shape, panorama_shape):

    # Calculate the spherical coordinates from 2D image coordinates
    phi = (u_pano / panorama_shape[0] - 0.5) * np.pi
    theta = (v_pano / panorama_shape[1] + 0.25) * 2 * np.pi

    # Calculate the cartesian coordinates from the spherical coordinates
    x = np.cos(phi) * np.cos(theta)
    y = np.sin(phi)
    z = np.cos(phi) * np.sin(theta)

    # Estimate on which cubemap the pixel is located
    if abs(x) >= abs(y) and abs(x) >= abs(z):
        face = 'E' if x > 0 else 'W'
        a = z if x > 0 else -z
        b = y
        ma = abs(x)
    elif abs(y) >= abs(x) and abs(y) >= abs(z):
        face = 'D' if y > 0 else 'U'
        a = x
        b = z if y > 0 else -z
        ma = abs(y)
    else:
        face = 'S' if z > 0 else 'N'
        a = -x if z > 0 else x
        b = y
        ma = abs(z)

    # Estimate the corresponding pixel on the cube
    u_cube = int(((a / ma) + 1.0) * 0.5 * (cubemap_shape[0] - 1))
    v_cube = int(((b / ma) + 1.0) * 0.5 * (cubemap_shape[1] - 1))

    return face, u_cube, v_cube

如果我使用Python脚本并在CPU上进行计算,则执行转换需要几秒钟的时间。有没有办法在 GPU 上进行此计算,最好使用 Python?我读到应该可以使用 OpenGL 或 DirectX,但我还没有找到方法。

我尝试过在CPU上使用多线程来提高性能,但这并没有多大帮助。我也尝试过写一个OpenGL着色器,但失败了。

python gpu cpu
1个回答
0
投票

首先,我使用 NumPy 实现了一个优化版本,一次性计算所有 uv 坐标和面,而不是分别计算等距柱状全景图每个像素的 uv 坐标和面。这使我的机器的性能提高了 10 倍。

def uv_pano_to_uv_cubes_cpu(u_pano, v_pano, cubemap_shape, panorama_shape):

    # Calculate the spherical coordinates from 2D image coordinates
    phi   = (u_pano / panorama_shape[0] * 1 - 0.5) * cp.pi
    theta = (v_pano / panorama_shape[1] * 2 + 0.5) * cp.pi

    # Calculate the cartesian coordinates from the spherical coordinates
    x = np.cos(phi) * np.cos(theta)
    y = np.sin(phi)
    z = np.cos(phi) * np.sin(theta)

    # Initialize arrays for the faces and the uv-coordinates on the cubemaps
    faces   = np.empty(x.shape, dtype=np.int32)
    u_cubes = np.empty(x.shape, dtype=np.float32)
    v_cubes = np.empty(x.shape, dtype=np.float32)

    # Estimate on which cubemap the pixel is located (North - South)
    condition_N_S = (abs(z) >= abs(x)) & (abs(z) >= abs(y))
    faces[condition_N_S] = np.where(z[condition_N_S] < 0, NORTH, SOUTH)
    a = y
    b = np.where(z > 0, -x, x)
    ma = abs(z)
    u_cubes[condition_N_S] = ((a[condition_N_S] / ma[condition_N_S]) + 1.0) * 0.5 * (cubemap_shape[0] - 1)
    v_cubes[condition_N_S] = ((b[condition_N_S] / ma[condition_N_S]) + 1.0) * 0.5 * (cubemap_shape[1] - 1)

    # Estimate on which cubemap the pixel is located (East - West)
    condition_E_W = (abs(x) >= abs(y)) & (abs(x) >= abs(z))
    faces[condition_E_W] = np.where(x[condition_E_W] > 0, EAST, WEST)
    a = y
    b = np.where(x > 0, z, -z)
    ma = abs(x)
    u_cubes[condition_E_W] = ((a[condition_E_W] / ma[condition_E_W]) + 1.0) * 0.5 * (cubemap_shape[0] - 1)
    v_cubes[condition_E_W] = ((b[condition_E_W] / ma[condition_E_W]) + 1.0) * 0.5 * (cubemap_shape[1] - 1)

    # Estimate on which cubemap the pixel is located (Up - Down)
    condition_U_D = (abs(y) >= abs(x)) & (abs(y) >= abs(z))
    faces[condition_U_D] = np.where(y[condition_U_D] < 0, UP, DOWN)
    a = np.where(y > 0, z, -z)
    b = x
    ma = abs(y)
    u_cubes[condition_U_D] = ((a[condition_U_D] / ma[condition_U_D]) + 1.0) * 0.5 * (cubemap_shape[0] - 1)
    v_cubes[condition_U_D] = ((b[condition_U_D] / ma[condition_U_D]) + 1.0) * 0.5 * (cubemap_shape[1] - 1)

    return faces, u_cubes.astype(int), v_cubes.astype(int)

def cubes_to_panorama_cpu(cubes):

    # Initalize an empty array for the equirectangular panorama on the CPU
    if len(cubes[NORTH].shape) > 2:
        pano_shape = (int(cubes[NORTH].shape[0] * 2), int(cubes[NORTH].shape[1] * 4), cubes[NORTH].shape[2])
    else:
        pano_shape = (int(cubes[NORTH].shape[0] * 2), int(cubes[NORTH].shape[1] * 4))
    pano = np.zeros(pano_shape, dtype=cp.uint8)

    # Initalize arrays for the indices of the panorama
    u_pano, v_pano = np.meshgrid(np.arange(pano_shape[0]), np.arange(pano_shape[1]), indexing='ij')

    # Calculate all corresponding indices on the cubemaps
    faces, u_cubes, v_cubes = Panorama.uv_pano_to_uv_cubes_cpu(u_pano, v_pano, cubes[NORTH].shape, pano_shape)

    # Write the corresponding pixels to the panorama
    for face in range(6):
        pano[faces == face] = cubes[face][u_cubes[faces == face], v_cubes[faces == face]]

    return pano

其次,我用 CuPy 函数替换了所有 NumPy 函数。这进一步将我的机器的性能提高了 3 倍。

def uv_pano_to_uv_cubes_gpu(u_pano, v_pano, cubemap_shape, panorama_shape):

    # Calculate the spherical coordinates from 2D image coordinates
    phi   = (u_pano / panorama_shape[0] * 1 - 0.5) * cp.pi
    theta = (v_pano / panorama_shape[1] * 2 + 0.5) * cp.pi

    # Calculate the cartesian coordinates from the spherical coordinates
    x = cp.cos(phi) * cp.cos(theta)
    y = cp.sin(phi)
    z = cp.cos(phi) * cp.sin(theta)

    # Initialize arrays for the faces and the uv-coordinates on the cubemaps
    faces   = cp.empty(x.shape, dtype=cp.int32)
    u_cubes = cp.empty(x.shape, dtype=cp.float32)
    v_cubes = cp.empty(x.shape, dtype=cp.float32)

    # Estimate on which cubemap the pixel is located (North - South)
    condition_N_S = (abs(z) >= abs(x)) & (abs(z) >= abs(y))
    faces[condition_N_S] = cp.where(z[condition_N_S] < 0, NORTH, SOUTH)
    a = y
    b = cp.where(z > 0, -x, x)
    ma = abs(z)
    u_cubes[condition_N_S] = ((a[condition_N_S] / ma[condition_N_S]) + 1.0) * 0.5 * (cubemap_shape[0] - 1)
    v_cubes[condition_N_S] = ((b[condition_N_S] / ma[condition_N_S]) + 1.0) * 0.5 * (cubemap_shape[1] - 1)

    # Estimate on which cubemap the pixel is located (East - West)
    condition_E_W = (abs(x) >= abs(y)) & (abs(x) >= abs(z))
    faces[condition_E_W] = cp.where(x[condition_E_W] > 0, EAST, WEST)
    a = y
    b = cp.where(x > 0, z, -z)
    ma = abs(x)
    u_cubes[condition_E_W] = ((a[condition_E_W] / ma[condition_E_W]) + 1.0) * 0.5 * (cubemap_shape[0] - 1)
    v_cubes[condition_E_W] = ((b[condition_E_W] / ma[condition_E_W]) + 1.0) * 0.5 * (cubemap_shape[1] - 1)

    # Estimate on which cubemap the pixel is located (Up - Down)
    condition_U_D = (abs(y) >= abs(x)) & (abs(y) >= abs(z))
    faces[condition_U_D] = cp.where(y[condition_U_D] < 0, UP, DOWN)
    a = cp.where(y > 0, z, -z)
    b = x
    ma = abs(y)
    u_cubes[condition_U_D] = ((a[condition_U_D] / ma[condition_U_D]) + 1.0) * 0.5 * (cubemap_shape[0] - 1)
    v_cubes[condition_U_D] = ((b[condition_U_D] / ma[condition_U_D]) + 1.0) * 0.5 * (cubemap_shape[1] - 1)

    return faces, u_cubes.astype(int), v_cubes.astype(int)

def cubes_to_panorama_gpu(cubes):

    # Initalize an empty array for the equirectangular panorama on the GPU
    if len(cubes[NORTH].shape) > 2:
        pano_shape = (int(cubes[NORTH].shape[0] * 2), int(cubes[NORTH].shape[1] * 4), cubes[NORTH].shape[2])
    else:
        pano_shape = (int(cubes[NORTH].shape[0] * 2), int(cubes[NORTH].shape[1] * 4))
    pano = cp.zeros(pano_shape, dtype=cp.uint8)

    # Move the cubemaps to the GPU
    cubes[NORTH] = cp.asarray(cubes[NORTH])
    cubes[SOUTH] = cp.asarray(cubes[SOUTH])
    cubes[EAST]  = cp.asarray(cubes[EAST])
    cubes[WEST]  = cp.asarray(cubes[WEST])
    cubes[UP]    = cp.asarray(cubes[UP])
    cubes[DOWN]  = cp.asarray(cubes[DOWN])

    # Initalize arrays for the indices of the panorama
    u_pano, v_pano = cp.meshgrid(cp.arange(pano_shape[0]), cp.arange(pano_shape[1]), indexing='ij')

    # Calculate all corresponding indices on the cubemaps
    faces, u_cubes, v_cubes = Panorama.uv_pano_to_uv_cubes_gpu(u_pano, v_pano, cubes[NORTH].shape, pano_shape)

    # Write the corresponding pixels to the panorama
    for face in range(6):
        pano[faces == face] = cubes[face][u_cubes[faces == face], v_cubes[faces == face]]

    # Move the panorama to the CPU
    pano = cp.asnumpy(pano)

    return pano

请注意,计算的符号和分配可能会有所不同,具体取决于捕获过程和立方体贴图的方向。

© www.soinside.com 2019 - 2024. All rights reserved.