4. 点云基础处理

点云数据拿到手,第一件事是什么?

不是急着做检测,而是先「洗数据」。

我刚开始做激光雷达感知那会儿,上来就把原始点云丢进检测模型,结果帧率直接掉到个位数,还全是噪点。后来才明白——原始点云里大量冗余点、离群噪点、无关区域,不处理根本没法用。

这一章,我们就聊聊点云预处理里最常用的四个滤波方法。说白了,就是给点云「瘦身」和「去杂质」。

4.1 体素滤波降采样

体素滤波,我习惯叫它「点云瘦身术」。

你想想看,一个64线激光雷达,一帧能扫出十几万个点。这么多点,很多是重复的、冗余的。比如一面墙,你扫出1000个点和扫出100个点,对检测来说差别不大,但计算量差了一个数量级。

原理其实很简单:

  • 把三维空间划分成一个个小立方体(体素)
  • 每个体素内只保留一个点(通常是重心或中心点)
  • 体素越大,降采样越狠

我在项目中遇到过一个问题:体素大小设得太小,降采样效果不明显;设得太大,远处的行人直接变成一两个点,检测器根本认不出来。后来我总结了一个经验——体素边长一般设为0.1m~0.5m,具体看你的场景和算力。

核心参数:leaf_size(体素边长)

经验值:城区道路0.2m,高速场景0.3m~0.5m

import open3d as o3d

# 读取点云
pcd = o3d.io.read_point_cloud("lidar_frame.pcd")

# 体素滤波降采样
voxel_size = 0.2  # 单位:米
down_pcd = pcd.voxel_down_sample(voxel_size)

print(f"原始点数: {len(pcd.points)}")
print(f"降采样后: {len(down_pcd.points)}")

我的小技巧:如果你用PCL库,记得先计算点云包围盒,再根据包围盒尺寸动态调整体素大小。这样能自适应不同场景。

4.2 直通滤波

直通滤波,说白了就是「切一刀」。

激光雷达扫出来的点云,范围很大。但我们的检测区域往往只关心前方几十米、左右几个车道。后面的、太远的、太高的点,留着干嘛?

应用场景:

  • 只保留前方0~50m的点云
  • 去掉地面以上的点(比如天桥、树叶)
  • 去掉左右两侧超出车道范围的点

我曾经犯过一个低级错误:直通滤波时只设了x轴范围,忘了设y轴。结果路边的灌木丛全被当成障碍物,导致车辆频繁误刹车。嗯,从那以后我每次都会检查三个轴的范围。

import numpy as np

def passthrough_filter(points, axis=0, min_val=-10, max_val=50):
    """
    直通滤波
    :param points: Nx3 点云数组
    :param axis: 0=x, 1=y, 2=z
    :param min_val: 最小值
    :param max_val: 最大值
    """
    mask = (points[:, axis] >= min_val) & (points[:, axis] <= max_val)
    return points[mask]

# 示例:保留前方0~50m,左右±5m,高度-1~2m
points = np.asarray(pcd.points)
filtered = passthrough_filter(points, 0, 0, 50)  # x轴
filtered = passthrough_filter(filtered, 1, -5, 5)  # y轴
filtered = passthrough_filter(filtered, 2, -1, 2)  # z轴

注意:直通滤波的顺序会影响结果。我建议先做z轴(高度)滤波,再做x、y轴。因为先去掉地面和天空的点,能减少后续计算量。

4.3 半径滤波

半径滤波,用来干掉「孤魂野鬼」。

激光雷达扫到一些稀疏的噪点,比如飞鸟、灰尘、传感器噪声。这些点周围没有邻居,一看就是「不合群」的。

判断逻辑:

  • 对每个点,统计它半径r范围内的邻居数量
  • 如果邻居数少于阈值k,就判定为离群点,删除

我习惯用半径滤波来处理多径反射造成的鬼影点。有一次在隧道里,激光雷达打到墙壁再反射回来,产生了一堆虚假点。半径滤波一上,全清掉了。

# Open3D 半径滤波
cl, ind = down_pcd.remove_radius_outlier(nb_points=6, radius=0.5)
filtered_pcd = down_pcd.select_by_index(ind)

print(f"滤波前: {len(down_pcd.points)}")
print(f"滤波后: {len(filtered_pcd.points)}")

参数调优建议:

  • 半径:一般设为0.3~1.0m,取决于点云密度
  • 邻居数阈值:通常设为3~10,密度越大阈值越高
  • 先做体素降采样,再做半径滤波,效果更好

4.4 统计滤波

统计滤波,比半径滤波更「科学」一点。

它不是用固定半径,而是用统计学的思路——计算每个点到它k个最近邻的平均距离。如果这个平均距离偏离整体分布太远,就认为是离群点。

具体步骤:

  1. 对每个点,找它的k个最近邻
  2. 计算到这些邻居的平均距离
  3. 所有点的平均距离构成一个分布(近似高斯分布)
  4. 设定一个标准差倍数std_ratio,超出范围的剔除

说白了,就是「大多数人都在这个距离范围内,你离大家太远,那你就是异类」。

# Open3D 统计滤波
cl, ind = filtered_pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
clean_pcd = filtered_pcd.select_by_index(ind)

print(f"统计滤波前: {len(filtered_pcd.points)}")
print(f"统计滤波后: {len(clean_pcd.points)}")

我的经验:std_ratio一般取1.5~3.0。取1.5比较激进,会去掉较多点;取3.0比较保守。我通常先用2.0试跑一遍,看效果再微调。

4.5 四种滤波方法对比

滤波方法 主要用途 核心参数 适用场景
体素滤波 降采样、减少点数 leaf_size 所有场景,预处理第一步
直通滤波 裁剪ROI区域 axis, min, max 固定场景,如车道检测
半径滤波 去除稀疏离群点 radius, nb_points 噪点较少的场景
统计滤波 去除统计离群点 nb_neighbors, std_ratio 噪点分布不均匀的场景

4.6 实战流程:一个完整的预处理管线

在实际项目中,我不会只用一种滤波。通常的流程是:

  1. 直通滤波:先切出ROI区域,去掉无关点
  2. 体素滤波:降采样,减少计算量
  3. 统计滤波:去除离群噪点
  4. (可选)半径滤波:如果还有残留噪点,再补一刀

这个顺序我踩过坑才总结出来的。以前我先把体素滤波放在第一步,结果直通滤波时还要处理大量冗余点,白白浪费算力。先切ROI再降采样,效率高很多。

def preprocess_pipeline(pcd, roi_range, voxel_size, nb_neighbors=20, std_ratio=2.0):
    # 1. 直通滤波
    points = np.asarray(pcd.points)
    mask_x = (points[:, 0] >= roi_range[0]) & (points[:, 0] <= roi_range[1])
    mask_y = (points[:, 1] >= roi_range[2]) & (points[:, 1] <= roi_range[3])
    mask_z = (points[:, 2] >= roi_range[4]) & (points[:, 2] <= roi_range[5])
    mask = mask_x & mask_y & mask_z
    pcd.points = o3d.utility.Vector3dVector(points[mask])
    
    # 2. 体素滤波
    pcd = pcd.voxel_down_sample(voxel_size)
    
    # 3. 统计滤波
    _, ind = pcd.remove_statistical_outlier(nb_neighbors, std_ratio)
    pcd = pcd.select_by_index(ind)
    
    return pcd

# 使用示例
roi = [0, 50, -5, 5, -1, 2]  # x, y, z范围
clean_pcd = preprocess_pipeline(pcd, roi, voxel_size=0.2)

避坑指南:

  • 体素滤波后,点云密度会下降,后续的统计滤波参数要相应调低
  • 直通滤波的边界值不要设得太紧,留一点余量,防止目标被误切
  • 如果场景中有大量动态物体(如行人、车辆),统计滤波的std_ratio建议取大一点

好了,四种滤波方法讲完了。你可能会问:这么多参数,怎么调?

我的建议是:先拿一帧数据,可视化出来,肉眼观察噪点和目标分布。然后从体素滤波开始,逐步调整。调参这事,说白了就是「试」出来的。多试几次,你就有感觉了。

点云预处理管线流程图 原始点云 ① 直通滤波 ② 体素滤波降采样 ③ 统计滤波去噪 干净点云 → 检测 参数建议 直通滤波: x:0~50m, y:±5m, z:-1~2m 体素滤波: leaf_size: 0.1~0.3m 统计滤波: nb_neighbors: 20 std_ratio: 1.5~2.5 ⚠️ 注意顺序: 先切ROI → 再降采样 → 最后去噪 效率最高!

可可精品资料库