| | import math |
| | import os |
| |
|
| | import numpy as np |
| | import pygltflib |
| | import trimesh |
| | from PIL import Image, ImageFilter |
| |
|
| |
|
| | def quaternion_multiply(q1, q2): |
| | x1, y1, z1, w1 = q1 |
| | x2, y2, z2, w2 = q2 |
| | return [ |
| | w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, |
| | w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2, |
| | w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2, |
| | w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, |
| | ] |
| |
|
| |
|
| | def glb_add_lights(path_input, path_output): |
| | """ |
| | Adds directional lights in the horizontal plane to the glb file. |
| | :param path_input: path to input glb |
| | :param path_output: path to output glb |
| | :return: None |
| | """ |
| | glb = pygltflib.GLTF2().load(path_input) |
| |
|
| | N = 3 |
| | angle_step = 2 * math.pi / N |
| | elevation_angle = math.radians(75) |
| |
|
| | light_colors = [ |
| | [1.0, 0.0, 0.0], |
| | [0.0, 1.0, 0.0], |
| | [0.0, 0.0, 1.0], |
| | ] |
| |
|
| | lights_extension = { |
| | "lights": [ |
| | {"type": "directional", "color": light_colors[i], "intensity": 2.0} |
| | for i in range(N) |
| | ] |
| | } |
| |
|
| | if "KHR_lights_punctual" not in glb.extensionsUsed: |
| | glb.extensionsUsed.append("KHR_lights_punctual") |
| | glb.extensions["KHR_lights_punctual"] = lights_extension |
| |
|
| | light_nodes = [] |
| | for i in range(N): |
| | angle = i * angle_step |
| |
|
| | pos_rot = [0.0, 0.0, math.sin(angle / 2), math.cos(angle / 2)] |
| | elev_rot = [math.sin(elevation_angle / 2), 0.0, 0.0, math.cos(elevation_angle / 2)] |
| | rotation = quaternion_multiply(pos_rot, elev_rot) |
| |
|
| | node = { |
| | "rotation": rotation, |
| | "extensions": {"KHR_lights_punctual": {"light": i}}, |
| | } |
| | light_nodes.append(node) |
| |
|
| | light_node_indices = list(range(len(glb.nodes), len(glb.nodes) + N)) |
| | glb.nodes.extend(light_nodes) |
| |
|
| | root_node_index = glb.scenes[glb.scene].nodes[0] |
| | root_node = glb.nodes[root_node_index] |
| | if hasattr(root_node, "children"): |
| | root_node.children.extend(light_node_indices) |
| | else: |
| | root_node.children = light_node_indices |
| |
|
| | glb.save(path_output) |
| |
|
| |
|
| | def extrude_depth_3d( |
| | path_rgb, |
| | path_depth, |
| | output_model_scale=100, |
| | filter_size=3, |
| | coef_near=0.0, |
| | coef_far=1.0, |
| | emboss=0.3, |
| | f_thic=0.05, |
| | f_near=-0.15, |
| | f_back=0.01, |
| | vertex_colors=True, |
| | scene_lights=True, |
| | ): |
| | f_far_inner = -emboss |
| | f_far_outer = f_far_inner - f_back |
| |
|
| | f_near = max(f_near, f_far_inner) |
| |
|
| | depth_image = Image.open(path_depth) |
| | assert depth_image.mode == "I", depth_image.mode |
| | depth_image = depth_image.filter(ImageFilter.MedianFilter(size=filter_size)) |
| |
|
| | w, h = depth_image.size |
| | d_max = max(w, h) |
| | depth_image = np.array(depth_image).astype(np.double) |
| | z_min, z_max = np.min(depth_image), np.max(depth_image) |
| | depth_image = (depth_image.astype(np.double) - z_min) / (z_max - z_min) |
| | depth_image[depth_image < coef_near] = coef_near |
| | depth_image[depth_image > coef_far] = coef_far |
| | depth_image = emboss * (depth_image - coef_near) / (coef_far - coef_near) |
| | rgb_image = np.array( |
| | Image.open(path_rgb).convert("RGB").resize((w, h), Image.Resampling.LANCZOS) |
| | ) |
| |
|
| | w_norm = w / float(d_max - 1) |
| | h_norm = h / float(d_max - 1) |
| | w_half = w_norm / 2 |
| | h_half = h_norm / 2 |
| |
|
| | x, y = np.meshgrid(np.arange(w), np.arange(h)) |
| | x = x / float(d_max - 1) - w_half |
| | y = -y / float(d_max - 1) + h_half |
| | z = -depth_image |
| | vertices_2d = np.stack((x, y, z), axis=-1) |
| | vertices = vertices_2d.reshape(-1, 3) |
| | colors = rgb_image[:, :, :3].reshape(-1, 3) / 255.0 |
| |
|
| | faces = [] |
| | for y in range(h - 1): |
| | for x in range(w - 1): |
| | idx = y * w + x |
| | faces.append([idx, idx + w, idx + 1]) |
| | faces.append([idx + 1, idx + w, idx + 1 + w]) |
| |
|
| | |
| |
|
| | nv = len(vertices) |
| | vertices = np.append( |
| | vertices, |
| | [ |
| | [-w_half - f_thic, -h_half - f_thic, f_near], |
| | [-w_half - f_thic, -h_half - f_thic, f_far_outer], |
| | [w_half + f_thic, -h_half - f_thic, f_near], |
| | [w_half + f_thic, -h_half - f_thic, f_far_outer], |
| | [w_half + f_thic, h_half + f_thic, f_near], |
| | [w_half + f_thic, h_half + f_thic, f_far_outer], |
| | [-w_half - f_thic, h_half + f_thic, f_near], |
| | [-w_half - f_thic, h_half + f_thic, f_far_outer], |
| | ], |
| | axis=0, |
| | ) |
| | faces.extend( |
| | [ |
| | [nv + 0, nv + 1, nv + 2], |
| | [nv + 2, nv + 1, nv + 3], |
| | [nv + 2, nv + 3, nv + 4], |
| | [nv + 4, nv + 3, nv + 5], |
| | [nv + 4, nv + 5, nv + 6], |
| | [nv + 6, nv + 5, nv + 7], |
| | [nv + 6, nv + 7, nv + 0], |
| | [nv + 0, nv + 7, nv + 1], |
| | ] |
| | ) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * 8, axis=0) |
| |
|
| | |
| |
|
| | nv = len(vertices) |
| | vertices_left_data = vertices_2d[:, 0] |
| | vertices_left_frame = vertices_2d[:, 0].copy() |
| | vertices_left_frame[:, 2] = f_near |
| | vertices = np.append(vertices, vertices_left_data, axis=0) |
| | vertices = np.append(vertices, vertices_left_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * h), axis=0) |
| | for i in range(h - 1): |
| | nvi_d = nv + i |
| | nvi_f = nvi_d + h |
| | faces.append([nvi_d, nvi_f, nvi_d + 1]) |
| | faces.append([nvi_d + 1, nvi_f, nvi_f + 1]) |
| |
|
| | nv = len(vertices) |
| | vertices_right_data = vertices_2d[:, -1] |
| | vertices_right_frame = vertices_2d[:, -1].copy() |
| | vertices_right_frame[:, 2] = f_near |
| | vertices = np.append(vertices, vertices_right_data, axis=0) |
| | vertices = np.append(vertices, vertices_right_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * h), axis=0) |
| | for i in range(h - 1): |
| | nvi_d = nv + i |
| | nvi_f = nvi_d + h |
| | faces.append([nvi_d, nvi_d + 1, nvi_f]) |
| | faces.append([nvi_d + 1, nvi_f + 1, nvi_f]) |
| |
|
| | nv = len(vertices) |
| | vertices_top_data = vertices_2d[0, :] |
| | vertices_top_frame = vertices_2d[0, :].copy() |
| | vertices_top_frame[:, 2] = f_near |
| | vertices = np.append(vertices, vertices_top_data, axis=0) |
| | vertices = np.append(vertices, vertices_top_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * w), axis=0) |
| | for i in range(w - 1): |
| | nvi_d = nv + i |
| | nvi_f = nvi_d + w |
| | faces.append([nvi_d, nvi_d + 1, nvi_f]) |
| | faces.append([nvi_d + 1, nvi_f + 1, nvi_f]) |
| |
|
| | nv = len(vertices) |
| | vertices_bottom_data = vertices_2d[-1, :] |
| | vertices_bottom_frame = vertices_2d[-1, :].copy() |
| | vertices_bottom_frame[:, 2] = f_near |
| | vertices = np.append(vertices, vertices_bottom_data, axis=0) |
| | vertices = np.append(vertices, vertices_bottom_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 * w), axis=0) |
| | for i in range(w - 1): |
| | nvi_d = nv + i |
| | nvi_f = nvi_d + w |
| | faces.append([nvi_d, nvi_f, nvi_d + 1]) |
| | faces.append([nvi_d + 1, nvi_f, nvi_f + 1]) |
| |
|
| | |
| |
|
| | nv = len(vertices) |
| | vertices = np.append( |
| | vertices, |
| | [ |
| | [-w_half - f_thic, -h_half - f_thic, f_near], |
| | [-w_half - f_thic, h_half + f_thic, f_near], |
| | ], |
| | axis=0, |
| | ) |
| | vertices = np.append(vertices, vertices_left_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + h), axis=0) |
| | for i in range(h - 1): |
| | faces.append([nv, nv + 2 + i + 1, nv + 2 + i]) |
| | faces.append([nv, nv + 2, nv + 1]) |
| |
|
| | nv = len(vertices) |
| | vertices = np.append( |
| | vertices, |
| | [ |
| | [w_half + f_thic, h_half + f_thic, f_near], |
| | [w_half + f_thic, -h_half - f_thic, f_near], |
| | ], |
| | axis=0, |
| | ) |
| | vertices = np.append(vertices, vertices_right_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + h), axis=0) |
| | for i in range(h - 1): |
| | faces.append([nv, nv + 2 + i, nv + 2 + i + 1]) |
| | faces.append([nv, nv + h + 1, nv + 1]) |
| |
|
| | nv = len(vertices) |
| | vertices = np.append( |
| | vertices, |
| | [ |
| | [w_half + f_thic, h_half + f_thic, f_near], |
| | [-w_half - f_thic, h_half + f_thic, f_near], |
| | ], |
| | axis=0, |
| | ) |
| | vertices = np.append(vertices, vertices_top_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + w), axis=0) |
| | for i in range(w - 1): |
| | faces.append([nv, nv + 2 + i, nv + 2 + i + 1]) |
| | faces.append([nv, nv + 1, nv + 2]) |
| |
|
| | nv = len(vertices) |
| | vertices = np.append( |
| | vertices, |
| | [ |
| | [-w_half - f_thic, -h_half - f_thic, f_near], |
| | [w_half + f_thic, -h_half - f_thic, f_near], |
| | ], |
| | axis=0, |
| | ) |
| | vertices = np.append(vertices, vertices_bottom_frame, axis=0) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * (2 + w), axis=0) |
| | for i in range(w - 1): |
| | faces.append([nv, nv + 2 + i + 1, nv + 2 + i]) |
| | faces.append([nv, nv + 1, nv + w + 1]) |
| |
|
| | |
| |
|
| | nv = len(vertices) |
| | vertices = np.append( |
| | vertices, |
| | [ |
| | [-w_half - f_thic, -h_half - f_thic, f_far_outer], |
| | [w_half + f_thic, -h_half - f_thic, f_far_outer], |
| | [w_half + f_thic, h_half + f_thic, f_far_outer], |
| | [-w_half - f_thic, h_half + f_thic, f_far_outer], |
| | ], |
| | axis=0, |
| | ) |
| | faces.extend( |
| | [ |
| | [nv + 0, nv + 2, nv + 1], |
| | [nv + 2, nv + 0, nv + 3], |
| | ] |
| | ) |
| | colors = np.append(colors, [[0.5, 0.5, 0.5]] * 4, axis=0) |
| |
|
| | trimesh_kwargs = {} |
| | if vertex_colors: |
| | trimesh_kwargs["vertex_colors"] = colors |
| | mesh = trimesh.Trimesh(vertices=vertices, faces=faces, **trimesh_kwargs) |
| |
|
| | mesh.merge_vertices() |
| |
|
| | current_max_dimension = max(mesh.extents) |
| | scaling_factor = output_model_scale / current_max_dimension |
| | mesh.apply_scale(scaling_factor) |
| |
|
| | path_out_base = os.path.splitext(path_depth)[0].replace("_16bit", "") |
| | path_out_glb = path_out_base + ".glb" |
| | path_out_stl = path_out_base + ".stl" |
| |
|
| | mesh.export(path_out_glb, file_type="glb") |
| | if scene_lights: |
| | glb_add_lights(path_out_glb, path_out_glb) |
| |
|
| | mesh.export(path_out_stl, file_type="stl") |
| |
|
| | return path_out_glb, path_out_stl |
| |
|