4. 点云预处理:滤波、下采样与离群点移除
点云预处理,说白了就是给原始点云数据“洗个澡”。
你想想看,不管是激光扫描还是结构光拍照,拿到的原始点云里总会有各种“脏东西”——噪点、飞点、边缘毛刺。如果不处理干净,后面的测量精度和抓取成功率根本没法保证。
我个人习惯把预处理分成三个步骤:滤波 → 下采样 → 离群点移除。顺序不能乱,每一步都有它的道理。
核心观点:预处理不是“越干净越好”,而是“保留有效信息的同时去掉干扰”。过度滤波会丢失细节,欠滤波则会把噪声带入后续算法。
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 离群点移除:最后的“清场”
离群点移除和统计滤波有点像,但更“暴力”。它专门针对那些明显偏离主点云的点——比如扫描时反光造成的“飞点”,或者边缘的“毛刺”。
我常用的方法是基于距离的离群点检测:
- 计算每个点到最近邻的距离
- 设定一个距离阈值(比如平均距离的 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% 的点,特征全没了。慢慢调,每次看效果,这才是工程思维。
总结一下:
- 统计滤波和半径滤波选一个就行,我推荐统计滤波(更稳定)
- 体素下采样是必选项,体素大小根据任务精度定
- 离群点移除是“保险”,放在最后做
- 参数调优要“由松到紧”,边看边调