2. 点云可视化:Open3D基础、点云渲染与交互、多视角可视化、点云动画
点云可视化,说白了就是让那些密密麻麻的坐标点“活”起来。你想想看,几千几万个 (x, y, z) 数字堆在一起,谁能看出个门道?只有把它们渲染成三维空间里的点,你才能一眼看出哪里是地面、哪里是墙面、哪里是障碍物。
我个人习惯用 Open3D 来做这件事。这个库上手快,交互也顺手。今天我们就从最基础的渲染开始,一步步做到能旋转、能切视角、甚至能看动画。
2.1 Open3D 基础:从零搭建可视化窗口
先装好 Open3D。如果你用的是 pip,一行命令搞定:
pip install open3d
装完之后,我们来写第一段可视化代码。别怕,就几行:
import open3d as o3d
import numpy as np
# 生成一些随机点云,假装是激光雷达扫出来的
points = np.random.rand(1000, 3) # 1000个点,每个点有xyz
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
# 可视化
o3d.visualization.draw_geometries([pcd])
运行之后,你会看到一个窗口,里面飘着一团彩色的点。按住鼠标左键拖拽可以旋转视角,滚轮可以缩放。嗯,这就是最基础的交互了。
pcd.paint_uniform_color([0, 0, 1])。我刚开始做项目时经常用颜色来区分不同类别的点,非常直观。
2.2 点云渲染:颜色、大小与法向量
光有位置还不够。点云渲染有三个核心参数:颜色、大小、法向量。我一个个说。
2.2.1 颜色渲染
颜色可以来自传感器(比如 RGB 相机),也可以是你自己赋值的。举个例子,根据点的高度来染色:
# 假设 points 是 Nx3 的数组
z = points[:, 2]
z_min, z_max = z.min(), z.max()
colors = (z - z_min) / (z_max - z_min) # 归一化到0~1
colors = np.stack([colors, np.zeros_like(colors), 1 - colors], axis=1)
pcd.colors = o3d.utility.Vector3dVector(colors)
这样低处的点偏蓝,高处的点偏红。我在做地形分割时经常用这个技巧,一眼就能看出地势起伏。
2.2.2 点的大小
Open3D 默认的点大小是 1.0 像素。有时候点太密,看起来糊成一团;有时候点太稀疏,又看不清结构。调整点大小需要修改可视化选项:
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(pcd)
opt = vis.get_render_option()
opt.point_size = 3.0 # 调大一点
vis.run()
vis.destroy_window()
2.2.3 法向量渲染
法向量是每个点的方向信息,对分割和识别特别重要。计算法向量也很简单:
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
可视化法向量时,可以用 draw_geometries 的 point_show_normal 参数:
o3d.visualization.draw_geometries([pcd], point_show_normal=True)
你会看到每个点上都长出了一根小箭头,指向法向量方向。这个在平面检测、边缘提取时特别有用。
2.3 多视角可视化:同时看多个角度
有时候一个视角不够用。比如你要同时看点云的俯视图和侧视图,或者对比原始点云和分割结果。Open3D 支持多窗口,也支持在一个窗口里分屏显示。
2.3.1 多窗口方案
# 窗口1:原始点云
o3d.visualization.draw_geometries([pcd_original], window_name="原始点云")
# 窗口2:分割后的点云
o3d.visualization.draw_geometries([pcd_segmented], window_name="分割结果")
两个窗口独立控制,互不干扰。我调试算法时经常这么干,左边看输入,右边看输出,对比起来很方便。
2.3.2 单窗口分屏
如果你不想开太多窗口,可以用 draw_geometries_with_animation_callback 或者自己写一个 VisualizerWithKeyCallback。不过更简单的办法是用 draw_geometries 的 view_params 参数预设视角:
# 预设俯视图视角
view_params = {
"front": [0, 0, -1], # 从正上方看
"lookat": [0, 0, 0],
"up": [0, -1, 0],
"zoom": 0.8
}
o3d.visualization.draw_geometries([pcd], **view_params)
你可以把多个视角的参数存成字典,循环展示,实现“多视角幻灯片”的效果。
2.4 点云动画:让点云动起来
点云动画,说白了就是逐帧更新点的位置或颜色。比如模拟激光雷达扫描的过程,或者展示点云随时间的变化。
Open3D 提供了 draw_geometries_with_animation_callback 函数,你只需要写一个回调函数,每帧更新点云数据:
import time
pcd = o3d.geometry.PointCloud()
points = np.random.rand(100, 3)
pcd.points = o3d.utility.Vector3dVector(points)
def animate_callback(vis):
# 每帧随机更新点的位置
new_points = np.random.rand(100, 3)
pcd.points = o3d.utility.Vector3dVector(new_points)
vis.update_geometry(pcd)
return False
o3d.visualization.draw_geometries_with_animation_callback([pcd], animate_callback)
运行之后,你会看到点云像雪花一样不断跳动。当然,实际项目中不会这么随机。我做过一个自动驾驶的演示,把连续 100 帧激光雷达数据做成动画,能清楚看到车辆在路口转弯的过程。
2.5 本章知识体系
下面这张图总结了点云可视化的核心流程和工具链:
2.6 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 点云显示为空白 | 坐标范围过大或过小 | 检查点云坐标是否在合理范围,尝试归一化 |
| 颜色显示不对 | 颜色值未归一化到 [0, 1] | 确保 RGB 值除以 255 |
| 动画卡顿 | 每帧数据量太大 | 降采样或使用 VoxelGrid 滤波 |
| 法向量方向混乱 | 邻域搜索半径不合适 | 根据点云密度调整 radius 和 max_nn |
draw_geometries,结果窗口卡死了 30 秒。后来我养成了习惯:大数据集先降采样到 10 万点以内再可视化。用 pcd.voxel_down_sample(voxel_size=0.05) 就能搞定。