点云里那些“不合群的点”:SOR 去噪算法的原理与实现

138 阅读7分钟

如果你接触过点云数据——
无论是 LiDAR、三维扫描,还是机器人感知系统——
你大概率都遇到过这样的问题:

点云里总有一些点,看起来“不太对劲”。

它们可能:

  • 离主体结构很远
  • 稀稀拉拉,孤零零地飘在空中
  • 不属于任何明显的平面、物体或轮廓

这些点,通常被称为噪声点,也常被叫作离群点(Outliers)

SOR(Statistical Outlier Removal,统计离群点去除)
正是用来解决这类问题的一种经典、实用的点云去噪方法。

我会分成 6 个层次,从「直觉 → 算法 → 参数 → 适用性 → 性能 → 工程实现注意点」。


一、一句话先给结论(工程视角)

SOR 的核心思想:
“如果一个点,和它周围邻居的平均距离,明显比大多数点都大,那它很可能是噪声。”

它不是看“孤立点”,而是看 统计异常


二、直觉理解(不用数学)

想象一个场景:

  • 大部分点云 → 像一群人站在广场上
  • 噪声点 → 偶尔有几个人站得特别远

SOR 做的事就是:

  1. 对每一个人:

    • 看他周围最近的 K 个人
    • 算一个“平均距离”
  2. 看全体人的“平均距离分布”

  3. 如果某个人的平均距离:

    • 明显比大家都大
    • 那就把他判定为噪声

👉 关键不是“远不远”,而是 “和整体相比是否异常”


三、算法流程(标准 SOR)

假设输入是点云 P = {p₁, p₂, ..., pₙ}

Step 1:K 近邻搜索(核心成本)

对每个点 pᵢ

  • 找到它的 K 个最近邻
  • 计算平均距离:

[
d_i = \frac{1}{K} \sum_{j=1}^{K} \text{dist}(p_i, neighbor_j)
]

👉 得到一个数组:

D = [d₁, d₂, d₃, ..., dₙ]

Step 2:统计分布

计算:

  • 全体平均值
    [
    \mu = mean(D)
    ]
  • 全体标准差
    [
    \sigma = std(D)
    ]

Step 3:阈值判断

定义阈值:

[
T = \mu + \alpha \cdot \sigma
]

其中:

  • α = StddevMulThresh(常用 1.0 ~ 2.0)

Step 4:过滤点

  • 如果
    [
    d_i > T
    ]
    → 认为 pᵢ离群点(噪声)
  • 否则 → 保留

四、SOR 的两个核心参数(非常重要)

1️⃣ MeanK(K)

每个点参与统计的邻居数量

  • 小(如 10):

    • 对局部变化更敏感
    • 容易误删边缘点
  • 大(如 50):

    • 更平滑、更稳定
    • 可能保留部分噪声

📌 常用经验值:

点云规模MeanK
几百点10–20
几千点20–30
稠密点云30–50

2️⃣ StddevMulThresh(α)

“容忍多少异常”

  • 小(1.0):

    • 严格
    • 去噪强
  • 大(2.0~3.0):

    • 保守
    • 保留更多点

📌 工程常用:

MeanK = 20
StddevMulThresh = 1.0 ~ 2.0

五、SOR 的优点与局限(帮你做工程取舍)

✅ 优点

  1. 无模型假设

    • 不需要知道场景结构
  2. 参数直观

    • 好调
  3. 稳定

    • 不容易“炸”

👉 非常适合作为 第一道去噪


❌ 局限

  1. 计算量大

    • KNN 是主要瓶颈
  2. 对密度变化敏感

    • 稀疏区域容易被误判
  3. 不保边缘

    • 点云边界天然“邻居少”

六、SOR 在 CPU / GPU / PCL 中的工程实现差异

🧠 CPU(PCL 实现)

  • 用 KD-Tree 做 KNN
  • O(N log N)
  • 稳定、好用、直接调用
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setMeanK(20);
sor.setStddevMulThresh(1.0);
sor.filter(*output);

👉 首选 MVP 方案


🚀 GPU(CUDA)

GPU 加速点:

  • KNN 搜索
  • 距离计算

难点:

  • KNN 不规则内存访问

  • 通常需要:

    • Grid-based
    • Voxel hashing
    • 或 FAISS / 自研结构

👉 适合大规模点云(10万+)


⚙️ oneAPI(DPC++)

  • 逻辑和 CUDA 类似

  • 但生态不成熟

  • 更适合作为:

    • Intel GPU / CPU fallback

七、什么时候“该用 SOR”,什么时候“不该用”

✅ 适合

  • LiDAR 原始点云
  • 随机噪声
  • 需要快速、稳定去噪

❌ 不适合

  • 强结构化场景(平面/边界极多)
  • 密度变化非常大的点云
  • 需要保留边缘的任务(建模)

👉 通常组合使用:

SOR → VoxelGrid → 平面/边缘算法

八、SOR 通常用在什么场景?

1️⃣ LiDAR 点云处理(最常见)

在车载 / 无人机 LiDAR 中,常见噪声包括:

  • 多径反射产生的飞点
  • 雨雾、灰尘干扰
  • 边缘误测点

SOR 通常作为第一步预处理

原始点云 → SOR 去噪 → 下采样 / 特征提取 / 建图

2️⃣ 三维重建与 3D 扫描

在建筑扫描、工业建模中:

  • 表面反光
  • 遮挡
  • 扫描误差

都会引入零散噪声点。

SOR 的优势在于:

  • 不依赖几何模型
  • 不需要先验结构

3️⃣ 机器人感知与 SLAM

在 SLAM 系统中:

  • 离群点会严重干扰点云匹配(ICP、Scan Matching)
  • 导致定位抖动甚至失败

SOR 常用于:

  • 提升点云匹配稳定性
  • 降低异常点对系统的影响

一句话总结:

只要点云中存在“随机、零散、不合群”的点,SOR 就很有用。


九、SOR 一般怎么“用在工程里”?

在实际工程中,SOR 很少单独存在,而是作为流水线的一部分:

原始点云
 → SOR(去明显噪声)
 → 下采样(VoxelGrid)
 → 特征提取 / 匹配 / 重建

很多成熟库(如 PCL)都已经内置了 SOR 实现,调用方式非常简单。

十、SOR 如何在工程中保证实时性?

从算法流程上看,SOR 似乎并不“轻量”:

  • 对每一个点做近邻搜索
  • 计算距离
  • 再做一次全局统计

那它为什么还能被广泛用于 实时 LiDAR、SLAM、机器人系统 中?

关键并不在于 SOR “有多快”,
而在于 工程上是如何使用它的


1️⃣ SOR 并不是用在“所有点”上

在真实系统中,SOR 很少直接作用于:

  • 百万级原始点云
  • 全分辨率扫描数据

通常的做法是:

原始点云
 → 下采样 / ROI 裁剪
 → SOR

先降低点数,再做统计去噪,可以大幅减少计算量。


2️⃣ 使用高效的近邻搜索结构

SOR 的主要开销来自 K 近邻搜索

工程中通常会使用:

  • KD-Tree
  • Octree
  • 体素网格加速结构

这些数据结构可以把近邻搜索的复杂度,从暴力的 O(N²),降到接近 O(N log N)。

这也是为什么:

  • 成熟库(如 PCL)中的 SOR
  • 在中等规模点云上表现非常稳定

3️⃣ 参数选择,本身就是一种“性能控制”

SOR 的两个核心参数,其实也在影响性能:

  • MeanK 越大 → 计算越慢
  • MeanK 越小 → 速度越快

在实时系统中,通常会:

  • 使用相对保守但不极端的 MeanK(如 20)
  • 在稳定性和性能之间取平衡

换句话说:

参数不是越“精确”越好,而是“够用就好”。


4️⃣ SOR 非常适合并行化

从计算模式上看:

  • 每个点的邻居搜索与距离计算
  • 本质上是相互独立的

这使得 SOR 非常适合:

  • 多线程 CPU
  • GPU 并行加速

在高性能系统中:

  • CPU 版本用于中小规模点云
  • GPU 版本用于大规模、实时处理

5️⃣ SOR 的“实时性角色定位”

在工程实践中,SOR 很少被要求:

  • 极致精度
  • 极致速度

它更像是一个:

“成本可控、效果稳定的第一道过滤器”

只要它能:

  • 快速去掉明显异常点
  • 为后续算法提供更干净的数据

那它就已经完成了自己的任务。


一句话总结

SOR 之所以能用于实时系统,并不是因为它天生极快,
而是因为它在工程中被放在了“合适的位置”,
并配合合适的数据规模、数据结构和参数使用。

这也是很多基础算法在工程中长期存在的原因:
不是因为完美,而是因为“足够好用”。

十一、给你一句工程师级总结

SOR 是一种“统计一致性过滤器”,
它不试图理解世界,只负责把“明显不合群的点”请出去。

这也是为什么它:

  • 适合作为第一步
  • 适合作为 CPU / GPU / PCL 的公共基线算法