File size: 3,212 Bytes
ad19db2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | """Offscreen renderer for point clouds and meshes (Open3D EGL, headless).
Produces clean multi-view sheets so reconstruction results can be judged
visually. Framing uses robust (percentile) bounds so a handful of stray
outlier points cannot shrink the subject to a speck.
"""
import os, argparse
import numpy as np
import open3d as o3d
from open3d.visualization import rendering
from PIL import Image, ImageDraw
W = H = 560
VIEWS = [("front", 0, 0), ("right", 90, 0), ("back", 180, 0),
("left", 270, 0), ("top", 0, 88), ("iso", 40, 25)]
BG = (0.12, 0.13, 0.15, 1.0)
def robust_bounds(pts, lo=0.5, hi=99.5):
mn = np.percentile(pts, lo, axis=0)
mx = np.percentile(pts, hi, axis=0)
return mn, mx
def geom_points(geom):
if isinstance(geom, o3d.geometry.PointCloud):
return np.asarray(geom.points)
return np.asarray(geom.vertices)
def render_geometry(geom, out_prefix, point_size=2.5, fill=0.85, label=None):
pts = geom_points(geom)
mn, mx = robust_bounds(pts)
center = (mn + mx) / 2.0
radius = float(np.linalg.norm(mx - mn)) / 2.0
dist = radius / (np.tan(np.radians(30.0)) * fill)
rend = rendering.OffscreenRenderer(W, H)
rend.scene.set_background(np.array(BG, dtype=np.float32))
mat = rendering.MaterialRecord()
if isinstance(geom, o3d.geometry.PointCloud):
mat.shader = "defaultUnlit"
mat.point_size = point_size
else:
mat.shader = "defaultLit"
mat.base_color = [0.78, 0.80, 0.85, 1.0]
rend.scene.add_geometry("g", geom, mat)
rend.scene.scene.enable_sun_light(True)
rend.scene.scene.set_sun_light([-0.3, -0.9, -0.5], [1.0, 1.0, 1.0], 75000)
tiles = []
for name, az, el in VIEWS:
a, e = np.radians(az), np.radians(el)
eye = center + dist * np.array([np.cos(e) * np.sin(a), np.sin(e), np.cos(e) * np.cos(a)])
up = [0, 1, 0] if el < 80 else [0, 0, -1]
rend.scene.camera.look_at(center, eye, up)
tiles.append((name, Image.fromarray(np.asarray(rend.render_to_image()))))
del rend
sheet = Image.new("RGB", (W * 3, H * 2), (30, 33, 38))
d = ImageDraw.Draw(sheet)
for i, (name, im) in enumerate(tiles):
x, y = (i % 3) * W, (i // 3) * H
sheet.paste(im, (x, y))
d.text((x + 10, y + 8), name, fill=(210, 215, 225))
if label:
d.text((10, H * 2 - 20), label, fill=(160, 168, 180))
sheet.save(out_prefix + "_sheet.png")
tiles[5][1].save(out_prefix + "_iso.png")
return out_prefix + "_sheet.png"
def load(path):
if path.endswith((".ply", ".obj")):
m = o3d.io.read_triangle_mesh(path)
if len(m.triangles) > 0:
m.compute_vertex_normals()
return m
if path.endswith(".obj"):
raise ValueError("empty obj: " + path)
return o3d.io.read_point_cloud(path)
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("input"); ap.add_argument("out_prefix")
ap.add_argument("--point-size", type=float, default=2.5)
ap.add_argument("--label", default=None)
a = ap.parse_args()
print(render_geometry(load(a.input), a.out_prefix,
point_size=a.point_size, label=a.label))
|