| import numpy as np |
| import cv2 |
| import os |
| from plyfile import PlyData, PlyElement |
| import utils3d |
| import torch |
|
|
| def spherical_uv_to_directions(uv: np.ndarray): |
| theta, phi = (1 - uv[..., 0]) * (2 * np.pi), uv[..., 1] * np.pi |
| directions = np.stack([ |
| np.sin(phi) * np.cos(theta), |
| np.sin(phi) * np.sin(theta), |
| np.cos(phi) |
| ], axis=-1) |
| return directions |
|
|
| def spherical_uv_to_directions_torch(uv: torch.Tensor): |
| """ |
| torch版本的球面UV坐标转方向向量 |
| Args: |
| uv: UV坐标,形状为 [H, W, 2] 或 [B, H, W, 2] |
| Returns: |
| directions: 方向向量,形状为 [H, W, 3] 或 [B, H, W, 3] |
| """ |
| theta = (1 - uv[..., 0]) * (2 * torch.pi) |
| phi = uv[..., 1] * torch.pi |
| directions = torch.stack([ |
| torch.sin(phi) * torch.cos(theta), |
| torch.sin(phi) * torch.sin(theta), |
| torch.cos(phi) |
| ], dim=-1) |
| return directions |
|
|
| def save_3d_points(points: np.array, colors: np.array, mask: np.array, filename: str): |
| points = points.reshape(-1, 3) |
| colors = colors.reshape(-1, 3) |
| mask = mask.reshape(-1) |
|
|
| vertex_data = np.empty(mask.sum(), dtype=[ |
| ('x', 'f4'), ('y', 'f4'), ('z', 'f4'), |
| ('red', 'u1'), ('green', 'u1'), ('blue', 'u1') |
| ]) |
| vertex_data['x'] = points[mask, 0] |
| vertex_data['y'] = points[mask, 1] |
| vertex_data['z'] = points[mask, 2] |
| vertex_data['red'] = colors[mask, 0] |
| vertex_data['green'] = colors[mask, 1] |
| vertex_data['blue'] = colors[mask, 2] |
|
|
| vertex_element = PlyElement.describe(vertex_data, 'vertex', comments=['point cloud']) |
| PlyData([vertex_element], text=True).write(filename) |
|
|
| def depth2pointcloud(depth_path: str, image_path: str, out_ply: str): |
| |
| depth = cv2.imread(depth_path, cv2.IMREAD_UNCHANGED) |
| if depth is None: |
| raise FileNotFoundError(f"无法读取深度图: {depth_path}") |
|
|
| |
| if depth.ndim == 3: |
| depth = cv2.cvtColor(depth, cv2.COLOR_BGR2GRAY) |
|
|
| depth = depth.astype(np.float32) |
| |
| if depth.dtype == np.uint8: |
| depth = depth / 255.0 |
| elif depth.dtype == np.uint16: |
| depth = depth / 65535.0 |
|
|
| h, w = depth.shape |
| uv = utils3d.numpy.image_uv(width=w, height=h) |
| dirs = spherical_uv_to_directions(uv) |
| points = depth[..., None] * dirs |
|
|
| |
| image = cv2.imread(image_path, cv2.IMREAD_COLOR) |
| if image is None: |
| raise FileNotFoundError(f"无法读取原图: {image_path}") |
| image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
| if image.shape[:2] != (h, w): |
| image = cv2.resize(image, (w, h), interpolation=cv2.INTER_LINEAR) |
|
|
| mask = depth > 0 |
| save_3d_points(points, image, mask, out_ply) |
| print(f"✅ 点云已保存到 {out_ply}") |
|
|
|
|
| def depth2pts(depth): |
| |
| depth.squeeze(1) |
|
|
| depth = depth.astype(np.float32) |
|
|
| b, h, w = depth.shape |
| |
| |
| uv = utils3d.numpy.image_uv(width=w, height=h) |
| |
| dirs = spherical_uv_to_directions(uv) |
| |
| |
| points = depth[..., None] * dirs[None, ...] |
| |
| return points |
|
|
| def depth2pts_torch(depth): |
| """ |
| 将深度图转换为3D点云,支持batch处理(torch版本) |
| Args: |
| depth: 深度图torch tensor,形状为 [B, H, W] 或 [B, 1, H, W],值范围0-1 |
| Returns: |
| points: 3D点云torch tensor,形状为 [B, H, W, 3] |
| """ |
| |
| depth = depth.float() |
| |
| |
| if depth.dim() == 4 and depth.shape[1] == 1: |
| depth = depth.squeeze(1) |
| |
| b, h, w = depth.shape |
| |
| |
| uv = utils3d.numpy.image_uv(width=w, height=h) |
| |
| uv = torch.from_numpy(uv).to(depth.device).to(depth.dtype) |
| |
| |
| dirs = spherical_uv_to_directions_torch(uv) |
| |
| |
| points = depth[..., None] * dirs[None, ...] |
| |
| return points |
|
|
|
|
|
|
| if __name__ == "__main__": |
| |
| |
| |
| path_ = "/home/tione/notebook/home/wenxuan/DAM_dualhead_alltrain/vis_exp2_insta820/rgb/" |
| for file in os.listdir(path_): |
| image_path = os.path.join(path_, file) |
| depth_path = os.path.join(path_.replace("rgb", "depth"), file.replace("rgb", "depth")) |
| os.makedirs(path_.replace("rgb", "pts"), exist_ok=True) |
| out_ply = os.path.join(path_.replace("rgb", "pts"), file.replace(".png", ".ply")) |
| depth2pointcloud(depth_path, image_path, out_ply) |
| |
| |
| |
|
|