|
| 1 | +#!/usr/bin/env python2 |
| 2 | + |
| 3 | +# %% init |
| 4 | + |
| 5 | +import os |
| 6 | +import cv2 |
| 7 | +import pylab as plt |
| 8 | + |
| 9 | +import re |
| 10 | + |
| 11 | +import numpy as np |
| 12 | +from open3d import Image, \ |
| 13 | + PointCloud, \ |
| 14 | + Vector3dVector, \ |
| 15 | + draw_geometries, \ |
| 16 | + estimate_normals, \ |
| 17 | + read_point_cloud, \ |
| 18 | + write_point_cloud, \ |
| 19 | + KDTreeSearchParamKNN, \ |
| 20 | + KDTreeSearchParamHybrid, \ |
| 21 | + orient_normals_towards_camera_location |
| 22 | + |
| 23 | +import open3d |
| 24 | + |
| 25 | +from read_model import read_model, qvec2rotmat |
| 26 | +from pcloud_compute import * |
| 27 | + |
| 28 | +#import pickle |
| 29 | + |
| 30 | +def get_sensor_poses(args, cam, time_stamp): |
| 31 | + # Input folder |
| 32 | + folder = args.workspace_path |
| 33 | + cam_folder = os.path.join(folder, cam) |
| 34 | + assert(os.path.exists(cam_folder)) |
| 35 | + |
| 36 | + # From frame to world coordinate system |
| 37 | + sensor_poses = read_sensor_poses(os.path.join(folder, cam + ".csv"), |
| 38 | + identity_camera_to_image=True) |
| 39 | + |
| 40 | + world2cam = sensor_poses[time_stamp] |
| 41 | + |
| 42 | + return world2cam |
| 43 | + |
| 44 | + |
| 45 | +# %% |
| 46 | +def get_points2(img, us, vs, cam2world, depth_range): |
| 47 | + distance_img = pgm2distance(img, encoded=False) |
| 48 | + empty = np.zeros((1024,1024)) |
| 49 | + depth_img = (empty).astype(np.float32) |
| 50 | + |
| 51 | + |
| 52 | + if cam2world is not None: |
| 53 | + R = cam2world[:3, :3] |
| 54 | + t = cam2world[:3, 3] |
| 55 | + else: |
| 56 | + R, t = np.eye(3), np.zeros(3) |
| 57 | + |
| 58 | + # from '[-1,1]x[-1,1]' to '[0,xdim]x[0,ydim]' |
| 59 | + xdim = depth_img.shape[1] |
| 60 | + ydim = depth_img.shape[0] |
| 61 | + fx = xdim/2 |
| 62 | + fy = ydim/2 |
| 63 | + cx = fx |
| 64 | + cy = fy |
| 65 | + cam_intrinsics = np.identity(3) |
| 66 | + cam_intrinsics[0,0] = fx |
| 67 | + cam_intrinsics[1,1] = fy |
| 68 | + cam_intrinsics[0,2] = cx |
| 69 | + cam_intrinsics[1,2] = cy |
| 70 | + |
| 71 | + |
| 72 | + points = [] |
| 73 | + for i in np.arange(distance_img.shape[0]): |
| 74 | + for j in np.arange(distance_img.shape[1]): |
| 75 | + x = us[i, j] |
| 76 | + y = vs[i, j] |
| 77 | + D = distance_img[i, j] |
| 78 | + z = - float(D) / np.sqrt(x*x + y*y + 1) |
| 79 | + |
| 80 | + if np.isinf(x) or np.isinf(y) or D < depth_range[0] or D > depth_range[1]: |
| 81 | +# depth_img[i,j] = 0 |
| 82 | + continue |
| 83 | + |
| 84 | + |
| 85 | +# print("arr", np.array([x, y, 1.])) |
| 86 | + |
| 87 | + point2D = cam_intrinsics.dot(np.array([x, y, 1.])) |
| 88 | + ii = np.int32(point2D[1]) |
| 89 | + jj = np.int32(point2D[0]) |
| 90 | + if ii < 0 or jj < 0: |
| 91 | +# print "What!!!" |
| 92 | +# print("original", np.array([x, y, 1.])) # (u,v) bigger than 1 or -1 ?? Why?!! |
| 93 | +# print("point2D", point2D) |
| 94 | + continue |
| 95 | + depth_img[ii,jj] = - z |
| 96 | + |
| 97 | + |
| 98 | + # 3D point in camera coordinate system |
| 99 | + point = np.array([x, y, 1.]) * z |
| 100 | + |
| 101 | + # Camera to World |
| 102 | + point = np.dot(R, point) + t |
| 103 | + |
| 104 | + points.append(point) |
| 105 | + |
| 106 | +# # for debug |
| 107 | +# plt.subplot(1, 3, 1) |
| 108 | +# plt.title('img') |
| 109 | +# plt.imshow(img) |
| 110 | +# plt.subplot(1, 3, 2) |
| 111 | +# plt.title('distance_img') |
| 112 | +# plt.imshow(distance_img) |
| 113 | +# plt.subplot(1, 3, 3) |
| 114 | +# plt.title('depth_img') |
| 115 | +# plt.imshow(depth_img) |
| 116 | +# plt.show() |
| 117 | + |
| 118 | + return np.vstack(points), depth_img |
| 119 | + |
| 120 | + |
| 121 | +def process_folder2(args, cam): |
| 122 | + # Input folder |
| 123 | + folder = args.workspace_path |
| 124 | + cam_folder = os.path.join(folder, cam) |
| 125 | + assert(os.path.exists(cam_folder)) |
| 126 | + # Output folder |
| 127 | + output_folder = os.path.join(args.output_path, cam) |
| 128 | + if not os.path.exists(output_folder): |
| 129 | + os.makedirs(output_folder) |
| 130 | + |
| 131 | + # Get camera projection info |
| 132 | + bin_path = os.path.join(args.workspace_path, "%s_camera_space_projection.bin" % cam) |
| 133 | + |
| 134 | + # From frame to world coordinate system |
| 135 | + sensor_poses = None |
| 136 | + if not args.ignore_sensor_poses: |
| 137 | + sensor_poses = read_sensor_poses(os.path.join(folder, cam + ".csv"), identity_camera_to_image=True) |
| 138 | + |
| 139 | + # Get appropriate depth thresholds |
| 140 | + depth_range = LONG_THROW_RANGE if 'long' in cam else SHORT_THROW_RANGE |
| 141 | + |
| 142 | + # Get depth paths |
| 143 | + depth_paths = sorted(glob(os.path.join(cam_folder, "*pgm"))) |
| 144 | + if args.max_num_frames == -1: |
| 145 | + args.max_num_frames = len(depth_paths) |
| 146 | + depth_paths = depth_paths[args.start_frame:(args.start_frame + args.max_num_frames)] |
| 147 | + |
| 148 | + us = vs = None |
| 149 | + # Process paths |
| 150 | + merge_points = args.merge_points |
| 151 | + rewrite = args.rewrite |
| 152 | + use_cache = args.use_cache |
| 153 | + points_merged = [] |
| 154 | + normals_merged = [] |
| 155 | + |
| 156 | + volume = open3d.ScalableTSDFVolume(voxel_length = 4.0 / 512.0, |
| 157 | + sdf_trunc = 0.1, color_type = open3d.TSDFVolumeColorType.None) |
| 158 | +# depth_sampling_stride = 16) |
| 159 | + |
| 160 | + for i_path, path in enumerate(depth_paths): |
| 161 | + output_suffix = "_%s" % args.output_suffix if len(args.output_suffix) else "" |
| 162 | +# pcloud_output_path = os.path.join(output_folder, os.path.basename(path).replace(".pgm", "%s.obj" % output_suffix)) |
| 163 | + |
| 164 | + pcloud_output_path = \ |
| 165 | + os.path.join(output_folder, |
| 166 | + os.path.basename(path).replace( |
| 167 | + ".pgm","%s.ply" % output_suffix)) |
| 168 | + print("File: " + pcloud_output_path) |
| 169 | + |
| 170 | + # if file exist |
| 171 | + output_file_exist = os.path.exists(pcloud_output_path) |
| 172 | + if output_file_exist and use_cache: |
| 173 | + print("Progress (file cache): %d/%d" % (i_path+1, len(depth_paths))) |
| 174 | + pcd = read_point_cloud(pcloud_output_path) |
| 175 | + points = pcd.points |
| 176 | + normals = pcd.normals |
| 177 | + else: |
| 178 | + print("Progress: %d/%d" % (i_path+1, len(depth_paths))) |
| 179 | + img = cv2.imread(path, -1) |
| 180 | + if us is None or vs is None: |
| 181 | + us, vs = parse_projection_bin(bin_path, img.shape[1], img.shape[0]) |
| 182 | + cam2world = get_cam2world(path, sensor_poses) if sensor_poses is not None else None |
| 183 | + points, depth_img = get_points2(img, us, vs, cam2world, depth_range) |
| 184 | + |
| 185 | + # get normals (orientes towards the camera) |
| 186 | + t = cam2world[:3, 3] |
| 187 | + cam_center = t # (0,0,0) is the camera center |
| 188 | + |
| 189 | + pcd = PointCloud() |
| 190 | + pcd.points = Vector3dVector(points) |
| 191 | + |
| 192 | + # compute normal of the point cloud (per camera) |
| 193 | + estimate_normals(pcd, |
| 194 | + search_param = |
| 195 | + # KDTreeSearchParamKNN(knn = 30)) |
| 196 | + KDTreeSearchParamHybrid(radius = 0.2, max_nn = 30)) |
| 197 | + |
| 198 | + # orient normals |
| 199 | + orient_normals_towards_camera_location(pcd, cam_center) |
| 200 | + |
| 201 | + |
| 202 | + |
| 203 | + # get normal as python array |
| 204 | + normals = np.asarray(pcd.normals) |
| 205 | + |
| 206 | + # from '[-1,1]x[-1,1]' to '[0,xdim]x[0,ydim]' |
| 207 | + w = depth_img.shape[1] # xdim |
| 208 | + h = depth_img.shape[0] # ydim |
| 209 | + fx = w/2 |
| 210 | + fy = h/2 |
| 211 | + cx = fx |
| 212 | + cy = fy |
| 213 | + |
| 214 | + cam_intrinsic = open3d.PinholeCameraIntrinsic() |
| 215 | + cam_intrinsic.set_intrinsics(w, h, fx, fy, cx, cy) |
| 216 | + # print cam_intrinsic.intrinsic_matrix |
| 217 | + |
| 218 | + |
| 219 | + # convert to Open3d Image |
| 220 | + depth = open3d.Image((depth_img).astype(np.float32)) |
| 221 | + |
| 222 | + |
| 223 | + # create dummy variable |
| 224 | + # color_type = open3d.TSDFVolumeColorType.None |
| 225 | + # we aren't using color |
| 226 | + color = Image( (np.ones((w,h))).astype(np.uint8) ) |
| 227 | + # color = depth |
| 228 | + rgbd = open3d.create_rgbd_image_from_color_and_depth(color, depth, |
| 229 | + depth_scale=1.0, |
| 230 | + depth_trunc=4, |
| 231 | + convert_rgb_to_intensity = False); |
| 232 | + |
| 233 | +# rgbd = open3d.create_rgbd_image_from_color_and_depth(color, depth, |
| 234 | +# depth_scale=1.0, |
| 235 | +# depth_trunc = depth_range[1], |
| 236 | +# convert_rgb_to_intensity = True); |
| 237 | +# |
| 238 | + |
| 239 | + # This transformation is requiere before of the line: |
| 240 | + # |
| 241 | + # depth_img[... , ...] = - z |
| 242 | + # |
| 243 | + # in get_points(...) |
| 244 | + # |
| 245 | + img2cam = np.array( |
| 246 | + [[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) |
| 247 | + |
| 248 | + cam2world_new = np.dot(cam2world, img2cam) |
| 249 | + |
| 250 | + # print("volume.integrate") |
| 251 | + volume.integrate(rgbd, cam_intrinsic, np.linalg.inv(cam2world_new) ) |
| 252 | + |
| 253 | + if merge_points: |
| 254 | + points_merged.extend(points) |
| 255 | + normals_merged.extend(normals) |
| 256 | + |
| 257 | + if rewrite: |
| 258 | + write_point_cloud(pcloud_output_path, pcd) |
| 259 | + |
| 260 | + |
| 261 | + return points_merged, normals_merged, volume |
| 262 | + |
| 263 | + |
| 264 | +# %% |
| 265 | +class ArgsTest: |
| 266 | + workspace_path = '' |
| 267 | + output_path = '' |
| 268 | + ignore_sensor_poses = False |
| 269 | + start_frame = 0 |
| 270 | + max_num_frames = -1 |
| 271 | + output_suffix = '' |
| 272 | + merge_points = True |
| 273 | + use_cache = False |
| 274 | + rewrite = False |
| 275 | + |
| 276 | +args = ArgsTest() |
| 277 | +#args.workspace_path = "../../data/HoloLensRecording__2018_10_23__14_05_39/" |
| 278 | +args.workspace_path = "../../data/HoloLens_recording_sample/" |
| 279 | + |
| 280 | +args.output_path = args.workspace_path |
| 281 | + |
| 282 | +args.start_frame = 0 |
| 283 | +args.max_num_frames = 5 |
| 284 | + |
| 285 | + |
| 286 | +# %% |
| 287 | +cam = 'long_throw_depth' |
| 288 | +points_merged, normals_merged, volume = process_folder2(args, cam) |
| 289 | + |
| 290 | +pcd_merged = PointCloud() |
| 291 | +pcd_merged.points = Vector3dVector(points_merged) |
| 292 | +pcd_merged.normals = Vector3dVector(normals_merged) |
| 293 | + |
| 294 | +#pcd.paint_uniform_color([1, 0.706, 0]) |
| 295 | +#draw_geometries([pcd_merged]) |
| 296 | + |
| 297 | + |
| 298 | +#folder = args.workspace_path |
| 299 | +#write_point_cloud(folder + "\\" + cam + ".ply", pcd) |
| 300 | + |
| 301 | +#draw_geometries([pcd2]) |
| 302 | + |
| 303 | +#pcd_vol = volume.extract_point_cloud() |
| 304 | +#pcd_vol.transform([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) |
| 305 | +#print pcd_vol |
| 306 | +#draw_geometries([pcd_vol]) |
| 307 | + |
| 308 | + |
| 309 | +# %% |
| 310 | +pcd_vol = volume.extract_point_cloud() |
| 311 | +#pcd_vol.transform([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) |
| 312 | +#print pcd_vol |
| 313 | +pcd_merged.paint_uniform_color([1, 0.706, 0]) |
| 314 | +draw_geometries([pcd_vol, pcd_merged]) |
| 315 | + |
| 316 | + |
| 317 | +# %% |
| 318 | +draw_geometries([pcd_vol]) |
| 319 | + |
| 320 | + |
| 321 | +# %% |
| 322 | +mesh = volume.extract_triangle_mesh() |
| 323 | +mesh.compute_vertex_normals() |
| 324 | +print mesh |
| 325 | +draw_geometries([mesh]) |
| 326 | + |
0 commit comments