2025-03-14 18:02:23 +01:00
|
|
|
from pathlib import Path
|
2024-04-19 13:38:09 +02:00
|
|
|
from sys import exit
|
2025-03-14 18:02:23 +01:00
|
|
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
import numpy as np
|
|
|
|
|
from configargparse import (
|
|
|
|
|
ArgParser,
|
|
|
|
|
ArgumentDefaultsRawHelpFormatter,
|
|
|
|
|
YAMLConfigFileParser,
|
|
|
|
|
)
|
2024-04-19 13:38:09 +02:00
|
|
|
from open3d.io import read_pinhole_camera_parameters, write_image
|
|
|
|
|
from open3d.utility import Vector3dVector
|
2025-03-14 18:02:23 +01:00
|
|
|
from open3d.visualization.rendering import MaterialRecord, OffscreenRenderer
|
2024-04-19 13:38:09 +02:00
|
|
|
from pointcloudset import Dataset
|
|
|
|
|
from rich.progress import track
|
|
|
|
|
|
|
|
|
|
from util import (
|
|
|
|
|
calculate_average_frame_rate,
|
2025-03-14 18:02:23 +01:00
|
|
|
create_video_from_images,
|
|
|
|
|
existing_file,
|
|
|
|
|
load_dataset,
|
2024-04-19 13:38:09 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_3d_images(
|
|
|
|
|
dataset: Dataset,
|
|
|
|
|
camera_config_input_json_path: Path,
|
|
|
|
|
output_images_path: Path,
|
|
|
|
|
image_pattern_prefix,
|
|
|
|
|
) -> list[Path]:
|
|
|
|
|
camera_params = read_pinhole_camera_parameters("saved_camera_settings.json")
|
|
|
|
|
|
|
|
|
|
width, height = 1920, 1080
|
|
|
|
|
renderer = OffscreenRenderer(width, height)
|
|
|
|
|
renderer.setup_camera(
|
|
|
|
|
intrinsic_matrix=camera_params.intrinsic.intrinsic_matrix,
|
|
|
|
|
extrinsic_matrix=camera_params.extrinsic,
|
|
|
|
|
intrinsic_height_px=camera_params.intrinsic.height,
|
|
|
|
|
intrinsic_width_px=camera_params.intrinsic.width,
|
|
|
|
|
)
|
|
|
|
|
renderer.scene.set_background([1, 1, 1, 1])
|
|
|
|
|
|
|
|
|
|
def color_points_by_range(pcd):
|
|
|
|
|
points = np.asarray(pcd.points)
|
|
|
|
|
distances = np.linalg.norm(points, axis=1)
|
|
|
|
|
max_distance = distances.max()
|
|
|
|
|
min_distance = distances.min()
|
2025-03-14 18:02:23 +01:00
|
|
|
normalized_distances = (distances - min_distance) / (
|
|
|
|
|
max_distance - min_distance
|
|
|
|
|
)
|
2024-04-19 13:38:09 +02:00
|
|
|
colors = plt.get_cmap("jet")(normalized_distances)[:, :3]
|
|
|
|
|
pcd.colors = Vector3dVector(colors)
|
|
|
|
|
return pcd
|
|
|
|
|
|
|
|
|
|
rendered_images = []
|
|
|
|
|
|
2025-03-14 18:02:23 +01:00
|
|
|
for i, pc in track(
|
|
|
|
|
enumerate(dataset, 1), description="Rendering images...", total=len(dataset)
|
|
|
|
|
):
|
2024-04-19 13:38:09 +02:00
|
|
|
o3d_pc = pc.to_instance("open3d")
|
|
|
|
|
o3d_pc = color_points_by_range(o3d_pc)
|
|
|
|
|
renderer.scene.add_geometry("point_cloud", o3d_pc, MaterialRecord())
|
|
|
|
|
image_path = output_images_path / f"{image_pattern_prefix}_{i:04d}.png"
|
|
|
|
|
write_image(image_path.as_posix(), renderer.render_to_image())
|
|
|
|
|
renderer.scene.remove_geometry("point_cloud")
|
|
|
|
|
rendered_images.append(image_path)
|
|
|
|
|
|
|
|
|
|
return rendered_images
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
parser = ArgParser(
|
|
|
|
|
config_file_parser_class=YAMLConfigFileParser,
|
|
|
|
|
default_config_files=["render3d_config.yaml"],
|
|
|
|
|
formatter_class=ArgumentDefaultsRawHelpFormatter,
|
|
|
|
|
description="Render a 3d representation of a point cloud",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
2025-03-14 18:02:23 +01:00
|
|
|
"--render-config-file", is_config_file=True, help="yaml config file path"
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--input-bag-path", required=True, type=existing_file, help="path to bag file"
|
2024-04-19 13:38:09 +02:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
2025-03-14 18:02:23 +01:00
|
|
|
"--tmp-files-path",
|
|
|
|
|
default=Path("./tmp"),
|
|
|
|
|
type=Path,
|
|
|
|
|
help="path temporary files will be written to",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--output-images",
|
|
|
|
|
type=bool,
|
|
|
|
|
default=True,
|
|
|
|
|
help="if rendered frames should be outputted as images",
|
2024-04-19 13:38:09 +02:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
2025-03-14 18:02:23 +01:00
|
|
|
"--output-images-path",
|
|
|
|
|
default=Path("./output"),
|
|
|
|
|
type=Path,
|
|
|
|
|
help="path rendered frames should be written to",
|
2024-04-19 13:38:09 +02:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
2025-03-14 18:02:23 +01:00
|
|
|
"--output-video",
|
|
|
|
|
type=bool,
|
|
|
|
|
default=True,
|
|
|
|
|
help="if rendered frames should be outputted as a video",
|
2024-04-19 13:38:09 +02:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--output-video-path",
|
|
|
|
|
default=Path("./output/2d_render.mp4"),
|
|
|
|
|
type=Path,
|
|
|
|
|
help="path rendered video should be written to",
|
|
|
|
|
)
|
2025-03-14 18:02:23 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--output-images-prefix",
|
|
|
|
|
default="2d_render",
|
|
|
|
|
type=str,
|
|
|
|
|
help="filename prefix for output",
|
|
|
|
|
)
|
2024-04-19 13:38:09 +02:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--camera-config-input-json-path",
|
|
|
|
|
default="./saved_camera_settings.json",
|
|
|
|
|
type=existing_file,
|
|
|
|
|
help="path to json file containing camera settings (can be created with the create_camera_settings.py script)",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
if args.output_images:
|
|
|
|
|
args.output_images_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
args.tmp_files_path = args.output_images_path
|
|
|
|
|
else:
|
|
|
|
|
args.tmp_files_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
if args.output_video:
|
|
|
|
|
args.output_video_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
2024-06-11 11:07:05 +02:00
|
|
|
dataset = load_dataset(args.input_bag_path)
|
2024-04-19 13:38:09 +02:00
|
|
|
|
|
|
|
|
images = render_3d_images(
|
2025-03-14 18:02:23 +01:00
|
|
|
dataset,
|
|
|
|
|
args.camera_config_input_json_path,
|
|
|
|
|
args.tmp_files_path,
|
|
|
|
|
args.output_images_prefix,
|
2024-04-19 13:38:09 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if args.output_video:
|
2025-03-14 18:02:23 +01:00
|
|
|
input_images_pattern = (
|
|
|
|
|
f"{args.tmp_files_path / args.output_images_prefix}_%04d.png"
|
|
|
|
|
)
|
|
|
|
|
create_video_from_images(
|
|
|
|
|
input_images_pattern,
|
|
|
|
|
args.output_video_path,
|
|
|
|
|
calculate_average_frame_rate(dataset),
|
|
|
|
|
)
|
2024-04-19 13:38:09 +02:00
|
|
|
|
|
|
|
|
if not args.output_images:
|
|
|
|
|
for image in images:
|
|
|
|
|
image.unlink()
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
exit(main())
|