4、点云数据基础:点云数据结构、滤波与可视化
点云这东西,说白了就是一堆三维坐标点的集合。每个点带着 (x, y, z) 信息,有时候还带上颜色、强度什么的。我刚开始接触点云时,觉得不就是个数组嘛,有什么好讲的?后来踩了不少坑才明白——数据结构选不对,后面所有算法都得重写。
4.1 点云数据结构:PCL vs Open3D
目前工业界最常用的两个库,一个是 PCL(Point Cloud Library),一个是 Open3D。我个人习惯是:做底层算法、嵌入式部署时用 PCL;做快速原型、可视化验证时用 Open3D。为什么?往下看。
4.1.1 PCL 的点云结构
PCL 的核心数据结构是 pcl::PointCloud<T>。T 可以是多种点类型,比如:
pcl::PointXYZ— 只有 x, y, zpcl::PointXYZRGB— 带 RGB 颜色pcl::PointXYZI— 带强度值(激光雷达常用)pcl::PointNormal— 带法向量
我在项目中遇到过一个问题:用 PointXYZ 存了 500 万个点,后面想加颜色信息,结果发现类型不兼容,只能全部重读。嗯,这里要注意——一开始就选好点类型,后面能省很多事。
// PCL 点云创建与基本操作
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 填充点云
cloud->width = 1000;
cloud->height = 1; // 无序点云
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i) {
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
height == 1 表示无序点云,height > 1 表示有序点云(比如深度图转过来的)。有序点云可以用 at(row, col) 快速索引,性能好很多。
4.1.2 Open3D 的点云结构
Open3D 就简洁多了。它的核心是 open3d::geometry::PointCloud,所有数据都存在 numpy 数组里。你想想看,这意味着什么?意味着你可以直接用 Python 的切片、广播、矩阵运算来操作点云,开发效率极高。
import open3d as o3d
import numpy as np
# 创建点云
pcd = o3d.geometry.PointCloud()
# 从 numpy 数组创建
points = np.random.rand(1000, 3)
pcd.points = o3d.utility.Vector3dVector(points)
# 添加颜色
colors = np.random.rand(1000, 3)
pcd.colors = o3d.utility.Vector3dVector(colors)
# 添加法向量
normals = np.random.rand(1000, 3)
pcd.normals = o3d.utility.Vector3dVector(normals)
4.2 点云滤波:去噪与降采样
原始点云数据通常很「脏」。传感器噪声、环境干扰、离群点……不处理的话,后面做配准、分割、重建全是错的。我见过有人拿原始点云直接跑 ICP,结果收敛到不知道哪里去了。
4.2.1 体素滤波(Voxel Grid Filter)
体素滤波说白了就是「三维像素化」。你把空间切成一个个小立方体(体素),每个体素里只保留一个点(通常是重心)。这样点云数量就大大减少了。
为什么要做体素滤波?两个原因:
- 降采样:1000 万个点降到 100 万个,计算量直接少一个数量级
- 均匀化:消除传感器近密远疏的问题,让点云分布更均匀
// PCL 体素滤波
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.01f, 0.01f, 0.01f); // 1cm 体素
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
voxel_filter.filter(*cloud_filtered);
# Open3D 体素滤波
voxel_size = 0.01 # 1cm
downpcd = pcd.voxel_down_sample(voxel_size)
4.2.2 统计滤波(Statistical Outlier Removal)
统计滤波是用来去除离群点的。它的原理很简单:计算每个点到它 k 个最近邻的平均距离,如果这个距离超过全局均值 + n 倍标准差,就认为是离群点。
我曾经在一个激光雷达项目里,点云里总有几根「飞线」——就是莫名其妙飘在空中的点。统计滤波一跑,干干净净。
// PCL 统计滤波
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50); // 考虑 50 个近邻
sor.setStddevMulThresh(1.0); // 标准差倍数
sor.filter(*cloud_filtered);
# Open3D 统计滤波
cl, ind = pcd.remove_statistical_outlier(nb_neighbors=50, std_ratio=1.0)
pcd_filtered = pcd.select_by_index(ind)
meanK 一般设 20-50,stddevMulThresh 设 1.0-2.0。设太小会把正常点也删掉,设太大又滤不干净。我习惯先设 1.0,看效果再微调。
4.3 点云可视化
可视化是点云开发里最容易被忽视的一环。很多人觉得「能看就行」,但实际调试时,一个好的可视化工具能帮你一眼看出问题。
4.3.1 PCL 可视化
PCL 的 pcl::visualization::CloudViewer 是最简单的,但功能有限。我推荐用 PCLVisualizer,支持多窗口、添加文字、交互操作。
// PCL 可视化
pcl::visualization::PCLVisualizer viewer("点云查看器");
viewer.setBackgroundColor(0, 0, 0); // 黑色背景
// 添加点云
viewer.addPointCloud<pcl::PointXYZ>(cloud, "sample cloud");
// 设置点大小
viewer.setPointCloudRenderingProperties(
pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
// 添加坐标轴
viewer.addCoordinateSystem(1.0);
// 循环显示
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
4.3.2 Open3D 可视化
Open3D 的可视化就优雅多了。一行代码就能看,而且支持鼠标交互(旋转、平移、缩放)。
# Open3D 可视化
o3d.visualization.draw_geometries([pcd],
window_name="点云查看",
width=800,
height=600)
# 更高级的用法:添加多个几何体
o3d.visualization.draw_geometries([pcd, mesh, lineset],
point_show_normal=True)
4.4 本章知识体系
下面这张图总结了点云数据基础的核心内容,从数据结构到滤波再到可视化,一条线串起来。
这张图把本章的三个核心模块串起来了。从上到下,先选好数据结构,再做滤波处理,最后可视化验证。每一步都有对应的工具和参数,实际项目中按这个流程走,基本不会出大问题。
公众号:蓝海资料掘金营,微信deep3321