点云预处理技术:从原始数据到可用特征
大家好,我是你们的老朋友。今天我们来聊聊点云预处理。
说实话,很多刚入行的同学容易忽略这一步。他们觉得,传感器数据拿过来,直接丢进网络不就完了?我刚开始做融合跟踪时也这么想。结果呢?点云里全是噪点,地面把障碍物都淹没了,聚类出来的目标乱七八糟。嗯,从那以后,我再也不敢跳过预处理了。
点云预处理,说白了就是三个核心任务:滤波、地面分割、聚类。我们一个一个来拆解。
一、点云滤波:先把垃圾扔掉
激光雷达采集的点云,原始数据里什么都有。远处的噪点、空气中的悬浮粒子、多路径反射造成的鬼影……你想想看,如果把这些脏数据直接拿去聚类,结果能好吗?
我个人习惯,拿到点云的第一件事就是滤波。常用的有两种:
1.1 体素滤波(Voxel Filter)
体素滤波的核心思想很简单:把空间切成一个个小立方体(体素),每个体素里只保留一个点。说白了就是降采样。
为什么要降采样?64线激光雷达一帧可能有十几万个点。这么多点,计算量太大了。体素滤波可以帮你把点数降到几千到几万,同时保留物体的几何结构。
关键参数:体素大小
体素越大,点越少,但细节丢失也越多。我一般设0.1m到0.2m。太小了降采样效果不明显,太大了小物体会被抹掉。
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("lidar_frame.pcd")
# 体素滤波,体素大小0.1m
voxel_size = 0.1
down_pcd = pcd.voxel_down_sample(voxel_size)
print(f"原始点数: {len(pcd.points)}")
print(f"滤波后点数: {len(down_pcd.points)}")
我的经验:体素滤波还有一个隐藏好处——它天然具有去噪能力。因为孤立的噪点周围没有其他点,体素里就它一个,保留下来也无所谓。但如果是离群噪点,后面统计滤波会处理它。
1.2 统计滤波(Statistical Outlier Removal)
统计滤波是专门用来干掉离群点的。它的原理是:计算每个点周围k个邻居的平均距离,如果这个距离明显大于平均值,就判定为离群点。
为什么会这样?因为真正的物体表面,点与点之间的距离是均匀的。而噪点孤零零地飘在外面,邻居离它很远。
# 统计滤波
cl, ind = down_pcd.remove_statistical_outlier(
nb_neighbors=20, # 邻居点数
std_ratio=2.0 # 标准差倍数阈值
)
filtered_pcd = down_pcd.select_by_index(ind)
print(f"滤波后点数: {len(filtered_pcd.points)}")
避坑指南:我曾经把std_ratio设成1.0,结果把一辆车的点云削掉了一半。因为车身上的点本身就有一定离散度。建议从2.0开始调,宁保守勿激进。
二、地面分割:把路和障碍物分开
地面分割是融合跟踪里非常关键的一步。为什么?因为地面点占了点云的一半以上。如果不分割,聚类时地面和障碍物会粘在一起。
常用的方法有两种:RANSAC和Patchwork。
2.1 RANSAC平面拟合
RANSAC(随机采样一致性)是一种迭代算法。它随机选三个点拟合一个平面,然后统计有多少点在这个平面上。重复多次,取点数最多的那个平面作为地面。
这个方法简单粗暴,但有个问题:它假设地面是一个完整的平面。遇到坡道、颠簸路面就麻烦了。
import numpy as np
from sklearn.linear_model import RANSACRegressor
# 假设 points 是 Nx3 的点云数组
# RANSAC 拟合平面: z = ax + by + c
X = points[:, :2] # (x, y)
y = points[:, 2] # z
ransac = RANSACRegressor(
residual_threshold=0.2, # 点到平面的距离阈值
max_trials=100
)
ransac.fit(X, y)
# 获取地面点索引
inlier_mask = ransac.inlier_mask_
ground_points = points[inlier_mask]
obstacle_points = points[~inlier_mask]
参数调优:residual_threshold设0.1m到0.3m比较合适。设太小了,地面点会被误判为障碍物;设太大了,低矮障碍物会被当成地面。
2.2 Patchwork:更鲁棒的地面分割
RANSAC在复杂场景下表现不佳。我推荐大家试试Patchwork。它是韩国KAIST团队提出的方法,把点云划分成多个扇形区域,每个区域独立拟合地面。
Patchwork的优势在于:它能处理起伏路面、坡道,甚至能区分路沿和地面。我在项目中用过,效果比RANSAC好不少。
# Patchwork 伪代码示意
# 实际使用时建议调用开源库 pypatchwork
from pypatchwork import PatchWork
# 初始化参数
params = {
"sensor_height": 1.8, # 激光雷达安装高度
"num_rings": 40, # 环数
"num_sectors": 60, # 扇形数
"uprightness_thr": 0.707 # 垂直度阈值
}
patchwork = PatchWork(params)
ground_mask = patchwork.run(points)
我的建议:如果场景是平坦的停车场或高速路,RANSAC完全够用。如果是城市道路、有坡道、有路沿,直接用Patchwork。别在RANSAC上浪费时间调参。
三、点云聚类:把障碍物一个个拎出来
地面分割之后,剩下的就是障碍物点云了。但这时候还是一团乱麻——行人、车辆、自行车都混在一起。我们需要把它们分开,这就是聚类要做的事。
3.1 DBSCAN:密度聚类
DBSCAN(基于密度的空间聚类)是我最常用的方法。它不需要预先指定类别数,能发现任意形状的簇,还能识别噪声点。
它的核心思想:如果一个点周围有足够多的邻居,就把它和邻居归为一类。然后不断向外扩张,直到找不到新的邻居为止。
from sklearn.cluster import DBSCAN
# 对地面分割后的障碍物点云聚类
obstacle_points = points[~inlier_mask]
# DBSCAN 聚类
clustering = DBSCAN(
eps=0.5, # 邻域半径
min_samples=10 # 核心点最少邻居数
).fit(obstacle_points)
labels = clustering.labels_
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
print(f"聚类出 {n_clusters} 个目标")
避坑指南:eps参数非常敏感。设0.3m,行人可能被切成好几块;设1.0m,两辆并排的车会粘在一起。我一般从0.5m开始调,根据激光雷达线束密度微调。
3.2 欧式聚类:简单高效
欧式聚类是DBSCAN的简化版。它基于欧氏距离,把距离小于阈值的点归为一类。实现简单,计算速度快。
PCL(Point Cloud Library)里提供了现成的实现。Open3D也有封装。
import open3d as o3d
# 假设 filtered_pcd 是地面分割后的障碍物点云
# 欧式聚类
labels = np.array(filtered_pcd.cluster_dbscan(
eps=0.5,
min_points=10,
print_progress=True
))
# 提取每个聚类的点云
max_label = labels.max()
for i in range(max_label + 1):
cluster = filtered_pcd.select_by_index(np.where(labels == i)[0])
# 计算包围盒
bbox = cluster.get_axis_aligned_bounding_box()
print(f"目标 {i}: 中心 {bbox.get_center()}, 尺寸 {bbox.get_extent()}")
经验之谈:欧式聚类和DBSCAN本质上是一回事。区别在于DBSCAN多了核心点、边界点的概念,对噪声更鲁棒。如果场景简单,欧式聚类就够了;如果场景复杂、噪声多,用DBSCAN。
四、预处理流程总结
好了,我们把整个流程串起来看看:
| 步骤 | 方法 | 目的 | 关键参数 |
|---|---|---|---|
| 1. 滤波 | 体素滤波 | 降采样,减少计算量 | 体素大小 0.1~0.2m |
| 2. 滤波 | 统计滤波 | 去除离群噪点 | 邻居数20,std_ratio 2.0 |
| 3. 地面分割 | RANSAC / Patchwork | 分离地面和障碍物 | 阈值0.1~0.3m |
| 4. 聚类 | DBSCAN / 欧式聚类 | 分离不同障碍物 | eps 0.5m, min_samples 10 |
我个人习惯的pipeline是:体素滤波 → 统计滤波 → Patchwork地面分割 → DBSCAN聚类。这套组合在大多数场景下表现稳定。
最后提醒一句:参数不是死的。不同激光雷达、不同场景,参数都要微调。我建议你拿自己的数据跑一遍,看看效果,再根据实际情况调整。
预处理做好了,后面的跟踪就轻松多了。嗯,今天就到这里。