mapvggt / mapgs /data /scene.py
ChenmingWu's picture
Upload folder using huggingface_hub
b2efbe4 verified
Raw
History Blame Contribute Delete
8.62 kB
"""Procedural driving-scene generator (for synthetic data, §2.1).
The ground-truth world is built as a set of 3D Gaussians (road surface + lane
stripes + curbs + roadside structures + moving vehicles). Rendering it with the
same rasterizer yields multi-view-consistent GT images and GT depth, and the HD
map (ground height field + lane / boundary polylines) is read off the same
scene (optionally with injected noise for the map-robustness study, §4.6).
World frame: x = lateral (right), y = forward, z = up.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional
import math
import numpy as np
import torch
from mapgs.geometry.cameras import look_at_pose
from mapgs.hdmap.ground_field import grid_field_from_points, GridGroundField
from mapgs.render.gaussians import Gaussians, GROUP_DYNAMIC
@dataclass
class MapNoise:
height_std: float = 0.0 # m, additive ground-height noise
lane_offset_std: float = 0.0 # m, lateral lane shift
drop_prob: float = 0.0 # fraction of polyline segments dropped
@dataclass
class ProceduralScene:
static: Gaussians
dyn_canonical: List[Gaussians]
box_centers: torch.Tensor # [I, F, 3]
box_rots: torch.Tensor # [I, F, 3, 3]
box_size: torch.Tensor # [I, 3]
canon_idx: torch.Tensor # [I]
ground: GridGroundField
lanes: List[torch.Tensor]
boundaries: List[torch.Tensor]
K: torch.Tensor # [V, 3, 3]
cam2world: torch.Tensor # [F, V, 4, 4]
F: int
V: int
fps: int
def _box_gaussians(g, center, size, color, n, opacity=0.9, jitter_scale=0.06):
pts = (g_rand(g, (n, 3)) - 0.5) * torch.tensor(size)
means = pts + torch.tensor(center)
scales = torch.full((n, 3), jitter_scale)
quats = torch.zeros(n, 4); quats[:, 0] = 1
opac = torch.full((n,), opacity)
cols = torch.tensor(color)[None].repeat(n, 1) + 0.03 * g_rand(g, (n, 3))
return means.float(), scales.float(), quats.float(), opac.float(), cols.clamp(0, 1).float()
def g_rand(g, shape):
return torch.rand(shape, generator=g)
def _centerline_x(y, curve_amp, curve_freq):
return curve_amp * torch.sin(y * curve_freq)
def generate_scene(
seed: int = 0,
F: int = 20,
fps: int = 10,
H: int = 256,
W: int = 448,
n_dynamic: int = 2,
fov_deg: float = 70.0,
curve_amp: float = 2.0,
curve_freq: float = 0.02,
length: float = 70.0,
map_noise: Optional[MapNoise] = None,
) -> ProceduralScene:
g = torch.Generator().manual_seed(seed)
map_noise = map_noise or MapNoise()
V = 3
dt = 1.0 / fps
# ---- ground surface samples + height field ----
ys = torch.linspace(-5, length, 200)
xs = torch.linspace(-12, 12, 60)
GX, GY = torch.meshgrid(xs, ys, indexing="xy")
slope = 0.01
base_z = slope * GY + 0.3 * torch.sin(GY * 0.05)
gpts = torch.stack([GX.reshape(-1), GY.reshape(-1), base_z.reshape(-1)], -1)
def ground_z(x, y):
return slope * y + 0.3 * torch.sin(y * 0.05)
# map ground field (optionally noisy)
map_pts = gpts.clone().numpy()
if map_noise.height_std > 0:
map_pts[:, 2] += np.random.RandomState(seed).randn(map_pts.shape[0]) * map_noise.height_std
ground = grid_field_from_points(map_pts, spacing=0.7)
# ---- lanes & boundaries (polylines along the curved centerline) ----
yline = torch.linspace(0, length, 120)
cx = _centerline_x(yline, curve_amp, curve_freq)
lane_off = [-3.5, 0.0, 3.5]
bnd_off = [-7.0, 7.0]
lanes, boundaries = [], []
rs = np.random.RandomState(seed + 1)
for off in lane_off:
loff = off + (rs.randn() * map_noise.lane_offset_std)
pl = torch.stack([cx + loff, yline, ground_z(cx + loff, yline)], -1)
if map_noise.drop_prob > 0: # drop a contiguous chunk
keep = torch.rand(pl.shape[0], generator=g) > map_noise.drop_prob
pl = pl[keep] if keep.any() else pl
lanes.append(pl.float())
for off in bnd_off:
pl = torch.stack([cx + off, yline, ground_z(cx + off, yline)], -1)
boundaries.append(pl.float())
# ---- static GT gaussians: road + stripes + curbs + buildings/poles ----
parts = []
# road surface gaussians
ny = 140
yy = torch.linspace(0, length, ny)
cxx = _centerline_x(yy, curve_amp, curve_freq)
for lane_w in torch.linspace(-6, 6, 25):
xw = cxx + lane_w
means = torch.stack([xw, yy, ground_z(xw, yy)], -1)
scales = torch.full((ny, 3), 0.18); scales[:, 2] = 0.02
quats = torch.zeros(ny, 4); quats[:, 0] = 1
opac = torch.full((ny,), 0.95)
gray = 0.32 + 0.04 * g_rand(g, (ny, 1))
cols = gray.repeat(1, 3)
parts.append((means, scales, quats, opac, cols))
# lane stripes (dashed white)
for off in lane_off:
xw = cxx + off
dash = (torch.arange(ny) % 6 < 3)
means = torch.stack([xw, yy, ground_z(xw, yy) + 0.01], -1)[dash]
m = dash.sum()
scales = torch.full((m, 3), 0.10); scales[:, 2] = 0.02
quats = torch.zeros(m, 4); quats[:, 0] = 1
opac = torch.full((m,), 0.98)
cols = torch.full((m, 3), 0.9)
parts.append((means, scales, quats, opac, cols))
# roadside structures (buildings/poles) -> vertical content for L_vert
n_build = 10
for i in range(n_build):
side = 1 if g_rand(g, (1,)).item() > 0.5 else -1
y0 = float(g_rand(g, (1,)).item()) * length
x0 = side * (8 + 5 * float(g_rand(g, (1,)).item()))
h = 2 + 6 * float(g_rand(g, (1,)).item())
col = (0.3 + 0.5 * g_rand(g, (3,))).tolist()
parts.append(_box_gaussians(g, [x0 + _centerline_x(torch.tensor(y0), curve_amp, curve_freq).item(), y0, ground_z(torch.tensor(x0), torch.tensor(y0)).item() + h / 2],
[3.0, 3.0, h], col, n=200))
static = Gaussians(
means=torch.cat([p[0] for p in parts]),
scales=torch.cat([p[1] for p in parts]),
quats=torch.cat([p[2] for p in parts]),
opacities=torch.cat([p[3] for p in parts]),
colors=torch.cat([p[4] for p in parts]),
)
# ---- dynamic vehicles ----
dyn_canonical: List[Gaussians] = []
box_centers = torch.zeros(n_dynamic, F, 3)
box_rots = torch.eye(3).view(1, 1, 3, 3).repeat(n_dynamic, F, 1, 1)
box_size = torch.zeros(n_dynamic, 3)
canon_idx = torch.zeros(n_dynamic, dtype=torch.long)
for i in range(n_dynamic):
lane_x = lane_off[i % len(lane_off)]
y0 = 10 + 15 * float(g_rand(g, (1,)).item())
speed = 3 + 4 * float(g_rand(g, (1,)).item()) # m/s
size = [4.2, 1.9, 1.5]
box_size[i] = torch.tensor(size)
col = (0.2 + 0.6 * g_rand(g, (3,))).tolist()
for f in range(F):
y = y0 + speed * f * dt
x = _centerline_x(torch.tensor(y), curve_amp, curve_freq).item() + lane_x
z = ground_z(torch.tensor(x), torch.tensor(y)).item() + size[2] / 2
box_centers[i, f] = torch.tensor([x, y, z])
# canonical gaussians placed at frame-0 box pose
c0 = box_centers[i, 0].tolist()
dyn_canonical.append(Gaussians(*_box_gaussians(g, c0, size, col, n=150)))
# ---- cameras: ego along lane 0, 3-cam front rig ----
fx = 0.5 * W / math.tan(math.radians(fov_deg) / 2)
K = torch.tensor([[fx, 0, W / 2], [0, fx, H / 2], [0, 0, 1]]).float()[None].repeat(V, 1, 1)
cam_yaws = [math.radians(22), 0.0, math.radians(-22)] # left, center, right
cam_lat = [-0.4, 0.0, 0.4]
cam2world = torch.zeros(F, V, 4, 4)
ego_speed = 6.0
for f in range(F):
y_ego = 2 + ego_speed * f * dt
x_ego = _centerline_x(torch.tensor(y_ego), curve_amp, curve_freq).item()
z_ego = ground_z(torch.tensor(x_ego), torch.tensor(y_ego)).item() + 1.5
fwd = torch.tensor([_centerline_x(torch.tensor(y_ego + 1), curve_amp, curve_freq).item() - x_ego, 1.0, 0.0])
fwd = fwd / fwd.norm()
heading = math.atan2(fwd[0].item(), fwd[1].item())
for v in range(V):
yaw = heading + cam_yaws[v]
eye = torch.tensor([x_ego + cam_lat[v] * math.cos(heading), y_ego, z_ego])
tgt = eye + torch.tensor([math.sin(yaw), math.cos(yaw), -0.05]) * 10.0
cam2world[f, v] = look_at_pose(eye, tgt)
return ProceduralScene(
static=static, dyn_canonical=dyn_canonical, box_centers=box_centers, box_rots=box_rots,
box_size=box_size, canon_idx=canon_idx, ground=ground, lanes=lanes, boundaries=boundaries,
K=K, cam2world=cam2world, F=F, V=V, fps=fps,
)