def parse_ply_file_extended(ifp): logger.info("Parse PLY File: ...") ply_data = PlyData.read(ifp) ( vertices, ply_data_vertex_dtype, ply_data_vertex_data_dtype, ) = PLYFileHandler.__ply_data_vertices_to_vetex_list(ply_data) ( faces, ply_data_face_type, ply_data_face_data_type, ) = PLYFileHandler.__ply_data_faces_to_face_list(ply_data) logger.info("Parse PLY File: Done") # return always 6 arguments. However, the latter may be empty return ( vertices, ply_data_vertex_dtype, ply_data_vertex_data_dtype, faces, ply_data_face_type, ply_data_face_data_type, )
def refine_mesh( self, openmvs_workspace_dp, openmvs_ifn, openmvs_ofn, lazy=False, mesh_ifp=None, use_cuda=False, ): # logger.info('refine_mesh: ...') # https://github.com/cdcseacave/openMVS/blob/master/apps/RefineMesh/RefineMesh.cpp # https://github.com/cdcseacave/openMVS/wiki/Usage if (not os.path.isfile(os.path.join(openmvs_workspace_dp, openmvs_ofn)) or not lazy): refine_mesh_call = [ self.refine_mesh_fp, "-i", openmvs_ifn, "-w", openmvs_workspace_dp, "-o", openmvs_ofn, "--use-cuda", str(int(use_cuda)), ] if mesh_ifp is not None: refine_mesh_call += ["--mesh-file", mesh_ifp] logger.info(str(refine_mesh_call)) refine_mesh_proc = subprocess.Popen(refine_mesh_call) refine_mesh_proc.wait()
def compute_cleaned_mesh( self, mesh_ifp, mesh_ofp, threshold=1.0, component_size=1000, delete_color=True, delete_scale=True, delete_conf=True, ): logger.info("compute_cleaned_mesh: ...") clean_call = [ self.meshclean_fp, # Threshold on the geometry confidence [1.0] # -t, --threshold=ARG "--threshold=" + str(threshold), # Minimum number of vertices per component [1000] # -c, --component-size=ARG "--component-size=" + str(component_size), ] if delete_color: clean_call += ["--delete-color"] if delete_scale: clean_call += ["--delete-scale"] if delete_conf: clean_call += ["--delete-conf"] clean_call += [mesh_ifp, mesh_ofp] subprocess.call(clean_call) logger.info("compute_cleaned_mesh: Done")
def write_camera_ply_file(ofp, cameras, plain_text_output=True): ply_data_vertex_data_dtype_list = [ ("x", "<f4"), ("y", "<f4"), ("z", "<f4"), ] ply_data_vertex_data_dtype_list += [ ("red", "u1"), ("green", "u1"), ("blue", "u1"), ] ply_data_vertex_data_dtype_list += [ ("nx", "<f4"), ("ny", "<f4"), ("nz", "<f4"), ] ply_data_vertex_data_dtype = np.dtype(ply_data_vertex_data_dtype_list) output_ply_data_vertex_element = ( PLYFileHandler.__cameras_2_ply_vertex_element( cameras, ply_data_vertex_data_dtype)) # [('x', '<f4'), ('y', '<f4'), ('z', '<f4'), ('red', 'u1'), ('green', 'u1'), ('blue', 'u1')] logger.info("Write (Camera) File With Vertices Only (no faces)") output_data = PlyData([output_ply_data_vertex_element], text=plain_text_output) output_data.write(ofp)
def convert_colmap_to_openMVS( self, colmap_dense_idp, openmvs_workspace_dp, openmvs_ofn, image_folder="images/", lazy=False, ): # logger.info('convert_colmap_to_openMVS: ...') openmvs_ofp = os.path.join(openmvs_workspace_dp, openmvs_ofn) if not os.path.isfile(openmvs_ofp) or not lazy: colmap_to_openmvs_call = [ self.interface_colmap_fp, "-i", colmap_dense_idp, "--image-folder", image_folder, "-w", openmvs_workspace_dp, "-o", openmvs_ofn, ] logger.info(str(colmap_to_openmvs_call)) colmap_to_openmvs_proc = subprocess.Popen(colmap_to_openmvs_call) colmap_to_openmvs_proc.wait()
def convert_vissat_config_json_to_geojson(vissat_config_json_ifp, geojson_ofp): with open(vissat_config_json_ifp) as json_file: vissat_config_json = json.load(json_file) bbx_utm = vissat_config_json["bounding_box"] zone_number = bbx_utm["zone_number"] hemisphere = bbx_utm["hemisphere"] ul_easting = bbx_utm["ul_easting"] ul_northing = bbx_utm["ul_northing"] logger.info("utm: {}".format( ([ul_easting, ul_northing, zone_number, hemisphere]))) # See write_aoi() in stereo_pipeline.py lr_easting = ul_easting + bbx_utm["width"] lr_northing = ul_northing - bbx_utm["height"] # compute a lat_lon bbx corners_easting = [ul_easting, lr_easting, lr_easting, ul_easting] corners_northing = [ul_northing, ul_northing, lr_northing, lr_northing] corners_lat = [] corners_lon = [] northern = True if hemisphere == "N" else False for i in range(4): lat, lon = utm.to_latlon( corners_easting[i], corners_northing[i], zone_number, northern=northern, ) corners_lat.append(lat) corners_lon.append(lon) lat_min = min(corners_lat) lat_max = max(corners_lat) lon_min = min(corners_lon) lon_max = max(corners_lon) lat, lon = utm_to_latlon(ul_easting, ul_northing, zone_number, hemisphere) logger.info("lat lon: {}".format([lat, lon])) vertex_list = [ (lon_min, lat_min), (lon_min, lat_max), (lon_max, lat_max), (lon_max, lat_min), ] # A LineString only marks the border of the area (but is not a closed # shape, i.e. no loop). # my_linestring = geojson.LineString(vertex_list) # geojson_str = geojson.dumps(my_linestring, sort_keys=True) # A polygon is filled by default in QGIS (style can be changed in the # raster layer properties) my_polygon = geojson.Polygon([vertex_list]) geojson_str = geojson.dumps(my_polygon, sort_keys=True) with open(geojson_ofp, "w") as geojson_file: geojson_file.write(geojson_str)
def write_colmap_model(odp, cameras, points, colmap_camera_model_name="SIMPLE_PINHOLE"): logger.info("Write Colmap model folder: " + odp) if not os.path.isdir(odp): os.mkdir(odp) # From ssr\ext\read_write_model.py # CameraModel = collections.namedtuple( # "CameraModel", ["model_id", "model_name", "num_params"]) # Camera = collections.namedtuple( # "Camera", ["id", "model", "width", "height", "params"]) # BaseImage = collections.namedtuple( # "Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"]) # Point3D = collections.namedtuple( # "Point3D", ["id", "xyz", "rgb", "error", "image_ids", "point2D_idxs"]) ( colmap_cams, colmap_images, ) = ColmapFileHandler.convert_cams_to_colmap_cams_and_colmap_images( cameras, colmap_camera_model_name) colmap_points3D = ColmapFileHandler.convert_points_to_colmap_points( points) write_model(colmap_cams, colmap_images, colmap_points3D, odp, ext=".txt")
def parse_colmap_model_folder(model_idp, image_idp): logger.info("Parse Colmap model folder: " + model_idp) ifp_s = os.listdir(model_idp) if (len( set(ifp_s).intersection( ["cameras.txt", "images.txt", "points3D.txt"])) == 3): ext = ".txt" elif (len( set(ifp_s).intersection( ["cameras.bin", "images.bin", "points3D.bin"])) == 3): ext = ".bin" else: assert False # No valid model folder # Cameras represent information about the camera model. # Images contain pose information. id_to_col_cameras, id_to_col_images, id_to_col_points3D = read_model( model_idp, ext=ext) cameras = ColmapFileHandler.convert_colmap_cams_to_cams( id_to_col_cameras, id_to_col_images, image_idp) points3D = ColmapFileHandler.convert_colmap_points_to_points( id_to_col_points3D) return cameras, points3D
def __ply_data_faces_to_face_list(ply_data): faces = [] ply_data_face_type = None ply_data_face_data_type = None if "face" in ply_data: # read faces ply_data_face_type = ply_data["face"].dtype logger.info("Found " + str(len(ply_data["face"].data)) + " faces") for line in ply_data["face"].data["vertex_indices"]: current_face = Face() current_face.vertex_indices = np.array( [line[0], line[1], line[2]]) faces.append(current_face) ply_data_face_data_type = [("vertex_indices", "i4", (3, ))] face_names = ply_data["face"].data.dtype.names if ("red" in face_names and "green" in face_names and "blue" in face_names): ply_data_face_data_type = [ ("vertex_indices", "i4", (3, )), ("red", "u1"), ("green", "u1"), ("blue", "u1"), ] return faces, ply_data_face_type, ply_data_face_data_type
def compute_texturing_with_mve(colmap_idp, mesh_ifp, texturing_odp, lazy=True): logger.info("compute_texturing_with_mve: ...") logger.info(f"colmap_idp: {colmap_idp}") logger.info(f"mesh_ifp: {mesh_ifp}") logger.info(f"texturing_odp: {texturing_odp}") mve_odp = os.path.dirname(texturing_odp) mve_mvs_reconstructor = MVEMVSReconstructor() # Import the colmap reconstruction into MVE exactly once, # since it is identical for all reconstructions. if not os.path.isdir(mve_odp): mve_mvs_reconstructor.create_scene_from_sfm_result( colmap_idp, mve_odp, downscale_level=-1, lazy=lazy) mve_mvs_reconstructor.compute_texture( mve_workspace=mve_odp, mesh_cleaned_ply_ifp=mesh_ifp, mesh_textured_odp=texturing_odp, ) logger.info("compute_texturing_with_mve: Done")
def compute_fssr_mesh_from_point_cloud( self, mve_point_cloud_with_scale_ply_fp, mesh_ply_fp ): logger.info("compute_mesh_from_point_cloud: ...") subprocess.call( [self.fssrecon_fp, mve_point_cloud_with_scale_ply_fp, mesh_ply_fp] ) logger.info("compute_mesh_from_point_cloud: Done")
def import_images_to_scene(self, image_idp): logger.info("import_images_to_scene: ...") make_scene_call = [ self.make_scene_fp, "--images-only", image_idp, self.workspace_folder, ] logger.vinfo("make_scene_call", make_scene_call) subprocess.call(make_scene_call)
def compute_mesh_with_openmvs(colmap_idp, odp, mesh_ply_ofn, plain_mesh_ply_ofn, lazy=False): logger.info("compute_mesh_with_openmvs: ...") # https://github.com/cdcseacave/openMVS/wiki/Usage # Exporting and Viewing Results # Each of the following commands also writes a PLY file that can be # used with many third-party tools. Alternatively, Viewer can be used # to export the MVS projects to PLY or OBJ formats. interface_mvs_fn = "interface_colmap.mvs" # This will create a "plain_mesh.ply" file mesh_mvs_fn = os.path.splitext(mesh_ply_ofn)[0] + ".mvs" openmvs_mvs_reconstructor = OpenMVSReconstructor() # This step already imports the dense point cloud computed by colmap # (Stored in fused.ply and fused.ply.vis) openmvs_mvs_reconstructor.convert_colmap_to_openMVS( colmap_dense_idp=colmap_idp, openmvs_workspace_dp=odp, openmvs_ofn=interface_mvs_fn, image_folder="images/", lazy=lazy, ) logger.vinfo("ofp", os.path.join(odp, mesh_ply_ofn)) # Adjust the default parameters (min_point_distance=2.5, # smoothing_iterations=2) to improve satellite reconstruction results min_point_distance = 0 smoothing_iterations = 0 openmvs_mvs_reconstructor.reconstruct_mesh( odp, interface_mvs_fn, mesh_mvs_fn, # OpenMVS Default Value: min_point_distance=2.5 min_point_distance=min_point_distance, # Clean options: # OpenMVS Default Value: decimate_value=1.0 decimate_value=1.0, # OpenMVS Default Value: smoothing_iterations=2 smoothing_iterations=smoothing_iterations, lazy=lazy, ) mesh_ply_ofp = os.path.join(odp, mesh_ply_ofn) plain_mesh_ply_ofp = os.path.join(odp, plain_mesh_ply_ofn) meshlab = Meshlab() meshlab.remove_color(mesh_ply_ofp, plain_mesh_ply_ofp) logger.info("compute_mesh_with_openmvs: Done")
def parse_ply_file(ifp): logger.info("Parse PLY File: ...") logger.vinfo("ifp", ifp) ply_data = PlyData.read(ifp) vertices, _, _ = PLYFileHandler.__ply_data_vertices_to_vetex_list( ply_data) faces, _, _ = PLYFileHandler.__ply_data_faces_to_face_list(ply_data) logger.info("Parse PLY File: Done") return vertices, faces
def create_config_from_template(config_template_ifp, config_fp): config_template_ifp = os.path.abspath(config_template_ifp) config_fp = os.path.abspath(config_fp) if not os.path.isfile(config_fp): logger.info(f"Config file {config_fp} not found") logger.info( f"Creating config file at {config_fp} from template {config_template_ifp}" ) copyfile(config_template_ifp, config_fp) return True else: return False
def compute_depth_maps(self, downscale_level=0): logger.info("compute_depth_maps: ...") downscale_s_str = "-s" + str(downscale_level) dm_recon_call = [ self.dm_recon_fp, downscale_s_str, self.workspace_folder, ] logger.vinfo("dm_recon_call", dm_recon_call) subprocess.call(dm_recon_call) logger.info("compute_depth_maps: ...")
def compute_mesh_with_mve( colmap_idp, odp, mesh_ply_ofn, plain_mesh_ply_ofn, meshing_algo, lazy=False, ): logger.info("compute_mesh_with_mve: ...") mesh_ply_ofp = os.path.join(odp, mesh_ply_ofn) plain_mesh_ply_ofp = os.path.join(odp, plain_mesh_ply_ofn) downscale_level = 0 mve_mvs_reconstructor = MVEMVSReconstructor() mve_mvs_reconstructor.create_scene_from_sfm_result( colmap_idp, odp, downscale_level=downscale_level, lazy=lazy) mve_mvs_reconstructor.compute_dense_point_cloud_from_depth_maps( odp, downscale_level=downscale_level, view_ids=None, fssr_output=True, lazy=lazy, ) meshlab = Meshlab() if meshing_algo == "fssr": mve_mvs_reconstructor.compute_fssr_reconstruction( odp, mesh_ply_ofp=mesh_ply_ofp, lazy=lazy) stem, ext = os.path.splitext(mesh_ply_ofp) cleaned_mesh_ply_ofp = stem + "_cleaned" + ext mve_mvs_reconstructor.compute_clean_mesh( odp, mesh_ply_ifp=mesh_ply_ofp, mesh_cleaned_ply_ofp=cleaned_mesh_ply_ofp, delete_color=False, lazy=lazy, ) meshlab.remove_color(cleaned_mesh_ply_ofp, plain_mesh_ply_ofp) elif meshing_algo == "gdmr": mve_mvs_reconstructor.compute_gdmr_reconstruction( odp, mesh_ply_ofp=mesh_ply_ofp, lazy=lazy) meshlab.remove_color(mesh_ply_ofp, plain_mesh_ply_ofp) else: logger.vinfo("meshing_algo", meshing_algo) assert False logger.vinfo("ofp", mesh_ply_ofp) logger.info("compute_mesh_with_mve: Done")
def process_mve_create_scene_tasks(self, mve_create_scene_tasks): for mve_create_scene_task in mve_create_scene_tasks: logger.info("Perform Create Scene Task: ...") self.workspace_folder = mve_create_scene_task.mve_model_dir self.create_scene_from_sfm_result( mve_create_scene_task.model_file_path ) logger.info("Perform Create Scene Task: Done") mve_point_cloud_ply_ofp = os.path.join( self.workspace_folder, mve_create_scene_task.mve_point_cloud_name, ) self.compute_dense_point_cloud_from_depth_maps( mve_point_cloud_ply_ofp )
def reconstruct_mesh_with_delaunay( self, reconstruction_idp, mesh_ply_ofp, max_proj_dist=None, max_depth_dist=None, lazy=False, ): logger.info("reconstruct_mesh: ...") assert os.path.isdir(self.colmap_exe_dp) assert os.path.isfile(os.path.join(self.colmap_exe_dp, "colmap")) if not os.path.isfile(mesh_ply_ofp) or not lazy: os.environ["PATH"] += os.pathsep + self.colmap_exe_dp dense_mesher_call = [ "colmap", "delaunay_mesher", "--input_path", reconstruction_idp, "--output_path", mesh_ply_ofp, "--input_type", "dense", ] if max_proj_dist is not None: dense_mesher_call += [ "--DelaunayMeshing.max_proj_dist", str(max_proj_dist), ] if max_depth_dist is not None: dense_mesher_call += [ "--DelaunayMeshing.max_depth_dist", str(max_depth_dist), ] logger.vinfo("dense_mesher_call: ", dense_mesher_call) subprocess.call(dense_mesher_call) logger.info("reconstruct_mesh: Done")
def create_textured_mesh_from_nvm_and_mesh( self, nvm_ifp, untextured_mesh_ifp, mve_workspace, texture_odp, lazy=True, ): logger.info("create_textured_mesh_from_nvm: ...") mve_creator = MVECreator(workspace_dp=mve_workspace) if not lazy or not os.path.isdir(mve_workspace): mve_creator.create_scene_from_sfm_result(nvm_ifp) texrecon = MVETexrecon() texrecon.create_textured_mesh( mve_workspace, untextured_mesh_ifp, texture_odp ) logger.info("create_textured_mesh_from_nvm: Done")
def texture_mesh( self, openmvs_workspace_dp, openmvs_ifn, openmvs_ofn, export_type, lazy=False, ): # export_type: '.ply' or '.obj' logger.info("texture_mesh: ...") if not (export_type == "ply" or export_type == "obj"): logger.vinfo("export_type", export_type) assert False export_file = os.path.splitext(openmvs_ofn)[0] + "." + export_type if (not os.path.isfile(openmvs_ofn) or not os.path.isfile(export_file) or not lazy): texture_mesh_call = [ self.texture_mesh_fp, "-i", openmvs_ifn, "-w", openmvs_workspace_dp, "-o", openmvs_ofn, "--export-type", export_type, ] logger.info(str(texture_mesh_call)) pRecons = subprocess.Popen(texture_mesh_call) pRecons.wait() logger.info("texture_mesh: Done")
def convert_visualsfm_to_openMVS(self, nvm_ifp, image_idp, openmvs_workspace_temp, openmvs_ofp): logger.info("convert_visualsfm_to_openMVS: ...") for possible_img_file in os.listdir(image_idp): img_ifp = os.path.join(image_idp, possible_img_file) img_ofp = os.path.join(openmvs_workspace_temp, possible_img_file) if os.path.isfile(img_ifp) and not os.path.isfile(img_ofp): shutil.copyfile(img_ifp, img_ofp) # InterfaceVisualSFM scene.nvm visualsfm_to_openmvs_call = [ self.interface_visualsfm_fp, "-i", nvm_ifp, "-w", openmvs_workspace_temp, "-o", openmvs_ofp, ] logger.info(str(visualsfm_to_openmvs_call)) visualsfm_to_openmvs_proc = subprocess.Popen(visualsfm_to_openmvs_call) visualsfm_to_openmvs_proc.wait() logger.info("convert_visualsfm_to_openMVS: Done")
def densify_point_cloud(self, openmvs_workspace_dp, openmvs_ifn, openmvs_ofn, lazy=False): logger.info("densify_point_cloud: ...") # The workspace folder defined by "-w" # MUST BE THE FOLDER WHICH CONTAINS THE *.mvs file if (not os.path.isfile(os.path.join(openmvs_workspace_dp, openmvs_ofn)) or not lazy): densify_point_cloud_call = [ self.densify_point_cloud_fp, "-i", openmvs_ifn, "-w", openmvs_workspace_dp, "-o", openmvs_ofn, ] logger.info(str(densify_point_cloud_call)) densify_proc = subprocess.Popen(densify_point_cloud_call) densify_proc.wait() logger.info("densify_point_cloud: Done ")
def compute_mesh_with_colmap( colmap_idp, mesh_odp, mesh_ply_ofn, plain_mesh_ply_ofn, meshing_algo, poisson_trim_thresh=10, lazy=False, ): """Poisson meshing works with a single point-cloud-ply-file, whereas delaunay meshing requires a full workspace. """ logger.info("compute_mesh_with_colmap: ...") assert meshing_algo in ["poisson_mesher", "delaunay_mesher"] mesh_ply_ofp = os.path.join(mesh_odp, mesh_ply_ofn) logger.vinfo("ofp", mesh_ply_ofp) colmap_mvs_reconstructor = ColmapMVSReconstructor() if meshing_algo == "poisson_mesher": point_cloud_ply_ifp = os.path.join(colmap_idp, "fused.ply") colmap_mvs_reconstructor.reconstruct_mesh_with_poisson( point_cloud_ply_ifp, mesh_ply_ofp, poisson_trim_thresh, lazy=lazy, ) elif meshing_algo == "delaunay_mesher": colmap_mvs_reconstructor.reconstruct_mesh_with_delaunay( colmap_idp, mesh_ply_ofp, # https://colmap.github.io/faq.html max_proj_dist=0, # Colmap Default Value: 2.5 lazy=lazy, ) plain_mesh_ply_ofp = os.path.join(mesh_odp, plain_mesh_ply_ofn) meshlab = Meshlab() meshlab.remove_color(mesh_ply_ofp, plain_mesh_ply_ofp) logger.info("compute_mesh_with_colmap: Done")
def compute_dense_point_cloud_from_depth_maps( self, mve_point_cloud_ply_ofp, downscale_level=0, view_ids=None, fssr_output=True, ): logger.info("compute_dense_point_cloud_from_depth_maps: ...") assert os.path.splitext(mve_point_cloud_ply_ofp)[1] == ".ply" scene2pset_call = [self.scene2pset_fp] if fssr_output: downscale_F_str = "-F" + str(downscale_level) scene2pset_call.append(downscale_F_str) if view_ids is not None: view_id_str = "--views=" + ",".join(map(str, view_ids)) scene2pset_call.append(view_id_str) scene2pset_call.append(self.workspace_folder) scene2pset_call.append(mve_point_cloud_ply_ofp) logger.vinfo("scene2pset_call", scene2pset_call) subprocess.call(scene2pset_call) logger.info("compute_dense_point_cloud_from_depth_maps: Done")
def create_scene_from_sfm_result(self, sfm_fp_or_dp, downscale_level=None): # Input can be a nvm file or a colmap model folder assert self.workspace_folder is not None logger.info("Creating Scene: ...") logger.info("Input file/folder: " + sfm_fp_or_dp) logger.info("Output Path: " + self.workspace_folder) downscale_s_str = "-s" + str(downscale_level) make_scene_call = [ self.make_scene_fp, downscale_s_str, sfm_fp_or_dp, self.workspace_folder, ] logger.vinfo("make_scene_call", make_scene_call) subprocess.call(make_scene_call) logger.info("Creating Scene: Done")
def reconstruct_mesh_with_poisson(self, point_cloud_ply_ifp, mesh_ply_ofp, poisson_trim_thresh, lazy): logger.info("reconstruct_mesh: ...") logger.vinfo("mesh_ply_ofp", mesh_ply_ofp) assert os.path.isdir(self.colmap_exe_dp) assert os.path.isfile(os.path.join(self.colmap_exe_dp, "colmap")) if not os.path.isfile(mesh_ply_ofp) or not lazy: os.environ["PATH"] += os.pathsep + self.colmap_exe_dp trim_thresh_str = str(poisson_trim_thresh) dense_mesher_call = [ "colmap", "poisson_mesher", "--input_path", point_cloud_ply_ifp, "--output_path", mesh_ply_ofp, "--PoissonMeshing.trim", trim_thresh_str, ] logger.vinfo("dense_mesher_call: ", dense_mesher_call) subprocess.call(dense_mesher_call) logger.info("reconstruct_mesh: Done")
def write_ply_file( ofp, vertices, with_colors=True, with_normals=False, faces=None, plain_text_output=False, with_measurements=False, ): logger.info("write_ply_file: " + ofp) ply_data_vertex_data_dtype_list = PLYFileHandler.build_type_list( vertices, with_colors, with_normals, with_measurements) logger.vinfo("ply_data_vertex_data_dtype_list", ply_data_vertex_data_dtype_list) output_ply_data_vertex_element = ( PLYFileHandler.__vertices_to_ply_vertex_element( vertices, ply_data_vertex_data_dtype_list)) if faces is None or len(faces) == 0: logger.info("Write File With Vertices Only (no faces)") output_data = PlyData([output_ply_data_vertex_element], text=plain_text_output) else: logger.info("Write File With Faces") logger.info("Number faces" + str(len(faces))) ply_data_face_data_type = [("vertex_indices", "i4", (3, ))] # we do not define colors for faces, # since we use the vertex colors to colorize the face output_ply_data_face_element = ( PLYFileHandler.__faces_to_ply_face_element( faces, ply_data_face_data_type)) output_data = PlyData( [output_ply_data_vertex_element, output_ply_data_face_element], text=plain_text_output, ) output_data.write(ofp)
def get_option_value(self, option, target_type, section=None): """ :param section: :param option: :param target_type: :return: """ if section is None: section = SSRConfig.default_section try: if target_type == list: option_str = self.config.get(section, option) option_str = SSRConfig.detect_missing_commas(option_str) option_str = SSRConfig.remove_appended_commas(option_str) result = json.loads(option_str) else: option_str = self.config.get(section, option) option_str = option_str.split("#")[0].rstrip() if ( target_type == bool ): # Allow True/False bool values in addition to 1/0 result = ( option_str == "True" or option_str == "T" or option_str == "1" ) else: result = target_type(option_str) except configparser.NoOptionError as NoOptErr: logger.info("ERROR: " + str(NoOptErr)) logger.info("CONFIG FILE: " + self.config_fp) assert False # Option Missing except: logger.info("option_str: " + str(option_str)) raise return result
def convert_depth_map_to_cam_coords( self, depth_map, depth_map_semantic, shift_to_pixel_center, # False for Colmap, True for MVE depth_map_display_sparsity=100, ): assert 0 < depth_map_display_sparsity height, width = depth_map.shape logger.info("height " + str(height)) logger.info("width " + str(width)) if self.height == height and self.width == width: x_step_size = 1.0 y_step_size = 1.0 else: x_step_size = self.width / width y_step_size = self.height / height logger.info("x_step_size " + str(x_step_size)) logger.info("y_step_size " + str(y_step_size)) fx, fy, skew, cx, cy = self.split_intrinsic_mat( self.get_calibration_mat()) logger.vinfo("fx, fy, skew, cx, cy: ", str([fx, fy, skew, cx, cy])) indices = np.indices((height, width)) y_index_list = indices[0].flatten() x_index_list = indices[1].flatten() depth_values = depth_map.flatten() assert len(x_index_list) == len(y_index_list) == len(depth_values) if shift_to_pixel_center: # https://github.com/simonfuhrmann/mve/blob/master/libs/mve/depthmap.cc # math::Vec3f v = invproj * math::Vec3f( # (float)x + 0.5f, (float)y + 0.5f, 1.0f); u_index_coord_list = x_step_size * x_index_list + 0.5 v_index_coord_list = y_step_size * y_index_list + 0.5 else: # https://github.com/colmap/colmap/blob/dev/src/base/reconstruction.cc # COLMAP assumes that the upper left pixel center is (0.5, 0.5) # i.e. pixels are already shifted u_index_coord_list = x_step_size * x_index_list v_index_coord_list = y_step_size * y_index_list # The cannoncial vectors are defined according to p.155 of # "Multiple View Geometry" by Hartley and Zisserman using a canonical # focal length of 1 , i.e. vec = [(x - cx) / fx, (y - cy) / fy, 1] skew_correction = (cy - v_index_coord_list) * skew / (fx * fy) x_coords_canonical = (u_index_coord_list - cx) / fx + skew_correction y_coords_canonical = (v_index_coord_list - cy) / fy z_coords_canonical = np.ones(len(depth_values), dtype=float) # Determine non-background data # non_background_flags = np.logical_not(np.isnan(depth_values)) depth_values_not_nan = np.nan_to_num(depth_values) non_background_flags = depth_values_not_nan > 0 x_coords_canonical_filtered = x_coords_canonical[non_background_flags] y_coords_canonical_filtered = y_coords_canonical[non_background_flags] z_coords_canonical_filtered = z_coords_canonical[non_background_flags] depth_values_filtered = depth_values[non_background_flags] if depth_map_display_sparsity != 100: x_coords_canonical_filtered = x_coords_canonical_filtered[:: depth_map_display_sparsity] y_coords_canonical_filtered = y_coords_canonical_filtered[:: depth_map_display_sparsity] z_coords_canonical_filtered = z_coords_canonical_filtered[:: depth_map_display_sparsity] depth_values_filtered = depth_values_filtered[:: depth_map_display_sparsity] if depth_map_semantic == Camera.DEPTH_MAP_WRT_CANONICAL_VECTORS: # In this case, the depth values are defined w.r.t. the canonical # vectors. This kind of depth data is used by Colmap. x_coords_filtered = (x_coords_canonical_filtered * depth_values_filtered) y_coords_filtered = (y_coords_canonical_filtered * depth_values_filtered) z_coords_filtered = (z_coords_canonical_filtered * depth_values_filtered) elif depth_map_semantic == Camera.DEPTH_MAP_WRT_UNIT_VECTORS: # In this case the depth values are defined w.r.t. the normalized # canonical vectors. This kind of depth data is used by MVE. cannonical_norms_filtered = np.linalg.norm( np.array( [ x_coords_canonical_filtered, y_coords_canonical_filtered, z_coords_canonical_filtered, ], dtype=float, ), axis=0, ) # Instead of normalizing the x,y and z component, we divide the # depth values by the corresponding norm. normalized_depth_values_filtered = (depth_values_filtered / cannonical_norms_filtered) x_coords_filtered = (x_coords_canonical_filtered * normalized_depth_values_filtered) y_coords_filtered = (y_coords_canonical_filtered * normalized_depth_values_filtered) z_coords_filtered = (z_coords_canonical_filtered * normalized_depth_values_filtered) else: assert False cam_coords = np.dstack( (x_coords_filtered, y_coords_filtered, z_coords_filtered))[0] return cam_coords