给定六个 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着色器,但失败了。
首先,我使用 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
请注意,计算的符号和分配可能会有所不同,具体取决于捕获过程和立方体贴图的方向。