3、点云数据读取与可视化:使用PCL读取PCD文件、使用Open3D可视化点云、调整视角与颜色映射
点云数据拿到手,第一件事是什么?
当然是看看它长什么样。
我刚开始做激光雷达感知那会儿,经常对着几十兆的PCD文件发愣——数据对不对?有没有噪点?目标在哪儿?这些光靠看数字是看不出来的。说白了,可视化就是点云处理的「眼睛」。今天咱们就把这双眼睛擦亮。
3.1 使用PCL读取PCD文件
PCL(Point Cloud Library)是点云处理的老牌库。我个人习惯用C++做底层处理时首选它。PCD文件格式是PCL自家的标准格式,结构清晰,支持多种字段类型。
先看一个最基础的读取示例:
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
// 定义点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 读取PCD文件
if (pcl::io::loadPCDFile<pcl::PointXYZ>("scene.pcd", *cloud) == -1) {
PCL_ERROR("Couldn't read file scene.pcd \n");
return -1;
}
std::cout << "Loaded " << cloud->width * cloud->height << " data points." << std::endl;
嗯,这里要注意:loadPCDFile返回-1表示读取失败。我曾经在项目里忘了检查返回值,结果拿着空指针往下跑,调试了半天才发现是文件路径写错了。
如果点云带有颜色信息,用pcl::PointXYZRGB类型:
pcl::PointCloud<pcl::PointXYZRGB>::Ptr colored_cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::io::loadPCDFile("colored_scene.pcd", *colored_cloud);
读取后怎么检查数据质量?我一般会看三个指标:
- 点云数量:太少可能是裁剪过度,太多要考虑性能
- 坐标范围:用
getMinMax3D()检查,防止离群点 - NaN值:用
isFinite()过滤,不然可视化时会出黑点
3.2 使用Open3D可视化点云
Open3D是我做可视化时的首选。为什么?因为它Python接口简洁,交互流畅,而且开箱即用。你想想看,调试算法时频繁改参数,用Python比C++快太多了。
安装很简单:
pip install open3d
读取并显示点云:
import open3d as o3d
# 读取PCD文件
pcd = o3d.io.read_point_cloud("scene.pcd")
# 可视化
o3d.visualization.draw_geometries([pcd])
就这么三行代码,点云就出来了。我第一次用的时候也觉得太方便了。
如果想在可视化窗口里交互操作:
- 左键拖拽:旋转视角
- 滚轮:缩放
- Ctrl + 左键:平移
- Shift + 左键:框选点云
这些操作在调试时特别有用。我记得有一次排查地面分割效果,就是靠框选功能快速定位了误分割的区域。
3.3 调整视角与颜色映射
默认视角往往不是最佳视角。比如看道路场景,俯视图比侧视图更有用。Open3D提供了灵活的视角控制。
3.3.1 设置视角
import open3d as o3d
import numpy as np
pcd = o3d.io.read_point_cloud("scene.pcd")
# 创建可视化窗口
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(pcd)
# 获取视角控制
ctrl = vis.get_view_control()
# 设置相机参数(前视图)
ctrl.set_front([0, 0, -1]) # 相机朝向
ctrl.set_up([0, -1, 0]) # 上方向
ctrl.set_lookat([0, 0, 0]) # 目标点
ctrl.set_zoom(0.8) # 缩放
vis.run()
vis.destroy_window()
参数怎么调?我一般先手动旋转到合适视角,然后打印当前参数:
# 在可视化窗口中按 'P' 键,会打印当前相机参数到控制台
这个技巧帮我省了不少时间。你想想看,手动调好视角,记下参数,下次直接复用,多省事。
3.3.2 颜色映射
点云默认是灰色的,但实际应用中我们需要用颜色表达信息。常见的映射方式有:
| 映射方式 | 适用场景 | 代码示例 |
|---|---|---|
| 高度映射 | 区分地面与障碍物 | pcd.colors = o3d.utility.Vector3dVector(colors) |
| 强度映射 | 识别反光物体 | 根据intensity字段着色 |
| 类别映射 | 语义分割结果展示 | 不同类别不同颜色 |
| 距离映射 | 判断远近关系 | 根据到传感器的距离着色 |
高度映射的完整实现:
import numpy as np
import open3d as o3d
pcd = o3d.io.read_point_cloud("scene.pcd")
points = np.asarray(pcd.points)
# 获取Z轴高度
z = points[:, 2]
z_min, z_max = z.min(), z.max()
# 归一化到[0,1]并映射到颜色
norm_z = (z - z_min) / (z_max - z_min)
# 使用jet颜色映射
colors = np.zeros((points.shape[0], 3))
colors[:, 0] = norm_z # 红色通道
colors[:, 1] = 1 - norm_z # 绿色通道
colors[:, 2] = 0.5 * (1 - norm_z) # 蓝色通道
pcd.colors = o3d.utility.Vector3dVector(colors)
o3d.visualization.draw_geometries([pcd])
强度映射稍微复杂一点,因为激光雷达的强度值范围不统一。Velodyne一般是0-255,Ouster是0-65535。我曾经被这个坑过——用Velodyne的代码处理Ouster数据,颜色全黑了。
# 假设pcd有intensity字段
intensity = np.asarray(pcd.intensity)
# 自适应归一化(去掉极端值)
percentile_99 = np.percentile(intensity, 99)
intensity_clipped = np.clip(intensity, 0, percentile_99)
norm_intensity = intensity_clipped / percentile_99
# 灰度映射
colors = np.stack([norm_intensity] * 3, axis=1)
pcd.colors = o3d.utility.Vector3dVector(colors)
3.4 实战:多视角可视化
在实际项目中,我经常需要同时看多个视角。比如看俯视图判断道路边界,看侧视图检测行人。Open3D支持多窗口:
# 创建两个窗口
vis1 = o3d.visualization.Visualizer()
vis2 = o3d.visualization.Visualizer()
vis1.create_window(window_name="俯视图", width=640, height=480)
vis2.create_window(window_name="侧视图", width=640, height=480)
vis1.add_geometry(pcd)
vis2.add_geometry(pcd)
# 设置俯视图视角
ctrl1 = vis1.get_view_control()
ctrl1.set_front([0, 0, -1])
ctrl1.set_up([0, -1, 0])
# 设置侧视图视角
ctrl2 = vis2.get_view_control()
ctrl2.set_front([1, 0, 0])
ctrl2.set_up([0, -1, 0])
vis1.run()
vis2.run()
vis1.destroy_window()
vis2.destroy_window()
这个技巧在调试多传感器融合时特别有用。我经常左边放激光雷达点云,右边放相机图像,对照着看,问题一目了然。
3.5 总结
今天的内容就这些。说白了,点云读取和可视化是基本功,但千万别小看它。我见过太多人花大量时间在算法上,结果可视化做得一塌糊涂,调试效率极低。
记住三个要点:
- PCL读取:注意文件路径和版本兼容性
- Open3D可视化:交互操作要熟练,快捷键记牢
- 颜色映射:选对特征值,做好归一化
下一章咱们聊聊点云滤波——怎么把噪点去掉,把地面分离出来。到时候见。