NuScenes点云数据可视化实战:5步搞定Lidar到图像的坐标转换(附完整代码)

NuScenes点云数据可视化实战:5步搞定Lidar到图像的坐标转换(附完整代码) NuScenes点云数据可视化实战5步搞定Lidar到图像的坐标转换附完整代码在自动驾驶和计算机视觉领域点云数据与图像数据的融合分析是感知系统开发的关键环节。NuScenes作为业界广泛使用的多模态数据集其丰富的传感器数据为算法验证提供了坚实基础。本文将手把手带您实现Lidar点云到相机图像的坐标转换全流程通过5个清晰步骤完成从原始数据到可视化效果的完整实现。1. 环境准备与数据加载开始前需要确保已安装必要的Python工具包pip install nuscenes-devkit pyquaternion opencv-python numpy加载NuScenes数据集时首先需要明确数据版本和存储路径。对于快速验证推荐使用mini版本from nuscenes.nuscenes import NuScenes from pyquaternion import Quaternion import numpy as np import cv2 import os # 数据集配置 version v1.0-mini # 可选mini/trainval/test dataroot /path/to/your/nuscenes/data nusc NuScenes(versionversion, datarootdataroot, verboseTrue)提示首次使用时建议运行nusc.list_scenes()查看可用场景nusc.render_sample(sample_token)可交互式浏览数据。数据加载的核心是获取传感器采样点的对应关系。NuScenes通过token系统关联各类数据# 获取第一个样本 sample nusc.sample[0] # 提取Lidar数据token lidar_token sample[data][LIDAR_TOP] lidar_data nusc.get(sample_data, lidar_token) # 加载点云数据 lidar_path os.path.join(dataroot, lidar_data[filename]) pointcloud np.fromfile(lidar_path, dtypenp.float32).reshape(-1, 5) # x,y,z,intensity,ring_index2. 坐标系转换原理剖析NuScenes涉及三类关键坐标系坐标系类型描述转换关系传感器坐标系Lidar/Camera的本地坐标系通过calibrated_sensor标定车体坐标系(ego)车辆中心的动态坐标系通过ego_pose记录全局坐标系场景固定坐标系所有ego_pose的基准坐标转换的完整链条为Lidar坐标系 → 车体坐标系 → 全局坐标系 → 目标相机车体坐标系 → 相机坐标系 → 图像像素坐标系关键转换矩阵包括lidar_to_ego从Lidar到车体的变换ego_to_global从车体到全局场景的变换global_to_camera_ego从全局到相机车体的逆变换camera_ego_to_camera从相机车体到相机本体的变换camera_intrinsic相机内参矩阵3. 矩阵运算实现定义通用的4x4变换矩阵生成函数def get_transform_matrix(calib_data, inverseFalse): 生成4x4齐次变换矩阵 :param calib_data: 标定数据字典(含rotation和translation) :param inverse: 是否求逆矩阵 :return: 4x4变换矩阵 mat np.eye(4) mat[:3, :3] Quaternion(calib_data[rotation]).rotation_matrix mat[:3, 3] calib_data[translation] return np.linalg.inv(mat) if inverse else mat获取Lidar到全局坐标系的变换# Lidar到车体 lidar_calib nusc.get(calibrated_sensor, lidar_data[calibrated_sensor_token]) lidar_to_ego get_transform_matrix(lidar_calib) # 车体到全局 ego_pose nusc.get(ego_pose, lidar_data[ego_pose_token]) ego_to_global get_transform_matrix(ego_pose) # 组合变换 lidar_to_global ego_to_global lidar_to_ego4. 相机坐标系处理选择目标相机并准备其变换矩阵以前置摄像头为例camera_type CAM_FRONT camera_data nusc.get(sample_data, sample[data][camera_type]) # 全局到相机车体 camera_ego_pose nusc.get(ego_pose, camera_data[ego_pose_token]) global_to_camera_ego get_transform_matrix(camera_ego_pose, inverseTrue) # 相机车体到相机本体 camera_calib nusc.get(calibrated_sensor, camera_data[calibrated_sensor_token]) camera_ego_to_camera get_transform_matrix(camera_calib, inverseTrue) # 相机内参 intrinsic np.eye(4) intrinsic[:3, :3] camera_calib[camera_intrinsic] # 完整变换链 global_to_image intrinsic camera_ego_to_camera global_to_camera_ego lidar_to_image global_to_image lidar_to_global5. 点云投影与可视化将Lidar点云转换为齐次坐标并投影# 转换为齐次坐标 points pointcloud[:, :3] # 取xyz homogeneous_points np.hstack([points, np.ones((len(points), 1))]) # 坐标变换 image_points (lidar_to_image homogeneous_points.T).T # 归一化 image_points[:, :2] / image_points[:, [2]] # 过滤镜头背后的点 valid_points image_points[image_points[:, 2] 0] pixel_coords valid_points[:, :2].astype(int)加载对应图像并绘制投影结果# 加载图像 img_path os.path.join(dataroot, camera_data[filename]) image cv2.imread(img_path) # 绘制点云 for x, y in pixel_coords: if 0 x image.shape[1] and 0 y image.shape[0]: cv2.circle(image, (x, y), 2, (0, 0, 255), -1) # 可选绘制3D标注框 for ann_token in sample[anns]: ann nusc.get(sample_annotation, ann_token) box Box(ann[translation], ann[size], Quaternion(ann[rotation])) corners box.corners().T hom_corners np.hstack([corners, np.ones((8, 1))]) img_corners (global_to_image hom_corners.T).T img_corners[:, :2] / img_corners[:, [2]] # 绘制立方体边 lines [(0,1), (1,2), (2,3), (3,0), (4,5), (5,6), (6,7), (7,4), (0,4), (1,5), (2,6), (3,7)] for start, end in lines: pt1 img_corners[start] pt2 img_corners[end] if pt1[2] 0 and pt2[2] 0: # 确保点在镜头前 cv2.line(image, pt1[:2].astype(int), pt2[:2].astype(int), (0,255,0), 2) cv2.imwrite(lidar_projection.jpg, image)通过这五个步骤我们实现了从原始Lidar数据到图像坐标系的完整投影流程。实际应用中可以进一步优化根据ring_index对点云进行颜色编码添加深度信息可视化实现多相机同步投影开发交互式查看工具