4. 点云预处理:滤波、下采样与离群点移除

点云预处理,说白了就是给原始点云数据“洗个澡”。

你想想看,不管是激光扫描还是结构光拍照,拿到的原始点云里总会有各种“脏东西”——噪点、飞点、边缘毛刺。如果不处理干净,后面的测量精度和抓取成功率根本没法保证。

我个人习惯把预处理分成三个步骤:滤波 → 下采样 → 离群点移除。顺序不能乱,每一步都有它的道理。

核心观点:预处理不是“越干净越好”,而是“保留有效信息的同时去掉干扰”。过度滤波会丢失细节,欠滤波则会把噪声带入后续算法。

点云预处理流程 原始点云 步骤1:统计滤波 去除稀疏噪点 k=30, sigma=1.5 步骤2:体素下采样 体素网格滤波 GridSize=1.0mm 输出 ⚠ 注意: • 统计滤波和半径滤波二选一即可,不需要同时做 • 下采样必须在滤波之后,否则噪声会被“放大”到体素网格里 • 离群点移除通常放在最后,作为“收尾”步骤

4.1 统计滤波:干掉那些“孤零零”的点

统计滤波的原理很简单:每个点周围有多少邻居?

如果一个点周围邻居太少,说明它是个“孤岛”,大概率是噪声。我一般用 k=30 作为邻居数量,标准差倍数设为 1.5。这两个参数怎么调?

参数 作用 调大 调小
k(邻居数) 计算平均距离时参考的点数 滤波更严格,保留更少点 滤波更宽松,保留更多点
sigma(标准差倍数) 判断离群点的阈值 保留更多边缘点 去除更多点

我的经验:在 Halcon 里做统计滤波,我习惯先用 smallest_unconnected_component 看一眼点云里有哪些孤立小团块。如果看到很多“飞点”,就把 sigma 降到 1.0;如果点云本身很密,sigma 可以放到 2.0。

// Halcon 统计滤波示例
* 读取点云
read_object_model_3d(PointCloud, 'ply', [], [], ObjectModel3D)
* 统计滤波参数
k := 30
sigma := 1.5
* 执行滤波
statistical_outlier_filter(ObjectModel3D, 'knn', k, sigma, FilteredModel)
* 查看剩余点数
get_object_model_3d_params(FilteredModel, 'num_points', NumPoints)

4.2 半径滤波:另一种思路

半径滤波和统计滤波是“亲戚”,但判断逻辑不同。

它是在每个点周围画一个固定半径的球,数球里有几个点。如果少于某个阈值,就干掉它。

方法 判断依据 适用场景
统计滤波 基于全局平均距离 点云密度均匀的场景
半径滤波 基于固定半径内的点数 点云密度变化大的场景

我记得有一次做汽车钣金件的测量,点云边缘密度特别低,中间密度高。用统计滤波把边缘细节全干掉了,换成半径滤波才保住边缘特征。

避坑指南:我曾经在半径滤波上吃过亏——半径设得太小(0.5mm),阈值设得太大(20个点),结果把整个点云都滤没了。后来我总结了一个经验:半径至少是点云平均点间距的 2-3 倍,阈值设 5-10 个点比较安全。

// Halcon 半径滤波示例
* 半径滤波参数
radius := 2.0
min_points := 8
* 执行半径滤波
radius_outlier_filter(ObjectModel3D, radius, min_points, FilteredModel)

4.3 体素下采样:给点云“减肥”

原始点云动不动就几十万甚至上百万个点,直接处理太慢了。体素下采样就是给点云“减肥”——用一个个小立方体(体素)把空间网格化,每个体素里只保留一个点。

体素大小怎么选?

  • 测量任务:体素大小 = 你允许的测量误差。比如你要测 0.1mm 的精度,体素就不能大于 0.1mm。
  • 抓取任务:体素可以大一点,0.5-1.0mm 都行,反正抓取不需要亚毫米精度。
  • 配准任务:体素大小建议在 0.3-0.5mm,太大会丢失特征点。

关键点:体素下采样不是简单的“抽稀”,而是用体素内所有点的重心来代表这个体素。所以它不会产生“锯齿”,比随机下采样靠谱得多。

// Halcon 体素下采样示例
* 设置体素大小(单位:米)
GridSize := 0.001  // 1mm
* 执行体素滤波
voxel_filter(ObjectModel3D, 'grid', GridSize, DownsampledModel)
* 对比点数
get_object_model_3d_params(ObjectModel3D, 'num_points', OriginalNum)
get_object_model_3d_params(DownsampledModel, 'num_points', DownNum)

4.4 离群点移除:最后的“清场”

离群点移除和统计滤波有点像,但更“暴力”。它专门针对那些明显偏离主点云的点——比如扫描时反光造成的“飞点”,或者边缘的“毛刺”。

我常用的方法是基于距离的离群点检测:

  1. 计算每个点到最近邻的距离
  2. 设定一个距离阈值(比如平均距离的 3 倍)
  3. 超过阈值的点直接删除

我的习惯:离群点移除我一般放在预处理最后一步。因为前面做了滤波和下采样,点云已经比较干净了,这时候离群点移除的参数可以设得宽松一些,只干掉那些“明显不对劲”的点。

// Halcon 离群点移除示例
* 基于距离的离群点检测
distance_threshold := 5.0  // 单位:mm
* 执行移除
connection_object_model_3d(ObjectModel3D, 'distance', distance_threshold, ConnectedModels)
* 保留最大的连通分量(主点云)
select_largest_object_model_3d(ConnectedModels, CleanModel, 1)

4.5 参数调优:我的“三板斧”

参数调优是预处理里最头疼的事。我总结了一套“三板斧”方法:

步骤 操作 工具
第一斧 可视化观察点云,标记明显噪点区域 visualize_object_model_3d
第二斧 用统计滤波粗滤,参数从保守开始 statistical_outlier_filter
第三斧 检查滤波结果,逐步收紧参数 get_object_model_3d_params

嗯,这里要注意:不要一次性把参数调到最狠。我见过有人上来就把 sigma 设成 0.5,结果点云只剩 30% 的点,特征全没了。慢慢调,每次看效果,这才是工程思维。

总结一下:

  • 统计滤波和半径滤波选一个就行,我推荐统计滤波(更稳定)
  • 体素下采样是必选项,体素大小根据任务精度定
  • 离群点移除是“保险”,放在最后做
  • 参数调优要“由松到紧”,边看边调