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 算法流程

说白了就三步:

  1. 遍历每个点,看它半径eps内有多少个点
  2. 如果邻居数 ≥ minPts,标记为核心点,开始扩张
  3. 核心点周围的点,只要在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
避坑指南:我曾经在高速场景用DBSCAN,eps设了0.8m。结果一辆车上的点被分成了三四个簇。后来发现,车速快的时候,点云间距会拉大。建议根据车速动态调整eps。

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库
小技巧:欧式聚类前,建议先做一下体素滤波降采样。点太多的话,KD树搜索会很慢。我一般降到每平方米50个点左右,速度能快5倍以上。

4.4 基于网格的聚类

网格聚类,思路完全不一样。它不关心单个点,而是把空间切分成小格子,然后统计每个格子里的点数。

4.4.1 怎么做?

步骤很简单:

  1. 把点云空间划分成固定大小的网格(比如0.2m × 0.2m)
  2. 统计每个网格内的点数
  3. 点数超过阈值的网格,标记为"有目标"
  4. 相邻的"有目标"网格,合并成一个簇

你想想看,这其实是在做图像化处理。把3D点云变成2D的栅格图,然后用图像连通域分析的方法做聚类。

4.4.2 优缺点

优点很明显:

  • 速度快,因为只处理网格,不处理每个点
  • 对噪声不敏感,个别噪点不影响网格统计
  • 容易并行化

缺点也很致命:

  • 网格大小难选。太小了,一个目标被切碎;太大了,多个目标粘一起
  • 丢失了点云的精确位置信息
我的经验:网格聚类最适合做粗检测。比如先快速找到哪些区域可能有目标,然后再用DBSCAN做精细聚类。我在做路侧雷达时,就用网格聚类做第一遍过滤,速度能到100Hz以上。

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精细聚类。这样既保证了速度,又保证了精度。

最后提醒一句:聚类参数一定要根据传感器特性和场景动态调整。我曾经在雨天吃过亏,雨滴产生的噪点让DBSCAN把整个路面都聚成了一类。后来加了速度滤波,才把问题解决。

好了,聚类这块就讲到这里。下一章咱们聊聊目标跟踪,那又是另一番天地了。