借助 NVIDIA Omniverse NuRec 将自动驾驶感知扩展至不同车型平台
感知栈的表现,取决于搭载它的车辆。同样一套软件,换到另一个车型上——比如从 SUV 换到轿车,或同系列中的另一个变体——它对世界的感知就会发生变化。传感器位置、标定参数、视场范围、遮挡关系、车身几何、时序同步以及覆盖区域都会随之改变。红绿灯可能出现在画面中不同的位置,路沿可能变得难以识别,覆盖边缘的行人可能变得模糊难辨。随着自动驾驶栈向新车型和变体扩展,即便底层感知栈不变,开发者也必须应对这些差异。
为每个车型重新采集并标注真实数据集成本高昂,在车辆开发早期甚至难以实现。新车队未必就绪,罕见场景也不一定能捕捉到。真实路采数据对于系统性能的验证和基准对齐仍然不可或缺,但合成数据可以帮助团队在目标车型的完整数据集尚未就绪前,先完成模型适配。
车型适配的挑战在于:让感知软件为一个尚不存在的车辆做好准备,并将这套软件扩展到各个平台变体上,而无需为每个变体单独采集和标注大量数据。
NVIDIA Omniverse NuRec 让这一过程变得更切实可行。它从已记录的真实路采数据出发,对每个场景进行三维重建,再为目标车辆配置渲染新的相机视角。
开发者可以借助真实数据来回答以下问题:
- 这个场景从目标传感器布局看会是什么样子?
- 哪些已有路采数据对新车型覆盖较好?
- 几何结构的变化在哪里产生了薄弱点?
- 哪些缺口需要通过真实采集来补齐?
本文将演示如何通过以下四步,利用已有路采数据将感知栈适配到新的传感器布局:
- 将重建后的路采场景与目标传感器配置配对。
- 渲染目标视角。
- 使用 NVIDIA Harmonizer 对画面进行精修。
- 基于输出结果训练感知模型。
本教程使用 NVIDIA Physical AI NuRec 数据集中的重建场景来完成上述四个步骤。
NuRec 如何支持车型适配
NuRec 使用 3D Gaussian 泼溅(Gaussian splatting)技术从传感器数据中重建真实世界环境,并将其渲染出来用于仿真。在车型适配场景下,NuRec 会先重建用现有车辆采集的真实行驶数据,再以目标车辆的视角渲染出新的摄像头视频流。
NuRec 有两项能力对车型适配特别有帮助。
- 新视角合成:NuRec 可以通过 gsplat 渲染场景,把高斯分布按指定相机模型进行投影。该能力支持修改相机外参、内参、视场角以及镜头模型,涵盖针孔、鱼眼和 f-theta 等配置。
- 可复用的场景数据与标注:重建后的场景保留了原始采集轨迹、各相机标定参数、动态物体跟踪信息以及地图数据。这些资源可用于在新渲染的视角中对目标、车道、信号灯和道路边界等标签进行对齐或适配。
借助 NuRec 将感知模型适配到新车型
下面的工作流展示了开发团队如何通过目标摄像头模组渲染已有的重建行驶数据,对生成的图像帧进行精调,并准备好用于感知模型训练的数据。
步骤一:下载重建场景
Hugging Face 上的 Physical AI NuRec Dataset 收录了超过 1,500 个神经重建的驾驶场景。每个场景时长约 20 秒,基于六个摄像头视角重建而成:120 度前广角、30 度前长焦、120 度左右交叉视角,以及 70 度左右后视角。
NuRec Dataset 受访问限制,需先接受许可,用 Hugging Face token 完成认证,然后即可下载单个场景。
import os
from huggingface_hub import login, snapshot_download
login(token=os.getenv("HF_TOKEN"))
# 从 26.04 版本下载一个重建场景(USDZ)
snapshot_download(
repo_id="nvidia/PhysicalAI-Autonomous-Vehicles-NuRec",
repo_type="dataset",
allow_patterns="sample_set/26.04_release/<scene-uuid>/*",
)
如果使用编码代理,可克隆 NVIDIA/nurec-skills 仓库,其中包含下载 Physical AI 数据集、使用 NuRec 渲染以及优化输出帧的相关技能。
git clone https://github.com/NVIDIA/nurec-skills.git
步骤 2:通过目标车型的传感器配置进行渲染
本教程使用随公开示例提供的合成目标摄像头配置,其摄像头名称、位姿、分辨率及镜头参数仅作演示。
所提供的合成配置仅包含一个目标摄像头,因此本教程会生成一路目标摄像头视频流。在配置文件中,每个传感器条目以冒号分隔,依次定义:摄像头名称、图像分辨率、F-Theta 镜头模型、内参以及相机到载具的位姿。
NRE 会将冒号分隔的名称转换为逻辑相机 ID。例如,把 camera:front:synthetic:120fov 转换为 camera_front_synthetic_120fov。若要添加另一个目标相机,只需在 rig.sensors 中再增加一条记录,赋予其唯一名称,并指定自身的 width、height、lens model、intrinsic 参数以及 nominalSensor2Rig_FLU 变换。
设置输入和输出位置:
export SCENE_DIR=/absolute/path/to/scene export SCENE_FILE=<SCENE_UUID>.usdz export RIG_DIR=/absolute/path/to/rig export RIG_FILE=minimal-synthetic-target-rig.json export OUTPUT_DIR=/absolute/path/to/output mkdir -p "$OUTPUT_DIR"
将公共 NuRec 容器固定到其不可变的摘要值,然后拉取镜像:
export NUREC_IMAGE='nvcr.io/nvidia/nre/nre-ga:26.04.01@sha256:97f43e7130c5636ce3e80ea3184d97f56a87fdd989b05cce42230881dbdea284'
docker pull "$NUREC_IMAGE"
docker image inspect "$NUREC_IMAGE" --format '{{range .RepoDigests}}{{println .}}{{end}}'
将轨迹导出和渲染作为独立的容器操作来执行。首先导出目标相机轨迹:
docker run --rm --gpus all --shm-size=64g \
-v "$SCENE_DIR":/inputs/scene:ro \
-v "$RIG_DIR":/inputs/rig:ro \
-v "$OUTPUT_DIR":/outputs \
"$NUREC_IMAGE" export-custom-rig-trajectory \
--artifact-path="/inputs/scene/$SCENE_FILE" \
--rig-json="/inputs/rig/$RIG_FILE" \
--output=/outputs/custom_rig_trajectories.json
接下来,对其中一个目标相机执行一次稀疏渲染以验证配置是否正确。在本例中,选择目标相机 camera_front_synthetic_120fov,因为示例目标 rig 定义了一台朝前的相机。渲染命令会加载导出的轨迹文件,然后通过 --camera-id 选择要渲染的相机。
该 USDZ 场景由六台源相机重建得到:120 度前广角相机、30 度前长焦相机、120 度交叉左右相机,以及 70 度后左和后右相机。目标相机无需与任何一台源相机一一对应,只要位姿使用同一个 rig 坐标系表示,并且其镜头模型受导出器支持,就可以采用不同的位置、朝向、分辨率、视场角或标定参数。放置在远离观测轨迹之外、或朝向源相机覆盖较少区域的相机,渲染质量可能会下降。
本教程使用前视目标摄像头。如需适配其他视角(例如左后摄像头),请先在目标 rig 的 JSON 中添加一个左后传感器,并为其配置左后摄像头到 rig 的外参,然后重新导出轨迹,最后将其归一化的逻辑 ID 传入 --camera-id。
docker run --rm --gpus all --shm-size=64g \
-v "$SCENE_DIR":/inputs/scene:ro \
-v "$OUTPUT_DIR":/outputs \
"$NUREC_IMAGE" render \
--artifact-path="/inputs/scene/$SCENE_FILE" \
--output-dir=/outputs/smoke \
--custom-rig-trajectory=/outputs/custom_rig_trajectories.json \
--no-replicate-training-views \
--renderer=default \
--image-format=png \
--frame-step=30 \
--image-scale=1 \
--frame-naming=frame-end-timestamp \
--camera-id=camera_front_synthetic_120fov
检查渲染结果,确认位姿、视场角和时间戳都正确。稀疏渲染验证通过后,将 --frame-step 设为 1,重新渲染目标摄像头的完整序列。针对目标 rig 中每个已校验的摄像头,重复使用相应的 --camera-id。
第 3 步:优化渲染帧
神经渲染可能会留下与视角相关的伪影、色彩或色调不一致,以及动态物体重建不完整的问题。NVIDIA Harmonizer 是一个公开的、具备时序感知能力的后处理模型,可修正 NuRec 及同类神经渲染中的伪影。它能提升画面质量,但无法修复错误的标定,也无法恢复原本就未被重建的场景覆盖区域,更不能替代对目标摄像头的校验流程。
NVIDIA/nurec-skills 中的 nurec-fixer 技能涵盖了环境搭建、推理、评估以及可选的 Harmonizer 微调。下载模型前,请先校验主机环境。
git clone https://github.com/NVIDIA/nurec-skills.git python nurec-skills/skills/nurec-fixer/scripts/validate_setup.py
在 Hugging Face 上接受模型许可,克隆 Harmonizer 仓库,构建其运行时镜像,并下载发布的 checkpoint:
git clone https://github.com/NVIDIA/harmonizer.git cd harmonizer docker build -t harmonizer-cosmos-env -f Dockerfile.cosmos . hf auth login ./download_checkpoints.sh test -f models/diffusion_harmonizer.pkl test -d src/checkpoints/nvidia/Cosmos-Predict2-0.6B-Text2Image
一次只运行一条相机序列。挂载父级渲染目录,让输出(写在输入目录旁边)保留在宿主机上:
export HARMONIZER_DIR=/absolute/path/to/harmonizer export RENDER_ROOT=/absolute/path/to/output/render export CAMERA_ID=camera_front_synthetic_120fov docker run --rm --gpus all --ipc=host \ --entrypoint python \ -v "$HARMONIZER_DIR":/work \ -v "$RENDER_ROOT":/frames \ -w /work/src \ harmonizer-cosmos-env \ inference_pix2pix_turbo_harmonizer.py \ --input_image="/frames/$CAMERA_ID" \ --model_path=/work/models/diffusion_harmonizer.pkl \ --model_identifier=harmonized \ --timestep=250 \ --resolution=1024 \ --use_sched
步骤 4. 训练感知模型
为新的车型线生成 NuRec 渲染数据集后,先对输出进行验证,再将其纳入感知训练流程。把这个新数据集当作从车辆采集的真实相机数据同等对待,然后根据感知模型的需求进行预处理。
车型适配方案
NVIDIA 在一项内部自动驾驶项目中评估了该工作流,该项目需要支持一种新的相机配置。团队已有一批来自其他车辆的行车数据,但目标车型线的数据尚未就绪。
借助 NuRec 智能体技能自动化工作流
NVIDIA/nurec-skills 仓库将主要步骤封装为智能体技能。编程智能体可以使用 physical-ai-datasets 来定位和下载场景,使用 nre 进行渲染,使用 nurec-fixer 来优化输出。
如需端到端示例,可观看神经重建智能体技能直播。
公开的 NVIDIA/nurec-skills 仓库包含定位 Physical AI 数据集、使用 NCore、运行 NuRec 以及应用 DiffusionHarmonizer 的技能。nurec-carline-adaptation 技能增加了一个轻量的兼容性和来源追溯层;它不重新分发 NuRec 源码或模型。
git clone https://github.com/NVIDIA/nurec-skills.git
克隆仓库后,向兼容的编程智能体发出以下指令:
使用 $nurec-carline-adaptation 技能验证这份 USDZ 和脱敏后的目标 rig,给我具体的本地 Docker 命令,运行一次单相机的冒烟测试,如果任何相机或挡风玻璃检查失败就在完整渲染前停下。
将 NuRec 应用于已有行车数据
要重建一段采集到的行驶数据,需要来自录制设备的多路同步视频,以及同一段录制的对应元数据。原始数据需要按统一的目录结构组织:
clip/
└── raw/
├── generated/
│ ├── front_wide.mp4
│ ├── front_tele.mp4
│ ├── cross_left.mp4
│ ├── cross_right.mp4
│ ├── rear_left.mp4
│ ├── rear_right.mp4
│ └── rear.mp4
└── clipgt/parquets/
├── calibration_estimate.parquet
├── egomotion_estimate.parquet
└── object_fused.parquet
MP4 文件来自车载相机录像系统,Parquet 文件则包含整套相机共享的元数据。
| 输入 | 内容 | 典型来源 |
|---|---|---|
calibration_estimate.parquet | 相机镜头参数与安装位置 | 相机标定或采集设备配置导出 |
egomotion_estimate.parquet | 车辆随时间变化的位置和姿态 | 定位、里程计、VIO 或 SLAM |
object_fused.parquet | 运动目标、类别、尺寸及轨迹 ID | 感知或标注流水线 |
这三个 Parquet 文件都是本教程所配套的转换器和重建流程所必需的。其他 NuRec 工作流可能支持在没有目标轨迹的情况下进行重建。这些文件名和 Parquet 序列化方式并不是 NCore 的硬性要求。只要相同的信息以 JSON、数据库或其他格式存储,就可以改造公开的 NCore 转换器模板来读取这些数据源。
本工作流假定所选视频已经同步,且覆盖同一段录制区间。标定数据必须涵盖每个被选中的相机,自运动数据必须覆盖从首次曝光到末次曝光的整个序列,本流程所需的目标轨迹也必须与该时间区间重叠,并使用同一个世界坐标系。语义校验器会在转换完成后运行,检查生成的 NCore 序列中这些关系是否一致。
将原始数据映射到 NCore
下一节中的配套转换器支持上文展示的七路视频与三张表格的布局。当文件遵循这一结构时,工作流可直接进入运行配套转换器步骤。
不支持的源格式需要基于公开转换器模板进行定制,读取源文件并将数据写入 NCore。
export CONVERTER_DIR="$WORK_DIR/cosmos-clipgt-converter" cp -R "$NUREC_SKILLS_REPO/skills/ncore/ncore_template" "$CONVERTER_DIR"
该模板是一个代码骨架,并非通用转换器。其源数据读取模块必须将对应的信息映射到以下 NCore 组件:
| 源数据 | NCore V4 |
|---|---|
| 编码后的相机图像及每帧曝光区间 | 每个逻辑相机对应一个 CameraSensorComponent |
| 相机镜头模型及内参 | IntrinsicsComponent |
| 相机到载具的外参 | PosesComponent:T_sensor_rig |
| 度量自车运动 | PosesComponent:T_rig_world |
| 可选的全局参考 | PosesComponent:T_world_world_global |
| 自车掩码 | MasksComponent |
| 目标观测与轨迹 | CuboidsComponent |
请遵循 NCore 的公开坐标系约定:
- 载具:
+X朝前,+Y朝左,+Z朝上 - 相机:
+X朝右,+Y朝下,+Z朝前 - 时间戳:整数微秒
- 距离单位:米;位姿:有效的 SE(3) 变换
位姿计算请使用 float64。将局部 `world` 帧重新定位,使首个载具位姿为单位阵。对于已同步且帧率恒定、但未记录帧时间戳的视频,第 `i` 帧的标称时间戳为:
frame_timestamp_us = anchor_timestamp_us + i * 1,000,000 / fps
尽可能使用录制时记录的真实曝光时间戳。上述公式假设所有 MP4 流是同步的。转换器会按 NCore 的 `separate-sensors` 配置输出序列:该配置会为每个被转换的相机生成一份相机组件归档,并为位姿、内参、掩码和 3D 包围框生成一份共享归档。转换器会保留每个相机的实际分辨率和颜色解码方式。
运行配套转换器
如果随教程仓库一起提供了针对该数据源的转换器,按以下方式运行:
export COSMOS_CONVERTER="$TUTORIAL_REPO/tools/cosmos_ncore/build_cosmos_ncore_v4.py" test -f "$COSMOS_CONVERTER" env -u PYTHONPATH "$NCORE_PYTHON" "$COSMOS_CONVERTER" \ --root "$INPUT_ROOT" \ --anchor-us "$ANCHOR_TIMESTAMP_US" \ --sequence-id "$SEQUENCE_ID" \ --fps "$FPS" \ --store-type itar
校验 NCore 序列
根据转换器的输出约定设置生成路径:
export NCORE_DIR="$INPUT_ROOT/ncore/$SEQUENCE_ID" export NCORE_MANIFEST="$NCORE_DIR/$SEQUENCE_ID.json" export NCORE_VALIDATOR="$TUTORIAL_REPO/tools/cosmos_ncore/validate_cosmos_ncore_v4.py" test -s "$NCORE_MANIFEST" python3 -m json.tool "$NCORE_MANIFEST" > /dev/null
运行针对该数据源的校验器:
env -u PYTHONPATH "$NCORE_PYTHON" "$NCORE_VALIDATOR" \ --root "$INPUT_ROOT" \ --sequence-id "$SEQUENCE_ID"
然后使用公开的 NCore 查看器浏览该序列,并打开 http://localhost:8080。
export NCORE_REPO=/absolute/path/to/ncore
export NCORE_MANIFEST=/absolute/path/to/sequence/<sequence-id>.json
(
cd "$NCORE_REPO"
bazel run //tools/ncore_vis -- \
v4 \
--component-group="$NCORE_MANIFEST"
)
预期输出
一个 separate-sensors 模式的 NCore V4 序列通常包含:
<sequence-id>/ ├── <sequence-id>.json ├── build_manifest.json ├── <sequence-id>.ncore4.zarr.itar ├── <sequence-id>.ncore4-<camera-id>.zarr.itar └── ... 每个被转换的相机对应一份归档
共享的 `<sequence-id>.ncore4.zarr.itar` 归档包含位姿、内参、掩码和 3D 包围框等组件。每一份 `<sequence-id>.ncore4-<camera-id>.zarr.itar` 归档包含一个被转换相机的所有帧。JSON manifest 与其引用的所有归档共同构成一个逻辑序列,必须放在一起保存。
生成 NuRec 辅助数据
转换后的 NCore 序列包含录制的传感器数据、标定、位姿以及目标轨迹,但还不包含本纯相机重建流程所需的语义分割、深度和自车掩码等推理产物。公开的 skills/nre 文档说明了这一阶段的操作,NuRec 辅助数据文档定义了具体的数据产物。NGC 上的 nre-tools-ga NGC 容器提供了相应的推理应用。
该流程使用 NRE_AUX_IMAGE 来标识辅助数据容器,并将其与独立的 nre-ga 训练和渲染镜像区分开来。26.04 GA 镜像通过单一入口暴露多个工具,因此流程中需要显式指定 ncore-aux-data 子命令:
export NUREC_AUX_IMAGE=nvcr.io/nvidia/nre/nre-tools-ga:26.04.00 python3 "$NUREC_SKILLS_REPO/skills/nre/scripts/validate_setup.py" --strict docker pull "$NUREC_AUX_IMAGE" docker run --rm --gpus all --shm-size=2g \ --env NGC_API_KEY \ --volume "$NCORE_DIR:/workdir/dataset" \ "$NUREC_AUX_IMAGE" ncore-aux-data \ --dataset-path="/workdir/dataset/$SEQUENCE_ID.json" \ --output-dir=/workdir/dataset \ --segmentation-backend=mask2former \ --depth-backend=depthanythingv2 \ --max-depth-m=80 \ --ego-mask \ --no-lidar-seg-camvis \ --no-seg-logits \ --zarr-store-type=itar \ --store-meta \ --num-threads=auto
所选的每个选项都有明确的作用。表 3 汇总了各个选项生成的数据产物。
| 选项 | 生成的数据产物 |
|---|---|
--segmentation-backend=mask2former | <sequence-id>.aux.sseg.zarr.itar:逐帧的语义类别掩码 |
--depth-backend=depthanythingv2 | <sequence-id>.aux.depth.zarr.itar:度量深度 |
--ego-mask | <sequence-id>.aux.egomask.zarr.itar:逐相机的自车排除掩码 |
--store-meta | <sequence-id>.aux-meta.json:从固定版本镜像解析得到的辅助数据配置 |
--no-lidar-seg-camvis |
由于工作流中不包含 LiDAR 数据,因此禁用 LiDAR 分割 |
省略 --camera-id 会处理 NCORE_SEQUENCE_JSON 中声明的所有相机。若要选择特定相机,需要为每个相机添加一个 --camera-id=<logical-camera-id> 参数,其中逻辑相机 ID 取自清单文件。
辅助数据生成完成后,重建输入目录的结构如下:
<sequence-id>/ ├── <sequence-id>.json ├── build_manifest.json ├── <sequence-id>.ncore4.zarr.itar ├── <sequence-id>.ncore4-<camera-id>.zarr.itar └── ... 每个转换后的相机对应一份相机归档 ├── <sequence-id>.aux.sseg.zarr.itar ├── <sequence-id>.aux.depth.zarr.itar ├── <sequence-id>.aux.egomask.zarr.itar └── <sequence-id>.aux-meta.json
开始使用
使用以下资源,即可开始将重建场景适配到目标车型。
- 从 Physical AI NuRec Dataset 下载 USDZ 场景以及随附的示例目标车架。
- 通过示例车架渲染场景,然后将车架 JSON 替换为目标车型配置,再次渲染场景。
- 查阅 NuRec 文档,了解环境要求、支持硬件、验证及渲染说明。
- 如需重建已记录的路测数据,请参阅 重建 AV 场景,以及 NVIDIA/nurec-skills 中的 NCore 转换和辅助数据相关指引。
- 使用 NVIDIA Harmonizer 进行时序一致的序列后处理。
这些公开软件、容器、数据集和模型工件需要相应的 NVIDIA NGC 或 Hugging Face 账号,并接受其许可条款。NuRec 运行时可在 NGC 上的 nre-ga 容器 中获取。