dataset_readers.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #
  2. # Copyright (C) 2023, Inria
  3. # GRAPHDECO research group, https://team.inria.fr/graphdeco
  4. # All rights reserved.
  5. #
  6. # This software is free for non-commercial, research and evaluation use
  7. # under the terms of the LICENSE.md file.
  8. #
  9. # For inquiries contact george.drettakis@inria.fr
  10. #
  11. import os
  12. import sys
  13. from PIL import Image
  14. from typing import NamedTuple
  15. from scene.colmap_loader import read_extrinsics_text, read_intrinsics_text, qvec2rotmat, \
  16. read_extrinsics_binary, read_intrinsics_binary, read_points3D_binary, read_points3D_text
  17. from utils.graphics_utils import getWorld2View2, focal2fov, fov2focal
  18. import numpy as np
  19. import json
  20. from pathlib import Path
  21. from plyfile import PlyData, PlyElement
  22. from utils.sh_utils import SH2RGB
  23. from scene.gaussian_model import BasicPointCloud
  24. class CameraInfo(NamedTuple):
  25. uid: int
  26. R: np.array
  27. T: np.array
  28. FovY: np.array
  29. FovX: np.array
  30. image: np.array
  31. image_path: str
  32. image_name: str
  33. width: int
  34. height: int
  35. class SceneInfo(NamedTuple):
  36. point_cloud: BasicPointCloud
  37. train_cameras: list
  38. test_cameras: list
  39. nerf_normalization: dict
  40. ply_path: str
  41. def getNerfppNorm(cam_info):
  42. def get_center_and_diag(cam_centers):
  43. cam_centers = np.hstack(cam_centers)
  44. avg_cam_center = np.mean(cam_centers, axis=1, keepdims=True)
  45. center = avg_cam_center
  46. dist = np.linalg.norm(cam_centers - center, axis=0, keepdims=True)
  47. diagonal = np.max(dist)
  48. return center.flatten(), diagonal
  49. cam_centers = []
  50. for cam in cam_info:
  51. W2C = getWorld2View2(cam.R, cam.T)
  52. C2W = np.linalg.inv(W2C)
  53. cam_centers.append(C2W[:3, 3:4])
  54. center, diagonal = get_center_and_diag(cam_centers)
  55. radius = diagonal * 1.1
  56. translate = -center
  57. return {"translate": translate, "radius": radius}
  58. def readColmapCameras(cam_extrinsics, cam_intrinsics, images_folder):
  59. cam_infos = []
  60. for idx, key in enumerate(cam_extrinsics):
  61. sys.stdout.write('\r')
  62. # the exact output you're looking for:
  63. sys.stdout.write("Reading camera {}/{}".format(idx+1, len(cam_extrinsics)))
  64. sys.stdout.flush()
  65. extr = cam_extrinsics[key]
  66. intr = cam_intrinsics[extr.camera_id]
  67. height = intr.height
  68. width = intr.width
  69. uid = intr.id
  70. R = np.transpose(qvec2rotmat(extr.qvec))
  71. T = np.array(extr.tvec)
  72. if intr.model=="SIMPLE_PINHOLE":
  73. focal_length_x = intr.params[0]
  74. FovY = focal2fov(focal_length_x, height)
  75. FovX = focal2fov(focal_length_x, width)
  76. elif intr.model=="PINHOLE":
  77. focal_length_x = intr.params[0]
  78. focal_length_y = intr.params[1]
  79. FovY = focal2fov(focal_length_y, height)
  80. FovX = focal2fov(focal_length_x, width)
  81. else:
  82. assert False, "Colmap camera model not handled!"
  83. image_path = os.path.join(images_folder, os.path.basename(extr.name))
  84. image_name = os.path.basename(image_path).split(".")[0]
  85. image = Image.open(image_path)
  86. cam_info = CameraInfo(uid=uid, R=R, T=T, FovY=FovY, FovX=FovX, image=image,
  87. image_path=image_path, image_name=image_name, width=width, height=height)
  88. cam_infos.append(cam_info)
  89. sys.stdout.write('\n')
  90. return cam_infos
  91. def fetchPly(path):
  92. plydata = PlyData.read(path)
  93. vertices = plydata['vertex']
  94. positions = np.vstack([vertices['x'], vertices['y'], vertices['z']]).T
  95. colors = np.vstack([vertices['red'], vertices['green'], vertices['blue']]).T / 255.0
  96. normals = np.vstack([vertices['nx'], vertices['ny'], vertices['nz']]).T
  97. return BasicPointCloud(points=positions, colors=colors, normals=normals)
  98. def storePly(path, xyz, rgb):
  99. # Define the dtype for the structured array
  100. dtype = [('x', 'f4'), ('y', 'f4'), ('z', 'f4'),
  101. ('nx', 'f4'), ('ny', 'f4'), ('nz', 'f4'),
  102. ('red', 'u1'), ('green', 'u1'), ('blue', 'u1')]
  103. normals = np.zeros_like(xyz)
  104. elements = np.empty(xyz.shape[0], dtype=dtype)
  105. attributes = np.concatenate((xyz, normals, rgb), axis=1)
  106. elements[:] = list(map(tuple, attributes))
  107. # Create the PlyData object and write to file
  108. vertex_element = PlyElement.describe(elements, 'vertex')
  109. ply_data = PlyData([vertex_element])
  110. ply_data.write(path)
  111. def readColmapSceneInfo(path, images, eval, llffhold=8):
  112. try:
  113. cameras_extrinsic_file = os.path.join(path, "sparse/0", "images.bin")
  114. cameras_intrinsic_file = os.path.join(path, "sparse/0", "cameras.bin")
  115. cam_extrinsics = read_extrinsics_binary(cameras_extrinsic_file)
  116. cam_intrinsics = read_intrinsics_binary(cameras_intrinsic_file)
  117. except:
  118. cameras_extrinsic_file = os.path.join(path, "sparse/0", "images.txt")
  119. cameras_intrinsic_file = os.path.join(path, "sparse/0", "cameras.txt")
  120. cam_extrinsics = read_extrinsics_text(cameras_extrinsic_file)
  121. cam_intrinsics = read_intrinsics_text(cameras_intrinsic_file)
  122. reading_dir = "images" if images == None else images
  123. cam_infos_unsorted = readColmapCameras(cam_extrinsics=cam_extrinsics, cam_intrinsics=cam_intrinsics, images_folder=os.path.join(path, reading_dir))
  124. cam_infos = sorted(cam_infos_unsorted.copy(), key = lambda x : x.image_name)
  125. if eval:
  126. train_cam_infos = [c for idx, c in enumerate(cam_infos) if idx % llffhold != 0]
  127. test_cam_infos = [c for idx, c in enumerate(cam_infos) if idx % llffhold == 0]
  128. else:
  129. train_cam_infos = cam_infos
  130. test_cam_infos = []
  131. nerf_normalization = getNerfppNorm(train_cam_infos)
  132. ply_path = os.path.join(path, "sparse/0/points3d.ply")
  133. bin_path = os.path.join(path, "sparse/0/points3d.bin")
  134. txt_path = os.path.join(path, "sparse/0/points3d.txt")
  135. if not os.path.exists(ply_path):
  136. print("Converting point3d.bin to .ply, will happen only the first time you open the scene.")
  137. try:
  138. xyz, rgb, _ = read_points3D_binary(bin_path)
  139. except:
  140. xyz, rgb, _ = read_points3D_text(txt_path)
  141. storePly(ply_path, xyz, rgb)
  142. try:
  143. pcd = fetchPly(ply_path)
  144. except:
  145. pcd = None
  146. scene_info = SceneInfo(point_cloud=pcd,
  147. train_cameras=train_cam_infos,
  148. test_cameras=test_cam_infos,
  149. nerf_normalization=nerf_normalization,
  150. ply_path=ply_path)
  151. return scene_info
  152. def readCamerasFromTransforms(path, transformsfile, white_background, extension=".png"):
  153. cam_infos = []
  154. with open(os.path.join(path, transformsfile)) as json_file:
  155. contents = json.load(json_file)
  156. fovx = contents["camera_angle_x"]
  157. frames = contents["frames"]
  158. for idx, frame in enumerate(frames):
  159. cam_name = os.path.join(path, frame["file_path"] + extension)
  160. matrix = np.linalg.inv(np.array(frame["transform_matrix"]))
  161. R = -np.transpose(matrix[:3,:3])
  162. R[:,0] = -R[:,0]
  163. T = -matrix[:3, 3]
  164. image_path = os.path.join(path, cam_name)
  165. image_name = Path(cam_name).stem
  166. image = Image.open(image_path)
  167. im_data = np.array(image.convert("RGBA"))
  168. bg = np.array([1,1,1]) if white_background else np.array([0, 0, 0])
  169. norm_data = im_data / 255.0
  170. arr = norm_data[:,:,:3] * norm_data[:, :, 3:4] + bg * (1 - norm_data[:, :, 3:4])
  171. image = Image.fromarray(np.array(arr*255.0, dtype=np.byte), "RGB")
  172. fovy = focal2fov(fov2focal(fovx, image.size[0]), image.size[1])
  173. FovY = fovx
  174. FovX = fovy
  175. cam_infos.append(CameraInfo(uid=idx, R=R, T=T, FovY=FovY, FovX=FovX, image=image,
  176. image_path=image_path, image_name=image_name, width=image.size[0], height=image.size[1]))
  177. return cam_infos
  178. def readNerfSyntheticInfo(path, white_background, eval, extension=".png"):
  179. print("Reading Training Transforms")
  180. train_cam_infos = readCamerasFromTransforms(path, "transforms_train.json", white_background, extension)
  181. print("Reading Test Transforms")
  182. test_cam_infos = readCamerasFromTransforms(path, "transforms_test.json", white_background, extension)
  183. if not eval:
  184. train_cam_infos.extend(test_cam_infos)
  185. test_cam_infos = []
  186. nerf_normalization = getNerfppNorm(train_cam_infos)
  187. ply_path = os.path.join(path, "points3d.ply")
  188. if not os.path.exists(ply_path):
  189. # Since this data set has no colmap data, we start with random points
  190. num_pts = 100_000
  191. print(f"Generating random point cloud ({num_pts})...")
  192. # We create random points inside the bounds of the synthetic Blender scenes
  193. xyz = np.random.random((num_pts, 3)) * 2.6 - 1.3
  194. shs = np.random.random((num_pts, 3)) / 255.0
  195. pcd = BasicPointCloud(points=xyz, colors=SH2RGB(shs), normals=np.zeros((num_pts, 3)))
  196. storePly(ply_path, xyz, SH2RGB(shs) * 255)
  197. try:
  198. pcd = fetchPly(ply_path)
  199. except:
  200. pcd = None
  201. scene_info = SceneInfo(point_cloud=pcd,
  202. train_cameras=train_cam_infos,
  203. test_cameras=test_cam_infos,
  204. nerf_normalization=nerf_normalization,
  205. ply_path=ply_path)
  206. return scene_info
  207. sceneLoadTypeCallbacks = {
  208. "Colmap": readColmapSceneInfo,
  209. "Blender" : readNerfSyntheticInfo
  210. }