Open3D与Nuscenes数据集:3D点云与标注框的跨坐标系可视化实践

张开发
2026/5/7 18:04:13 15 分钟阅读

分享文章

Open3D与Nuscenes数据集:3D点云与标注框的跨坐标系可视化实践
1. 初识Open3D与Nuscenes数据集第一次接触3D点云可视化时我被那些漂浮在空中的彩色小点震撼到了。这些看似杂乱无章的点实际上精确记录了周围环境的每一个细节。Open3D这个开源库让我能够轻松地将这些数据变成直观的3D图像而Nuscenes数据集则提供了丰富的自动驾驶场景数据。Nuscenes数据集包含了1000个精心标注的驾驶场景每个场景都有来自雷达、相机等多种传感器的数据。最让我兴奋的是它还包含了详细的3D标注框标注了车辆、行人等各种物体的精确位置和大小。但问题来了这些数据存储在不同的坐标系中如何将它们统一显示出来记得我第一次尝试可视化时点云和标注框完全对不上就像把两张不同地图强行叠在一起。经过反复试验才发现这是因为雷达数据使用雷达坐标系而标注框使用全局坐标系。要把它们放在同一个视图里必须进行精确的坐标系转换。2. 理解Nuscenes的坐标系系统在Nuscenes数据集中有三种主要坐标系需要特别注意。首先是全局坐标系global这是固定不变的世界坐标系所有场景中的物体位置都相对于这个坐标系定义。其次是自车坐标系ego它以车辆为中心随着车辆移动而变化。最后是雷达坐标系lidar这是以雷达传感器为原点的局部坐标系。让我打个比方想象你在一个巨大的停车场全局坐标系你站在自己的车旁边自车坐标系而你的眼睛就是雷达雷达坐标系。当你移动时自车坐标系跟着你移动但停车场本身是固定的。同样你的眼睛看到的景象雷达数据会随着你转头而变化。在代码实现中我们需要特别注意坐标系的定义差异。自车坐标系使用前左上XYZ定义而雷达坐标系使用左前上XYZ定义。这个细微差别如果不处理好就会导致可视化结果完全错乱。3. 点云数据加载与可视化加载Nuscenes的点云数据其实很简单。数据存储在.bin文件中每个点包含x、y、z坐标以及反射强度等信息。使用Open3D可视化这些点的代码非常直观def points2o3d(self, points): pcd open3d.geometry.PointCloud() pcd.points open3d.utility.Vector3dVector(points[:, :3]) # 根据强度值着色 intensity points[:, 3] intensity_colors np.full((points.shape[0], 3), 255) intensity_colors[:, 0] intensity / np.max(intensity) pcd.colors open3d.utility.Vector3dVector(intensity_colors) return pcd这段代码创建了一个Open3D点云对象并将点坐标和颜色信息赋给它。我特别喜欢用反射强度来着色这样能直观看出哪些物体反射更强如金属表面。在实际项目中我发现点云密度对可视化效果影响很大。有时候远处物体点云太稀疏几乎看不清楚。这时可以调整Open3D的渲染选项比如增大点的大小opt self.vis.get_render_option() opt.point_size 1.5 # 默认是1可以适当增大4. 3D标注框的可视化处理Nuscenes中的3D标注框存储在全局坐标系中而我们需要将其转换到雷达坐标系才能与点云一起显示。这个过程涉及到多次坐标系转换box self.nusc.get_box(ann_token) # 从全局转到自车坐标系 box.translate(-np.array(pose_record[translation])) box.rotate(Quaternion(pose_record[rotation]).inverse) # 从自车坐标系转到雷达坐标系 box.translate(-np.array(cs_record[translation])) box.rotate(Quaternion(cs_record[rotation]).inverse)这段代码先通过平移和旋转将标注框从全局坐标系转到自车坐标系再转到雷达坐标系。我最初实现时经常搞错旋转的顺序导致标注框方向错误。后来发现一定要先平移再旋转顺序不能颠倒。转换完成后我们需要将标注框表示为8个角点然后用线连接这些角点形成立方体corners box.corners().T # 转换为(8,3)在Open3D中我们使用LineSet来表示这些框线o3d_bbox open3d.geometry.LineSet() o3d_bbox.points open3d.utility.Vector3dVector(bbox) o3d_bbox.lines open3d.utility.Vector2iVector(bbox_lines) o3d_bbox.colors open3d.utility.Vector3dVector(colors)5. 跨坐标系可视化的完整实现将点云和标注框统一显示的关键在于正确处理所有坐标系转换。下面是一个完整的可视化流程加载雷达点云数据已在雷达坐标系加载3D标注框并将其从全局坐标系转换到雷达坐标系创建Open3D可视化窗口添加点云和标注框几何体设置合适的视角和渲染选项完整的Open3D可视化类大致如下class Open3D_visualizer(): def __init__(self, points, gt_bboxes, pred_bboxes): self.vis open3d.visualization.Visualizer() self.points self.points2o3d(points) self.gt_boxes self.box2o3d(gt_bboxes, red) self.pred_boxes self.box2o3d(pred_bboxes, green) def show(self): self.vis.create_window(window_name3D可视化) self.vis.add_geometry(self.points) if self.gt_boxes: for box in self.gt_boxes: self.vis.add_geometry(box) # 设置视角 view_ctrl self.vis.get_view_control() view_ctrl.set_front([0, 0, 1]) view_ctrl.set_up([0, 1, 0]) view_ctrl.set_zoom(0.2) self.vis.run() self.vis.destroy_window()在实际使用中我发现设置合适的初始视角非常重要。上面的代码将z轴设为前方y轴向上这样符合大多数人的观看习惯。zoom值可以根据场景大小调整0.2适合大多数城市驾驶场景。6. 3D标注框在图像上的反投影除了3D可视化我们经常需要将3D标注框投影到2D图像上验证标注准确性。这需要知道雷达到每个相机的投影矩阵def get_lidar2image(self, camera_info): lidar2camera_r np.linalg.inv(camera_info[sensor2lidar_rotation]) lidar2camera_t (camera_info[sensor2lidar_translation] lidar2camera_r.T) lidar2camera_rt np.eye(4).astype(np.float32) lidar2camera_rt[:3, :3] lidar2camera_r.T lidar2camera_rt[3, :3] -lidar2camera_t camera_intrinsics np.eye(4).astype(np.float32) camera_intrinsics[:3, :3] camera_info[cam_intrinsic] return camera_intrinsics lidar2camera_rt.T得到投影矩阵后我们可以将3D框的角点投影到图像平面coords np.concatenate([bboxes.reshape(-1, 3), np.ones((num_bboxes * 8, 1))], axis-1) coords coords transform.T coords coords.reshape(-1, 8, 4) coords[:, :, 0] / coords[:, :, 2] # x x/z coords[:, :, 1] / coords[:, :, 2] # y y/z这个过程需要注意的是透视除法除以z值这是将3D坐标转为2D图像坐标的关键步骤。我最初忘记这一步结果投影点全部集中在图像中心附近。7. 常见问题与调试技巧在实际项目中我遇到过各种坐标系转换的问题。分享几个调试技巧检查坐标系定义确认你清楚每个坐标系的原点和轴向定义。Nuscenes的不同坐标系轴向定义可能与你想象的不同。逐步验证转换不要一次性完成所有转换。可以先验证全局到自车的转换是否正确再验证自车到雷达的转换。使用已知点验证找一些已知在多个坐标系中坐标的点如车辆原点手动验证转换结果。可视化中间结果在转换过程中间阶段可视化数据确保每一步都正确。注意旋转顺序四元数旋转的顺序很重要不同的顺序可能导致完全不同的结果。记得有一次我的标注框总是比实际位置偏了一些后来发现是在转换时漏掉了一个平移操作。这种问题通过打印中间变量值很容易发现print(原始标注框中心:, ann[translation]) print(转换后中心:, box.center)8. 性能优化与高级功能当处理大量数据时性能可能成为问题。以下是几个优化建议批量处理点云避免逐个点处理使用NumPy的向量化操作。减少不必要的转换如果只需要在某个坐标系中可视化就不要做多余的坐标系转换。使用Open3D的缓存Open3D的Visualizer类支持几何体缓存重复添加相同几何体时性能更好。选择性渲染对于远处的物体可以降低点云密度或简化标注框显示。对于更高级的应用可以考虑添加交互功能如点击标注框显示物体信息实现时间序列可视化展示场景的动态变化集成语义分割结果用不同颜色显示不同类别的点# 示例添加交互回调 def key_callback(vis): if vis.get_key() ord(H): print(帮助信息...) self.vis.register_key_callback(key_callback)在实际项目中我发现良好的交互功能可以大大提高标注验证效率。比如通过按键切换不同视角或者隐藏/显示特定类别的物体。

更多文章