第4章:点云数据基础

各位同学,今天我们来聊聊点云数据。说实话,我刚入行那会儿,觉得点云就是一堆三维坐标点,没什么特别的。直到第一次做机器人抓取,点云数据乱七八糟,算法根本跑不动,我才意识到——点云处理,基础不牢,地动山摇。

这一章,我带你从点云的数据结构开始,一步步搞定可视化、滤波。这些都是后续一切高级操作的地基。

4.1 点云数据结构

点云说白了,就是三维空间中一堆点的集合。每个点通常包含 (x, y, z) 坐标,有时候还带着颜色、法向量、强度等信息。

目前最主流的两个库:PCL(Point Cloud Library)和 Open3D。我个人习惯,做机器人抓取时用 Open3D 更多,因为它 Python 接口友好,调试起来快。但 PCL 在工业界积累深厚,很多底层算法更成熟。

4.1.1 PCL 中的点云结构

PCL 的核心数据结构是 pcl::PointCloud<T>。T 可以是多种点类型:

点类型 包含字段 典型用途
pcl::PointXYZ x, y, z 纯几何位置
pcl::PointXYZRGB x, y, z, r, g, b 带颜色的点云
pcl::PointNormal x, y, z, normal_x, normal_y, normal_z 带法向量的点云

举个例子,创建一个点云并添加点:

pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
cloud->width = 100;
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 表示无序点云。如果是 organized point cloud(比如从深度图转来的),height 就是图像高度。

4.1.2 Open3D 中的点云结构

Open3D 就简单多了。核心类是 open3d.geometry.PointCloud。它的 points 是一个 numpy 数组,操作起来非常 Pythonic。

import open3d as o3d
import numpy as np

# 创建点云
points = np.random.rand(100, 3).astype(np.float64)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)

# 添加颜色
colors = np.random.rand(100, 3).astype(np.float64)
pcd.colors = o3d.utility.Vector3dVector(colors)

你看,代码量少了很多。我在项目中经常用 Open3D 做快速原型验证,效率很高。

我的小建议: 如果你做研究或快速开发,优先选 Open3D。如果做工业级部署,PCL 的 C++ 实现性能更稳。两个都学,不吃亏。

4.2 点云可视化

可视化是点云处理中最直观的调试手段。你想想看,一堆数字你很难看出问题,但一显示出来,离群点、空洞、噪声一目了然。

4.2.1 PCL 可视化

PCL 用 pcl::visualization::CloudViewerPCLVisualizer。前者简单,后者功能强。

pcl::visualization::CloudViewer viewer("Simple Cloud Viewer");
viewer.showCloud(cloud);
while (!viewer.wasStopped()) {
    // 保持窗口打开
}

我曾经踩过一个坑:在循环里反复调用 showCloud,结果界面卡死。后来发现,加个 spinOnce 就好了。

4.2.2 Open3D 可视化

Open3D 的可视化就优雅多了:

o3d.visualization.draw_geometries([pcd], 
                                  window_name="点云可视化",
                                  width=800, 
                                  height=600)

一行代码搞定。而且支持鼠标交互——旋转、平移、缩放,非常方便。

避坑指南: 我曾经在远程服务器上跑可视化,结果 Open3D 直接报错。因为服务器没有 GUI 环境。解决方案:用 o3d.visualization.draw_geometriesnon_blocking 模式,或者干脆用 o3d.io.write_point_cloud 保存下来,下载到本地看。

4.3 体素滤波

体素滤波(Voxel Grid Filter),说白了就是降采样。为什么要做?因为原始点云太密了,动辄几十万、上百万个点,计算起来慢得要命。

它的原理很简单:把空间划分成一个个小立方体(体素),每个体素内只保留一个点(通常是重心)。

体素滤波原理示意图 原始点云(密集) 滤波后(稀疏)

代码实现:

# Open3D 体素滤波
voxel_size = 0.05  # 5cm 体素
downsampled_pcd = pcd.voxel_down_sample(voxel_size=voxel_size)
// PCL 体素滤波
pcl::VoxelGrid<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setLeafSize(0.05f, 0.05f, 0.05f);
sor.filter(*cloud_filtered);
注意: 体素大小怎么选?我一般先设 0.01m(1cm),看效果再调。太小了降采样效果不明显,太大了会丢失细节。做抓取时,目标物体尺寸的 1/10 到 1/20 是个不错的起点。

4.4 统计滤波

统计滤波(Statistical Outlier Removal)用来去除离群点。原理很直观:计算每个点到它 k 个最近邻的平均距离。如果这个距离超过全局均值一定标准差,就认为是离群点。

为什么会需要它?传感器噪声、边缘跳变都会产生一些孤立的点。这些点对后续的配准、分割影响很大。

# Open3D 统计滤波
cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
filtered_pcd = pcd.select_by_index(ind)
// PCL 统计滤波
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50);
sor.setStddevMulThresh(1.0);
sor.filter(*cloud_filtered);

参数怎么调?nb_neighbors(或 setMeanK)我一般设 20-50。std_ratio 设 1.0-2.0。值越小,剔除得越狠。

我的经验: 有一次做零件抓取,点云里总有几个飘在空中的点,怎么都去不掉。后来发现是反光造成的。把 std_ratio 从 2.0 降到 1.5,问题就解决了。所以,参数要根据实际数据微调。

4.5 半径滤波

半径滤波(Radius Outlier Removal)是另一种去噪方法。它检查每个点周围指定半径内有多少个邻居。如果邻居数量少于阈值,就干掉它。

和统计滤波的区别?统计滤波看的是距离分布,半径滤波看的是局部密度。说白了,统计滤波适合去除稀疏的离群点,半径滤波适合去除孤立的点簇。

# Open3D 半径滤波
cl, ind = pcd.remove_radius_outlier(nb_points=16, radius=0.05)
filtered_pcd = pcd.select_by_index(ind)
// PCL 半径滤波
pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem;
outrem.setInputCloud(cloud);
outrem.setRadiusSearch(0.05);
outrem.setMinNeighborsInRadius(10);
outrem.filter(*cloud_filtered);

参数选择上,radius 我一般设成体素大小的 2-3 倍。nb_points 设 10-20 比较常见。

总结一下三种滤波的适用场景:
  • 体素滤波:降采样,减少数据量。必用。
  • 统计滤波:去除稀疏离群点。推荐先用这个。
  • 半径滤波:去除孤立小点簇。如果统计滤波效果不好,再试这个。

好了,这一章的内容就到这里。点云数据结构是基础,可视化是调试利器,三种滤波是预处理三板斧。把这些练熟了,后面的配准、分割、抓取姿态估计才能跑得顺。


专注资料整理