4. 点云聚类:DBSCAN聚类、欧式聚类、基于网格的聚类
各位同学,咱们今天聊聊点云聚类。说实话,雷达点云处理里,聚类这一步非常关键。你想想看,传感器扫回来一堆点,哪个点是哪个目标身上的?没人告诉你。聚类就是干这个的——把属于同一个物体的点,归到一堆。
我刚开始做雷达感知那会儿,总觉得聚类很简单,不就是把近的点放一起嘛。后来踩了不少坑才发现,选不对聚类方法,后面跟踪、识别全得崩。今天我把三种最常用的方法掰开揉碎了讲:DBSCAN、欧式聚类、还有基于网格的聚类。
4.1 为什么需要聚类?
先问个问题:雷达检测完,输出了一堆点云坐标。每个点都带着距离、速度、角度信息。但哪个点是行人?哪个点是车?哪个点是路边的护栏?
聚类就是做这个分组的。它把空间上靠近、运动特性相似的点,合并成一个簇。每个簇,就对应一个潜在目标。
我在项目里遇到过这种情况:一辆大卡车旁边站着个人,如果聚类半径设大了,人和车就聚到一起了,变成一个目标。这要是做自动驾驶,后果你懂的。所以聚类参数调不好,后面全是白干。
4.2 DBSCAN聚类
DBSCAN,全称是 Density-Based Spatial Clustering of Applications with Noise。名字挺长,但核心思想很简单:找密度高的区域。
它不关心簇的形状是圆是方,只要点够密,就归为一类。稀疏的点,直接标为噪声。
4.2.1 核心参数
DBSCAN有两个关键参数:
| 参数 | 含义 | 我的经验值 |
|---|---|---|
| eps | 邻域半径 | 0.5m ~ 1.5m(看场景) |
| minPts | 核心点最少邻居数 | 3 ~ 8(点云稀疏时取小值) |
eps设小了,一个行人可能被切成好几块。eps设大了,人和车又粘一起。我个人习惯是先看点云的平均间距,然后取1.5倍作为eps的初始值。
4.2.2 算法流程
说白了就三步:
- 遍历每个点,看它半径eps内有多少个点
- 如果邻居数 ≥ minPts,标记为核心点,开始扩张
- 核心点周围的点,只要在eps内,都归到同一个簇
嗯,这里要注意:DBSCAN不需要预先指定要聚成几类。这点比K-means强太多。你想想看,路上有多少目标,你事先哪知道?
4.2.3 代码示例
from sklearn.cluster import DBSCAN
import numpy as np
# 假设points是N×3的点云数组
points = np.array([[x1,y1,z1], [x2,y2,z2], ...])
# 我一般先做一下距离归一化
# 因为雷达的x、y、z量纲一致,直接算欧式距离就行
clustering = DBSCAN(eps=1.0, min_samples=5).fit(points)
# 聚类结果
labels = clustering.labels_
# -1表示噪声点,其他数字表示簇ID
4.3 欧式聚类
欧式聚类,名字听着高大上,其实就是基于距离的最近邻搜索。它比DBSCAN更直观:两个点距离小于阈值,就认为是一家人。
4.3.1 原理
欧式聚类的逻辑很简单:
- 随便选一个点,找它周围距离小于阈值r的所有点
- 把这些点归为一簇
- 对新加入的点,继续找它们的邻居
- 直到没有新点加入,再选下一个未处理的点
说白了,这就是一个区域生长的过程。我刚开始做的时候,觉得这方法太笨了。后来发现,在点云密度均匀的场景下,它比DBSCAN快得多。
4.3.2 和DBSCAN的区别
| 对比项 | DBSCAN | 欧式聚类 |
|---|---|---|
| 核心思想 | 密度连通 | 距离最近 |
| 噪声处理 | 自动识别噪声 | 需要额外判断 |
| 计算速度 | 较慢(密度计算) | 较快(KD树加速) |
| 适用场景 | 密度不均匀 | 密度均匀 |
我个人习惯:如果场景里目标大小差异很大(比如行人旁边有大货车),用DBSCAN。如果场景比较规整(比如停车场),用欧式聚类更快。
4.3.3 代码示例
import open3d as o3d
# 把numpy点云转成open3d格式
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
# 欧式聚类
# 参数:聚类阈值0.5m,最少点数10,最多点数1000
labels = pcd.cluster_dbscan(eps=0.5, min_points=10, print_progress=True)
# 注意:open3d里cluster_dbscan实际是DBSCAN实现
# 纯欧式聚类需要自己写或用PCL库
4.4 基于网格的聚类
网格聚类,思路完全不一样。它不关心单个点,而是把空间切分成小格子,然后统计每个格子里的点数。
4.4.1 怎么做?
步骤很简单:
- 把点云空间划分成固定大小的网格(比如0.2m × 0.2m)
- 统计每个网格内的点数
- 点数超过阈值的网格,标记为"有目标"
- 相邻的"有目标"网格,合并成一个簇
你想想看,这其实是在做图像化处理。把3D点云变成2D的栅格图,然后用图像连通域分析的方法做聚类。
4.4.2 优缺点
优点很明显:
- 速度快,因为只处理网格,不处理每个点
- 对噪声不敏感,个别噪点不影响网格统计
- 容易并行化
缺点也很致命:
- 网格大小难选。太小了,一个目标被切碎;太大了,多个目标粘一起
- 丢失了点云的精确位置信息
4.4.3 代码示例
import numpy as np
def grid_clustering(points, grid_size=0.5, min_points=3):
# 计算每个点所在的网格索引
grid_indices = np.floor(points[:, :2] / grid_size).astype(int)
# 统计每个网格的点数
unique_indices, counts = np.unique(grid_indices, axis=0, return_counts=True)
# 过滤掉点数少的网格
valid_grids = unique_indices[counts >= min_points]
# 这里可以用连通域分析合并相邻网格
# 简单实现:直接返回网格中心作为聚类结果
cluster_centers = valid_grids * grid_size + grid_size / 2
return cluster_centers
4.5 三种方法怎么选?
我给大家一个选择思路:
- 场景复杂、目标大小不一 → DBSCAN(灵活,但慢)
- 场景简单、点云均匀 → 欧式聚类(快,够用)
- 需要实时性、粗检测 → 网格聚类(极快,精度低)
说实话,没有银弹。我在实际项目中,经常是网格聚类 + DBSCAN组合使用。先用网格快速定位目标区域,再在区域内用DBSCAN精细聚类。这样既保证了速度,又保证了精度。
好了,聚类这块就讲到这里。下一章咱们聊聊目标跟踪,那又是另一番天地了。