4. 障碍物感知与数据预处理:激光雷达点云滤波与聚类

大家好,我是你们的讲师。今天我们来聊聊避障算法里最“接地气”的一环——激光雷达点云处理。

说实话,很多新手一上来就调避障参数,结果发现机器人要么撞墙,要么原地转圈。为什么?因为感知层的数据就没处理好。你想想看,原始点云里全是噪声和冗余,直接拿去用,算法不崩才怪。

我个人习惯把这一章分成四个步骤:滤波 → 聚类 → 包围盒 → 输出。每一步都有坑,咱们一个一个来填。

原始点云 (.pcd / .bin) 点云滤波 直通 + 体素 点云聚类 DBSCAN 包围盒生成 AABB / OBB 图:点云预处理与障碍物感知标准流程 每一步都影响最终避障效果,缺一不可

4.1 直通滤波:先砍掉没用的区域

直通滤波,说白了就是“切一刀”。

激光雷达扫一圈,能扫到几十米外的树、楼、甚至天上的鸟。但我们的机器人只关心前方3~5米内的障碍物。那远处的点云就是干扰。

我建议你这样做:

  • X轴方向:只保留 -2m 到 +2m(左右范围)
  • Y轴方向:只保留 0.3m 到 5m(前方范围)
  • Z轴方向:只保留 -0.5m 到 1.5m(高度范围,过滤地面和头顶)

重要经验:我在项目中遇到过,Z轴下限设成0会导致地面点云被保留,聚类时地面和障碍物糊在一起。后来我改成 -0.3m 到 1.5m,效果立竿见影。

// C++ 示例:PCL 直通滤波
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(-0.3, 1.5);  // 砍掉地面和头顶
pass.filter(*cloud_filtered);

4.2 体素滤波:给点云“减肥”

原始点云有多密?16线激光雷达一帧能产生3万个点,32线直接翻倍。这么多点,CPU算不过来。

体素滤波就是“降采样”。它把空间切成一个个小立方体(体素),每个立方体里只保留一个重心点。

嗯,这里要注意:体素大小不能乱设。

  • 设太大(比如0.2m):障碍物细节丢失,小物体直接消失
  • 设太小(比如0.01m):降采样效果不明显,计算量还是大
体素大小 点云数量(约) 适用场景
0.05 m 8000 ~ 12000 高精度避障(室内)
0.10 m 3000 ~ 5000 一般导航(推荐)
0.20 m 1000 ~ 2000 快速原型验证

我的习惯:先设0.1m跑一遍,看聚类效果。如果障碍物边缘模糊,再调小到0.08m。如果机器人反应迟钝,就调大到0.12m。说白了,这是个“精度 vs 速度”的平衡。

// C++ 示例:PCL 体素滤波
pcl::VoxelGrid<pcl::PointXYZ> voxel;
voxel.setInputCloud(cloud_filtered);
voxel.setLeafSize(0.1f, 0.1f, 0.1f);  // 10cm 体素
voxel.filter(*cloud_voxel);

4.3 DBSCAN 聚类:把点云变成“障碍物”

滤波之后,我们得到了一堆稀疏的点。但这些点属于哪个障碍物?不知道。所以需要聚类。

DBSCAN 是我最常用的聚类算法。它不需要预先指定聚类数量,而且能处理任意形状的障碍物——这点比 K-means 强太多。

DBSCAN 有两个关键参数:

  • Eps(邻域半径):两个点距离小于这个值,就算“邻居”
  • MinPts(最小点数):一个区域至少要有多少个点,才算一个簇

我曾经踩过的坑:有一次在室外测试,Eps设了0.3m,结果一棵树被分成了5个簇,机器人绕着树转圈。后来我改成0.5m,树被正确识别为一个整体。但Eps太大又会把相邻障碍物合并——所以这个参数需要根据传感器安装高度和场景反复调。

// C++ 示例:PCL DBSCAN 聚类
pcl::EuclideanClusterExtraction ec;
ec.setClusterTolerance(0.5);   // Eps = 0.5m
ec.setMinClusterSize(10);      // MinPts = 10
ec.setMaxClusterSize(25000);
ec.setInputCloud(cloud_voxel);
ec.extract(cluster_indices);

你想想看,如果 MinPts 设成 5,那远处几个稀疏的噪声点也会被当成障碍物。我一般设 10~20,既能过滤噪声,又不会漏掉小障碍物。

4.4 障碍物包围盒生成

聚类完成后,每个障碍物是一堆点。但避障算法需要的是“这个障碍物有多大、在什么位置”。所以我们要生成包围盒。

常用的有两种:

  • AABB(轴对齐包围盒):计算简单,但不旋转,适合静态障碍物
  • OBB(有向包围盒):会跟着障碍物旋转,更精确,但计算量大一点

我个人习惯:动态障碍物用 OBB,静态障碍物用 AABB。因为动态障碍物(比如行人)会转身,OBB 能更好地描述它的实际轮廓。

// C++ 示例:生成 AABB 包围盒
pcl::PointXYZ min_pt, max_pt;
pcl::getMinMax3D(*cluster_cloud, min_pt, max_pt);

// 输出障碍物信息
std::cout << "障碍物中心: (" 
          << (min_pt.x + max_pt.x)/2 << ", "
          << (min_pt.y + max_pt.y)/2 << ")"
          << " 尺寸: " 
          << max_pt.x - min_pt.x << " x "
          << max_pt.y - min_pt.y << std::endl;

关键提醒:包围盒的尺寸一定要加上机器人自身的“膨胀半径”。比如机器人宽0.6m,那障碍物的包围盒每边至少膨胀0.3m。否则机器人会“擦肩而过”,然后撞上。

4.5 本章小结

好了,这一章的内容就这些。总结一下核心要点:

  1. 直通滤波:砍掉无用区域,减少计算量
  2. 体素滤波:降采样,平衡精度和速度
  3. DBSCAN 聚类:把点云变成有意义的障碍物
  4. 包围盒生成:输出障碍物的位置和尺寸

说实话,这一章的内容看起来简单,但我在实际项目中反复调参的次数最多。尤其是 Eps 和体素大小,不同场景(室内/室外、白天/夜晚)差异很大。我的建议是:先跑通流程,再优化参数。不要一开始就追求完美,否则你会被参数调疯的。

下一章我们会讲怎么把这些障碍物信息传给避障算法。但今天先把感知层练熟,这是所有避障的基础。

可可精品资料库