# nuScenes数据格式与实车部署标注指南 ## 📋 目录 1. [nuScenes GT数据格式详解](#1-nuscenes-gt数据格式详解) 2. [BEVFusion输入输出格式](#2-bevfusion输入输出格式) 3. [实车相机配置变动方案](#3-实车相机配置变动方案) 4. [数据采集与标注流程](#4-数据采集与标注流程) 5. [工具与代码示例](#5-工具与代码示例) --- ## 1. nuScenes GT数据格式详解 ### 1.1 数据组织结构 nuScenes数据集按照以下层级组织: ``` nuscenes/ ├── v1.0-trainval/ # 元数据(JSON格式) │ ├── sample.json # 关键帧样本 │ ├── sample_data.json # 传感器数据 │ ├── scene.json # 场景信息 │ ├── calibrated_sensor.json # 传感器标定 │ ├── ego_pose.json # 车辆位姿 │ ├── instance.json # 目标实例 │ ├── category.json # 目标类别 │ └── attribute.json # 目标属性 ├── samples/ # 关键帧数据 │ ├── CAM_FRONT/ │ ├── CAM_FRONT_LEFT/ │ ├── CAM_FRONT_RIGHT/ │ ├── CAM_BACK/ │ ├── CAM_BACK_LEFT/ │ ├── CAM_BACK_RIGHT/ │ ├── LIDAR_TOP/ │ └── RADAR_FRONT/ ├── sweeps/ # 中间帧数据 └── maps/ # HD地图数据 ├── expansion/ └── basemap/ ``` ### 1.2 单个Sample的数据结构 BEVFusion使用的处理后的info格式 (pkl文件): ```python { 'token': 'ca9a282c9e77460f8360f564131a8af5', # 样本唯一ID 'timestamp': 1532402927647951, # 时间戳(微秒) 'scene_token': 'scene-0001', # 场景ID # ========== LiDAR数据 ========== 'lidar_path': 'samples/LIDAR_TOP/n015-2018-07-24-11-22-45+0800__LIDAR_TOP__1532402927647951.pcd.bin', 'sweeps': [ # 多帧LiDAR数据 { 'data_path': 'sweeps/LIDAR_TOP/...', 'sensor2lidar_rotation': [[...], [...], [...]], # 3x3旋转矩阵 'sensor2lidar_translation': [x, y, z], 'timestamp': 1532402927547951 }, # ... 共9帧sweep ], # ========== LiDAR位姿 ========== 'lidar2ego_translation': [x, y, z], # LiDAR到车体坐标系 'lidar2ego_rotation': [w, x, y, z], # 四元数 'ego2global_translation': [x, y, z], # 车体到全局坐标系 'ego2global_rotation': [w, x, y, z], # ========== 相机数据 (6个相机) ========== 'cams': { 'CAM_FRONT': { 'data_path': 'samples/CAM_FRONT/...', 'cam_intrinsic': [ # 3x3内参矩阵 [fx, 0, cx], [0, fy, cy], [0, 0, 1] ], 'sensor2ego_translation': [x, y, z], # 相机到车体 'sensor2ego_rotation': [w, x, y, z], 'sensor2lidar_rotation': [[...], [...], [...]], # 相机到LiDAR 'sensor2lidar_translation': [x, y, z], 'timestamp': 1532402927612460 }, 'CAM_FRONT_RIGHT': { ... }, 'CAM_FRONT_LEFT': { ... }, 'CAM_BACK': { ... }, 'CAM_BACK_LEFT': { ... }, 'CAM_BACK_RIGHT': { ... } }, # ========== 3D目标检测标注 ========== 'gt_boxes': [ # N x 9数组 # [x, y, z, w, l, h, yaw, vx, vy] # x,y,z: 中心点(全局坐标) # w,l,h: 宽度,长度,高度(米) # yaw: 偏航角(弧度) # vx,vy: 速度(m/s) ], 'gt_names': ['car', 'pedestrian', ...], # 类别名称 'gt_velocity': [[vx, vy], ...], # 速度(m/s) 'num_lidar_pts': [34, 12, ...], # 每个box内的点数 'num_radar_pts': [5, 2, ...], # 雷达点数 'valid_flag': [True, True, ...], # 是否有效 # ========== 目标类别(10类) ========== # car, truck, construction_vehicle, bus, trailer, # barrier, motorcycle, bicycle, pedestrian, traffic_cone } ``` ### 1.3 BEV语义分割标注 BEV分割标注从nuScenes地图数据动态生成: ```python # 在LoadBEVSegmentation中实时生成 gt_masks_bev: np.ndarray # (num_classes, H, W) # H=200, W=200 (对应100m x 100m, 分辨率0.5m) # num_classes=6: # [0] drivable_area - 可行驶区域 # [1] ped_crossing - 人行横道 # [2] walkway - 人行道 # [3] stop_line - 停止线 # [4] carpark_area - 停车区 # [5] divider - 分隔线 # 值: 0/1 (二值mask) ``` **生成过程**: 1. 从nuScenes Map API获取当前位置的地图数据 2. 根据LiDAR位姿确定BEV范围 (默认50m x 50m) 3. 渲染6个语义类别到200x200网格 4. 进行坐标变换和旋转对齐 --- ## 5. 工具与代码示例 ### 5.1 数据转换工具集 #### 5.1.1 自定义数据转nuScenes格式 ```python #!/usr/bin/env python3 # tools/data_converter/custom_to_nuscenes.py import os import pickle import numpy as np from tqdm import tqdm from pyquaternion import Quaternion from pathlib import Path import json class CustomDataConverter: """将实车采集数据转换为nuScenes格式""" def __init__( self, custom_data_root: str, output_root: str, camera_names: list = None, ): self.custom_root = Path(custom_data_root) self.output_root = Path(output_root) # 默认相机配置 self.camera_names = camera_names or [ 'CAM_FRONT', 'CAM_FRONT_LEFT', 'CAM_FRONT_RIGHT', 'CAM_BACK' ] # 加载标定数据 self.calib = self.load_calibration() def load_calibration(self): """加载相机和LiDAR标定参数""" calib_file = self.custom_root / 'calibration' / 'sensors.json' with open(calib_file, 'r') as f: calib = json.load(f) return calib def convert_sample(self, sample_id: str): """转换单个样本""" # 1. 加载LiDAR数据 lidar_path = self.custom_root / 'lidar' / f'{sample_id}.pcd' # 2. 加载相机图像 cam_data = {} for cam_name in self.camera_names: img_path = self.custom_root / 'camera' / cam_name / f'{sample_id}.jpg' # 获取该相机的标定参数 cam_calib = self.calib['cameras'][cam_name] # 计算变换矩阵 cam2ego = self._build_transform_matrix( cam_calib['translation'], cam_calib['rotation'] ) ego2lidar_inv = self._build_transform_matrix( self.calib['lidar']['translation'], self.calib['lidar']['rotation'] ) ego2lidar = np.linalg.inv(ego2lidar_inv) cam2lidar = ego2lidar @ cam2ego cam_data[cam_name] = { 'data_path': str(img_path.relative_to(self.output_root)), 'cam_intrinsic': np.array(cam_calib['intrinsic']), 'sensor2ego_translation': cam_calib['translation'], 'sensor2ego_rotation': cam_calib['rotation'], 'sensor2lidar_rotation': cam2lidar[:3, :3], 'sensor2lidar_translation': cam2lidar[:3, 3], 'timestamp': self._get_timestamp(sample_id) } # 3. 加载标注 (如果存在) anno_file = self.custom_root / 'annotations' / f'{sample_id}.json' if anno_file.exists(): with open(anno_file, 'r') as f: annos = json.load(f) gt_boxes, gt_names, gt_velocity = self._parse_annotations(annos) else: gt_boxes = np.zeros((0, 7)) gt_names = [] gt_velocity = np.zeros((0, 2)) # 4. 构建info字典 info = { 'token': sample_id, 'timestamp': self._get_timestamp(sample_id), 'lidar_path': str(lidar_path.relative_to(self.output_root)), 'sweeps': [], # 如果有多帧sweep,在这里添加 'cams': cam_data, # LiDAR位姿 'lidar2ego_translation': self.calib['lidar']['translation'], 'lidar2ego_rotation': self.calib['lidar']['rotation'], 'ego2global_translation': [0, 0, 0], # 如果有GPS数据 'ego2global_rotation': [1, 0, 0, 0], # 标注 'gt_boxes': gt_boxes, 'gt_names': gt_names, 'gt_velocity': gt_velocity, 'num_lidar_pts': np.array([0] * len(gt_boxes)), # 需要统计 'valid_flag': np.array([True] * len(gt_boxes)), } return info def _build_transform_matrix(self, translation, rotation): """构建4x4变换矩阵""" T = np.eye(4) T[:3, :3] = Quaternion(rotation).rotation_matrix T[:3, 3] = translation return T def _parse_annotations(self, annos): """解析标注数据""" gt_boxes = [] gt_names = [] gt_velocity = [] for anno in annos['annotations']: # nuScenes格式: [x, y, z, w, l, h, yaw] box = [ anno['translation'][0], anno['translation'][1], anno['translation'][2], anno['size'][0], # width anno['size'][1], # length anno['size'][2], # height self._quaternion_to_yaw(anno['rotation']) ] gt_boxes.append(box) gt_names.append(anno['category_name']) gt_velocity.append(anno.get('velocity', [0, 0])) return ( np.array(gt_boxes), np.array(gt_names), np.array(gt_velocity) ) def _quaternion_to_yaw(self, q): """四元数转偏航角""" return Quaternion(q).yaw_pitch_roll[0] def _get_timestamp(self, sample_id): """获取时间戳 (微秒)""" # 从文件名或元数据中提取 # 示例: sample_id = "2024_01_15_10_30_25_123456" # 返回Unix时间戳(微秒) return int(sample_id.split('_')[-1]) def convert_all(self, split='train'): """转换所有数据""" # 获取样本列表 sample_list_file = self.custom_root / f'{split}_samples.txt' with open(sample_list_file, 'r') as f: sample_ids = [line.strip() for line in f] infos = [] for sample_id in tqdm(sample_ids, desc=f"Converting {split} set"): info = self.convert_sample(sample_id) infos.append(info) # 保存为pkl output_file = self.output_root / f'custom_infos_{split}.pkl' data = { 'infos': infos, 'metadata': { 'version': 'custom-v1.0', 'num_cameras': len(self.camera_names), 'camera_names': self.camera_names } } with open(output_file, 'wb') as f: pickle.dump(data, f) print(f"✅ 已转换 {len(infos)} 个样本到 {output_file}") return output_file # ========== 使用示例 ========== if __name__ == '__main__': converter = CustomDataConverter( custom_data_root='/data/custom_vehicle_data', output_root='/data/custom_nuscenes_format', camera_names=['CAM_FRONT', 'CAM_LEFT', 'CAM_RIGHT', 'CAM_BACK'] ) # 转换训练集和验证集 converter.convert_all(split='train') converter.convert_all(split='val') ``` #### 5.1.2 标定验证工具 ```python #!/usr/bin/env python3 # tools/verify_calibration.py import numpy as np import cv2 import open3d as o3d from pathlib import Path def verify_camera_lidar_calibration( lidar_file: str, image_file: str, calib_file: str, output_file: str = None ): """ 验证相机-LiDAR外参标定精度 将LiDAR点投影到图像上,目视检查对齐情况 """ # 1. 加载点云 pcd = o3d.io.read_point_cloud(lidar_file) points = np.asarray(pcd.points) # (N, 3) # 2. 加载图像 img = cv2.imread(image_file) h, w = img.shape[:2] # 3. 加载标定参数 import json with open(calib_file, 'r') as f: calib = json.load(f) intrinsic = np.array(calib['intrinsic']) # (3, 3) lidar2cam = np.array(calib['lidar2cam']) # (4, 4) # 4. 投影到相机坐标系 points_homo = np.hstack([points, np.ones((len(points), 1))]) # (N, 4) points_cam = points_homo @ lidar2cam.T # (N, 4) points_cam = points_cam[:, :3] # (N, 3) # 5. 过滤相机后方的点 mask = points_cam[:, 2] > 0 points_cam = points_cam[mask] # 6. 投影到图像平面 points_img = points_cam @ intrinsic.T # (N, 3) points_img = points_img[:, :2] / points_img[:, 2:] # (N, 2) # 7. 过滤图像外的点 mask = ( (points_img[:, 0] >= 0) & (points_img[:, 0] < w) & (points_img[:, 1] >= 0) & (points_img[:, 1] < h) ) points_img = points_img[mask] depths = points_cam[mask, 2] # 8. 可视化 img_with_points = img.copy() # 根据深度着色 depths_norm = (depths - depths.min()) / (depths.max() - depths.min()) colors = cv2.applyColorMap( (depths_norm * 255).astype(np.uint8), cv2.COLORMAP_JET ) for i, (u, v) in enumerate(points_img.astype(int)): color = tuple(map(int, colors[i][0])) cv2.circle(img_with_points, (u, v), 2, color, -1) # 9. 保存结果 if output_file: cv2.imwrite(output_file, img_with_points) print(f"✅ 验证结果保存至: {output_file}") # 10. 计算重投影统计 print(f"\n标定验证统计:") print(f" 投影点数: {len(points_img)}") print(f" 深度范围: {depths.min():.2f}m - {depths.max():.2f}m") print(f" 图像尺寸: {w}x{h}") return img_with_points # ========== 批量验证 ========== def batch_verify_calibration(data_root: str, num_samples: int = 10): """批量验证多个样本的标定""" data_path = Path(data_root) calib_file = data_path / 'calibration' / 'CAM_FRONT.json' # 随机选择样本 import random sample_ids = list((data_path / 'lidar').glob('*.pcd')) sample_ids = random.sample(sample_ids, min(num_samples, len(sample_ids))) output_dir = data_path / 'calibration_verification' output_dir.mkdir(exist_ok=True) for lidar_file in sample_ids: sample_id = lidar_file.stem image_file = data_path / 'camera' / 'CAM_FRONT' / f'{sample_id}.jpg' output_file = output_dir / f'{sample_id}_verification.jpg' verify_camera_lidar_calibration( str(lidar_file), str(image_file), str(calib_file), str(output_file) ) print(f"\n✅ 已验证 {num_samples} 个样本") print(f" 结果保存在: {output_dir}") if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument('--data_root', required=True) parser.add_argument('--num_samples', type=int, default=10) args = parser.parse_args() batch_verify_calibration(args.data_root, args.num_samples) ``` #### 5.1.3 BEV分割标注工具 ```python #!/usr/bin/env python3 # tools/annotate_bev_segmentation.py import numpy as np import cv2 from pathlib import Path import json class BEVSegmentationAnnotator: """BEV语义分割标注工具""" def __init__( self, xbound=(-50, 50, 0.5), ybound=(-50, 50, 0.5), classes=None ): self.xbound = xbound self.ybound = ybound self.classes = classes or [ 'drivable_area', 'ped_crossing', 'walkway', 'stop_line', 'carpark_area', 'divider' ] # 计算BEV网格大小 self.bev_h = int((ybound[1] - ybound[0]) / ybound[2]) # 200 self.bev_w = int((xbound[1] - xbound[0]) / xbound[2]) # 200 def render_bev_from_lidar(self, lidar_file: str, output_file: str): """从LiDAR点云渲染BEV俯视图""" import open3d as o3d # 加载点云 pcd = o3d.io.read_point_cloud(lidar_file) points = np.asarray(pcd.points) # 创建BEV图像 bev_img = np.zeros((self.bev_h, self.bev_w, 3), dtype=np.uint8) # 将点投影到BEV网格 for point in points: x, y, z = point # 过滤范围外的点 if not (self.xbound[0] <= x < self.xbound[1] and self.ybound[0] <= y < self.ybound[1]): continue # 转换为网格坐标 i = int((y - self.ybound[0]) / self.ybound[2]) j = int((x - self.xbound[0]) / self.xbound[2]) # 根据高度着色 height_norm = np.clip((z + 2) / 4, 0, 1) # -2m到2m color = int(height_norm * 255) bev_img[i, j] = [color, color, color] # 翻转Y轴 (图像坐标系) bev_img = np.flip(bev_img, axis=0).copy() # 保存 cv2.imwrite(output_file, bev_img) print(f"✅ BEV图像保存至: {output_file}") return bev_img def polygon_to_mask( self, polygon_json: str, class_name: str, output_file: str = None ): """将labelme标注的多边形转换为mask""" # 加载labelme的JSON with open(polygon_json, 'r') as f: data = json.load(f) # 创建mask mask = np.zeros((self.bev_h, self.bev_w), dtype=np.uint8) # 绘制多边形 for shape in data['shapes']: if shape['label'] == class_name: points = np.array(shape['points'], dtype=np.int32) cv2.fillPoly(mask, [points], 1) if output_file: np.save(output_file, mask) print(f"✅ Mask保存至: {output_file}") return mask def merge_all_classes(self, polygon_dir: str, output_file: str): """合并所有类别的mask""" polygon_dir = Path(polygon_dir) masks = np.zeros((len(self.classes), self.bev_h, self.bev_w), dtype=np.uint8) for i, class_name in enumerate(self.classes): json_file = polygon_dir / f'{class_name}.json' if json_file.exists(): mask = self.polygon_to_mask(str(json_file), class_name) masks[i] = mask else: print(f"⚠️ 未找到类别 {class_name} 的标注") # 保存 np.save(output_file, masks) print(f"✅ 完整BEV mask保存至: {output_file}") print(f" 形状: {masks.shape}") return masks # ========== 使用流程 ========== if __name__ == '__main__': annotator = BEVSegmentationAnnotator() # 步骤1: 渲染BEV图像 bev_img = annotator.render_bev_from_lidar( 'data/lidar/sample_001.pcd', 'data/bev_images/sample_001.png' ) # 步骤2: 使用labelme标注多边形 # labelme data/bev_images/sample_001.png # 为每个类别绘制多边形,保存为JSON # 步骤3: 转换为mask masks = annotator.merge_all_classes( polygon_dir='data/bev_annotations/sample_001', output_file='data/bev_masks/sample_001.npy' ) print("✅ BEV分割标注完成!") ``` --- ## 6. 实车部署完整流程 ### 6.1 数据采集 ```bash # 1. 相机数据采集 roslaunch vehicle_perception camera_record.launch \ cameras:="front,left,right,back" \ fps:=10 \ output:=/data/raw/camera # 2. LiDAR数据采集 roslaunch vehicle_perception lidar_record.launch \ topic:=/velodyne_points \ output:=/data/raw/lidar # 3. 同步检查 python tools/check_sync.py \ --camera_dir /data/raw/camera \ --lidar_dir /data/raw/lidar \ --max_time_diff 0.05 # 50ms ``` ### 6.2 标定 ```bash # 相机内参标定 python tools/calibrate_camera.py \ --images /data/calibration/checkerboard \ --pattern 9x6 \ --square_size 0.05 # 5cm # LiDAR-相机外参标定 python tools/calibrate_lidar_camera.py \ --lidar /data/calibration/lidar.pcd \ --image /data/calibration/cam_front.jpg \ --target_type checkerboard \ --output calib/lidar2cam.json # 验证标定 python tools/verify_calibration.py \ --data_root /data/raw \ --num_samples 10 ``` ### 6.3 数据转换 ```bash # 转换为nuScenes格式 python tools/data_converter/custom_to_nuscenes.py \ --custom_root /data/raw \ --output /data/nuscenes_format \ --camera_names CAM_FRONT CAM_LEFT CAM_RIGHT CAM_BACK # 验证转换结果 python tools/check_data_integrity.py \ --data_root /data/nuscenes_format \ --check_images \ --check_lidar \ --check_annotations ``` ### 6.4 标注 ```bash # 启动CVAT标注服务 cd cvat docker-compose up -d # 导入数据 python tools/import_to_cvat.py \ --data_root /data/nuscenes_format \ --task_name "vehicle_annotation_batch_001" # 标注完成后导出 python tools/export_from_cvat.py \ --task_id 123 \ --output /data/annotations/batch_001.json # 转换为训练格式 python tools/cvat_to_nuscenes_anno.py \ --cvat_json /data/annotations/batch_001.json \ --output /data/nuscenes_format/annotations ``` ### 6.5 训练配置调整 ```yaml # configs/custom_vehicle/custom_4cam.yaml # 继承基础配置 _base_: - ../nuscenes/det/transfusion/secfpn/camera+lidar/swint_v0p075/multitask_enhanced_phase1_HIGHRES.yaml # 修改相机数量 model: encoders: camera: backbone: # 使用预训练权重 init_cfg: checkpoint: runs/enhanced_from_epoch19/epoch_10.pth vtransform: # 4相机配置 num_cameras: 4 # 数据配置 data: train: type: NuScenesDataset dataset_root: /data/nuscenes_format ann_file: /data/nuscenes_format/custom_infos_train.pkl pipeline: ${train_pipeline} val: type: NuScenesDataset dataset_root: /data/nuscenes_format ann_file: /data/nuscenes_format/custom_infos_val.pkl pipeline: ${val_pipeline} # 训练参数 (迁移学习) train: max_epochs: 20 # 微调20个epoch lr: warmup_iters: 500 max_lr: 0.0001 # 降低学习率 ``` ### 6.6 微调训练 ```bash # 启动训练 torchpack dist-run -np 6 python tools/train.py \ configs/custom_vehicle/custom_4cam.yaml \ --model.encoders.camera.backbone.init_cfg.checkpoint=runs/enhanced_from_epoch19/epoch_10.pth \ --run-dir runs/custom_vehicle_finetune # 监控训练 tensorboard --logdir runs/custom_vehicle_finetune ``` --- ## 7. 常见问题与解决方案 ### Q1: 相机数量不同如何处理? ```python # 方案1: 修改模型输入层 # mmdet3d/models/encoders/camera_encoder.py class CameraEncoder: def __init__(self, num_cameras=6, ...): # 改为4 self.num_cameras = num_cameras # 模型自动适配 # 方案2: 使用虚拟相机填充 # 如果模型期望6相机,实际只有4相机 # 可以复制前相机或使用黑图填充 ``` ### Q2: 分辨率不同如何处理? ```yaml # 配置中调整 model: encoders: camera: vtransform: image_size: [384, 1024] # 从[256, 704]改为实际分辨率 feature_size: [48, 128] # 相应调整 ``` ### Q3: FOV差异如何处理? ```python # 需要重新标定,获取准确内参 # 如果FOV差异>30度,建议重新训练 # 如果<30度,可以通过fine-tuning适配 ``` ### Q4: 没有BEV分割标注怎么办? ```python # 方案1: 仅训练3D检测,关闭分割 model: heads: object: ... # 保留 map: null # 关闭 # 方案2: 使用预训练的分割头冻结 model: heads: map: freeze: true # 冻结分割头权重 ``` --- ## 8. 性能优化建议 ### 8.1 数据增强 ```yaml # 针对实车场景调整增强策略 augment2d: resize: [[0.4, 0.6], [0.5, 0.5]] # 更大的resize范围 rotate: [-10, 10] # 更大的旋转 brightness: 0.2 # 亮度增强 contrast: 0.2 # 对比度增强 ``` ### 8.2 困难样本挖掘 ```python # 工具: 识别模型表现差的场景 python tools/find_hard_samples.py \ --checkpoint runs/custom_vehicle_finetune/epoch_10.pth \ --data_root /data/nuscenes_format \ --output hard_samples.txt # 针对性增加这些场景的标注 ``` ### 8.3 评估指标 ```bash # 在实车数据上评估 python tools/test.py \ configs/custom_vehicle/custom_4cam.yaml \ runs/custom_vehicle_finetune/epoch_10.pth \ --eval bbox segm # 分析性能 python tools/analyze_results.py \ --results results.pkl \ --by_scene \ --by_distance \ --by_weather ``` --- ## 9. 总结 ### 关键步骤检查清单 ``` □ 数据采集 □ 相机数据完整 (4-6路) □ LiDAR数据完整 □ 时间戳同步 (<50ms) □ 场景覆盖充分 □ 传感器标定 □ 相机内参精度 (重投影误差<1px) □ LiDAR-相机外参精度 (旋转<0.5°, 平移<2cm) □ 标定验证通过 □ 数据转换 □ 转换为nuScenes格式 □ 数据完整性检查 □ 可视化验证 □ 标注 □ 3D box标注 (IoU>0.7) □ BEV分割标注 (可选) □ 质量审核通过 □ 数据集规模 (>5000帧) □ 训练 □ 配置文件调整 □ 迁移学习/微调 □ 性能验证 □ 实车测试 ``` ### 最小可行部署 ``` 最小数据集: 5000帧 最小标注: 仅3D检测 训练时间: 2-3天 (6xV100) 性能预期: NDS 0.55-0.65 ``` ### 完整部署 ``` 推荐数据集: 10000-15000帧 完整标注: 3D检测 + BEV分割 训练时间: 5-7天 性能预期: NDS 0.65-0.75 ``` --- **文档版本**: v1.0 **最后更新**: 2025-10-25 **适用于**: BEVFusion + 自定义传感器配置