nuScenes数据集实战:如何用Python快速提取关键帧点云数据(附避坑指南)
·
nuScenes数据集实战:Python高效提取关键帧点云数据的全流程指南
1. 环境准备与数据加载
在开始处理nuScenes数据集之前,我们需要确保开发环境配置正确。首先安装必要的Python包:
pip install nuscenes-devkit pandas numpy open3d pyquaternion
创建项目目录结构时,建议采用以下组织方式:
/nuscenes_project
├── /data
│ ├── /sets
│ │ └── /nuscenes # 官方数据集存放位置
├── /notebooks # Jupyter实验笔记
└── /scripts # 处理脚本
加载数据集的核心代码如下:
from nuscenes.nuscenes import NuScenes
# 初始化数据集接口
nusc = NuScenes(
version='v1.0-mini', # 或使用完整版'v1.0-trainval'
dataroot='/path/to/data/sets/nuscenes',
verbose=True
)
# 验证数据加载
print(f"数据集包含 {len(nusc.sample)} 个关键帧样本")
2. 关键帧点云数据结构解析
nuScenes采用多层数据结构组织传感器数据:
- Scene: 20秒的连续驾驶片段
- Sample: 0.5秒间隔的关键帧(含所有传感器数据)
- SampleData: 单个传感器的具体数据记录
激光雷达数据存储在LIDAR_TOP通道,每个关键帧对应一个.pcd.bin文件。通过以下代码可获取首个样本的点云元数据:
first_sample = nusc.sample[0]
lidar_token = first_sample['data']['LIDAR_TOP']
lidar_data = nusc.get('sample_data', lidar_token)
print(f"点云文件路径: {lidar_data['filename']}")
print(f"时间戳: {lidar_data['timestamp']}")
print(f"坐标系: {nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token'])}")
关键数据结构关系如下图所示:
| 层级 | 描述 | 关联关系 |
|---|---|---|
| Scene | 20秒场景 | 包含40个Sample |
| Sample | 关键帧 | 包含12个Sensor数据 |
| SampleData | 传感器数据 | 通过token关联具体文件 |
3. 点云数据提取核心技术
3.1 原始数据读取与解析
nuScenes使用二进制格式存储点云数据,每个点包含(x,y,z,intensity)四维信息。读取时需要特别注意坐标系转换:
import numpy as np
def load_point_cloud(nusc, sample_token, sensor='LIDAR_TOP'):
"""
加载指定样本的点云数据
返回:(N,4) numpy数组,包含x,y,z,intensity
"""
sample = nusc.get('sample', sample_token)
lidar_data = nusc.get('sample_data', sample['data'][sensor])
pcl_path = os.path.join(nusc.dataroot, lidar_data['filename'])
# 读取二进制文件
scan = np.fromfile(pcl_path, dtype=np.float32)
points = scan.reshape(-1, 4)
# 转换到车辆坐标系
cs_record = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token'])
points[:, :3] = np.dot(Quaternion(cs_record['rotation']).rotation_matrix,
points[:, :3].T).T + np.array(cs_record['translation'])
return points
3.2 批量提取优化技巧
处理大规模数据时,建议使用多进程加速:
from multiprocessing import Pool
def process_sample(args):
nusc, sample_token = args
return load_point_cloud(nusc, sample_token)
with Pool(8) as p: # 使用8个进程
point_clouds = p.map(process_sample, [(nusc, s['token']) for s in nusc.sample])
3.3 坐标系转换实战
nuScenes涉及三种主要坐标系:
- 传感器坐标系:原始数据坐标系
- 车辆坐标系:以车辆为中心的坐标系
- 全局坐标系:地图坐标系
转换示例代码:
from pyquaternion import Quaternion
def sensor_to_global(nusc, points, sensor_token):
"""
将点云从传感器坐标系转换到全局坐标系
"""
sd_record = nusc.get('sample_data', sensor_token)
cs_record = nusc.get('calibrated_sensor', sd_record['calibrated_sensor_token'])
pose_record = nusc.get('ego_pose', sd_record['ego_pose_token'])
# 传感器->车辆
points[:, :3] = np.dot(Quaternion(cs_record['rotation']).rotation_matrix,
points[:, :3].T).T + cs_record['translation']
# 车辆->全局
points[:, :3] = np.dot(Quaternion(pose_record['rotation']).rotation_matrix,
points[:, :3].T).T + pose_record['translation']
return points
4. 常见问题解决方案
4.1 路径配置问题
当数据集路径与默认结构不符时,需要修改nuscenes-devkit源码中的路径解析逻辑。找到安装包中的nuscenes.py,约第100行处修改:
# 原代码
if not os.path.exists(self.dataroot):
raise Exception("Dataroot does not exist: %s" % self.dataroot)
# 修改为自定义路径检查逻辑
custom_path = os.path.join(self.dataroot, 'custom_subdir')
if os.path.exists(custom_path):
self.dataroot = custom_path
elif not os.path.exists(self.dataroot):
raise Exception("Dataroot does not exist: %s" % self.dataroot)
4.2 内存优化策略
处理完整数据集时,可采用生成器模式逐帧处理:
def point_cloud_generator(nusc, sample_tokens):
for token in sample_tokens:
yield load_point_cloud(nusc, token)
# 使用示例
pc_gen = point_cloud_generator(nusc, [s['token'] for s in nusc.sample])
for pc in pc_gen:
process_point_cloud(pc) # 自定义处理函数
4.3 数据可视化技巧
使用Open3D进行交互式可视化:
import open3d as o3d
def visualize_point_cloud(points):
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points[:, :3])
# 强度值归一化用于着色
colors = np.zeros((points.shape[0], 3))
colors[:, 0] = points[:, 3] / points[:, 3].max() # 红色通道表示强度
pcd.colors = o3d.utility.Vector3dVector(colors)
o3d.visualization.draw_geometries([pcd])
5. 高级应用与性能优化
5.1 点云特征提取
提取法向量和曲率等几何特征:
from sklearn.neighbors import NearestNeighbors
def compute_normals(points, k=30):
nbrs = NearestNeighbors(n_neighbors=k).fit(points[:, :3])
_, indices = nbrs.kneighbors(points[:, :3])
normals = np.zeros((points.shape[0], 3))
for i in range(points.shape[0]):
cov = np.cov(points[indices[i], :3].T)
U, S, Vt = np.linalg.svd(cov)
normals[i] = U[:, -1] # 最小特征值对应特征向量
return normals
5.2 点云下采样策略
使用体素网格下采样保持几何特征:
def voxel_downsample(points, voxel_size=0.1):
voxel_grid = {}
for p in points:
voxel = tuple((p[:3] // voxel_size).astype(int))
if voxel not in voxel_grid:
voxel_grid[voxel] = []
voxel_grid[voxel].append(p)
# 取每个体素中心点
downsampled = np.array([np.mean(np.array(v), axis=0)
for v in voxel_grid.values()])
return downsampled
5.3 多传感器数据同步
实现激光雷达与相机数据对齐:
def map_pointcloud_to_image(nusc, points, sample_token, camera_channel='CAM_FRONT'):
sample = nusc.get('sample', sample_token)
cam_token = sample['data'][camera_channel]
# 获取标定参数
cam = nusc.get('sample_data', cam_token)
cs_rec = nusc.get('calibrated_sensor', cam['calibrated_sensor_token'])
# 点云->相机坐标系
points_hom = np.concatenate([points[:, :3], np.ones((points.shape[0], 1))], axis=1)
points_cam = np.dot(np.linalg.inv(Quaternion(cs_rec['rotation']).rotation_matrix),
(points_hom[:, :3].T - cs_rec['translation'][:, np.newaxis])).T
# 相机坐标系->图像平面
K = np.array(cs_rec['camera_intrinsic'])
points_img = np.dot(K, points_cam.T).T
points_img = points_img[:, :2] / points_img[:, 2][:, np.newaxis]
# 过滤超出图像范围的点
valid = (points_img[:, 0] >= 0) & (points_img[:, 0] < cam['width']) & \
(points_img[:, 1] >= 0) & (points_img[:, 1] < cam['height']) & \
(points_cam[:, 2] > 0) # z>0表示在相机前方
return points_img[valid], valid
6. 实际项目中的经验分享
在处理多个自动驾驶项目后,我总结了以下关键经验:
-
数据校验必不可少:始终检查点云与标注的对应关系,使用
nusc.render_sample_data()可视化验证 -
内存管理策略:
- 对于大规模处理,考虑使用
memory-mapped文件 - 将处理流程分解为多个阶段,中间结果保存到磁盘
- 对于大规模处理,考虑使用
-
性能瓶颈分析:
- 90%的处理时间消耗在数据加载和坐标系转换
- 使用Numba加速关键计算部分
-
标注数据利用:
def get_annotated_points(nusc, sample_token):
sample = nusc.get('sample', sample_token)
points = load_point_cloud(nusc, sample_token)
# 初始化标注掩码
annotations = np.zeros(points.shape[0], dtype=int)
for ann_token in sample['anns']:
ann = nusc.get('sample_annotation', ann_token)
box = nusc.get_box(ann_token)
# 转换到激光雷达坐标系
box.translate(-np.array(ann['translation']))
box.rotate(Quaternion(ann['rotation']).inverse)
# 判断点是否在标注框内
indices = box.points_in_box(points[:, :3])
annotations[indices] = 1
return points, annotations
更多推荐



所有评论(0)