4. 障碍物检测与跟踪:点云聚类、目标检测算法、卡尔曼滤波跟踪、匈牙利匹配
各位同学,欢迎来到第四讲。今天聊的话题,是自动驾驶感知系统里最“硬核”的一环——障碍物检测与跟踪。
你想想看,车在路上跑,周围有车、有人、有自行车,还有各种奇奇怪怪的障碍物。系统必须知道“那里有个东西”、“它是什么”、“它接下来要去哪”。这三个问题,就是我们今天要解决的。
我个人习惯把这一整套流程叫做“感知-跟踪-预测”的闭环。说白了,就是先找到障碍物,然后盯住它,最后猜它下一步想干嘛。今天我们先讲前两步。
4.1 点云聚类:从海量点中找出“物体”
激光雷达扫一圈,回来几十万个点。这些点密密麻麻,但系统不知道哪些点属于同一辆车。所以第一步,就是做聚类。
我常用的方法是基于距离的聚类,比如DBSCAN。它的核心思想很简单:如果两个点挨得够近,就认为它们属于同一个物体。
嗯,这里要注意一个参数——邻域半径。设得太小,一辆大卡车会被切成好几块;设得太大,旁边的人行道和行人就粘在一起了。我在项目中遇到过这个问题,当时调试了好久,最后发现要根据激光雷达的线束密度动态调整半径。
给你们看一段伪代码,理解一下思路:
// 点云聚类伪代码
function clusterPointCloud(points, radius, minPoints):
clusters = []
visited = set()
for each point in points:
if point in visited:
continue
neighbors = findNeighbors(point, points, radius)
if len(neighbors) < minPoints:
markAsNoise(point)
continue
// 核心点,开始生长
newCluster = expandCluster(point, neighbors, radius, minPoints)
clusters.append(newCluster)
visited.update(newCluster)
return clusters
除了DBSCAN,还有欧几里得聚类、基于体素的聚类。我个人偏爱DBSCAN,因为它不需要预先知道有多少个物体,而且能处理任意形状的障碍物。
4.2 目标检测算法:给障碍物“贴标签”
聚类之后,我们得到了一堆“点云团”。但系统还不知道这些团是什么。是车?是行人?还是路边的垃圾桶?
这就轮到目标检测算法上场了。在自动驾驶领域,主流方案分两派:
- 基于视觉的检测: 用摄像头图像,跑深度学习模型,比如YOLO、Faster R-CNN。优点是信息丰富,能识别颜色、纹理;缺点是受光照影响大,没有深度信息。
- 基于激光雷达的检测: 直接用点云数据,跑PointNet、VoxelNet这类模型。优点是三维信息准确,不受光照影响;缺点是点云稀疏,远距离识别能力弱。
我建议的做法是——融合。用激光雷达做定位和测距,用摄像头做分类和识别。两者互补,效果最好。
举个例子,我曾经在一个项目中,只用激光雷达检测行人,结果经常把路边的消防栓误检成人。后来加了摄像头图像做二次验证,误检率直接降了80%。
4.3 卡尔曼滤波跟踪:让障碍物“动起来”
检测到障碍物之后,下一帧它可能移动了位置。我们需要一个方法,来估计它每一时刻的状态(位置、速度、加速度)。
卡尔曼滤波,就是干这个的。它像一个“智能平滑器”,能把有噪声的观测数据,变成平滑的运动轨迹。
它的核心思想就两步:
- 预测: 根据上一帧的状态,猜这一帧它应该在哪。
- 更新: 用这一帧的观测数据,修正刚才的猜测。
我给你们画个简单的状态方程:
// 卡尔曼滤波状态方程
// 状态向量: [x, y, vx, vy] 位置和速度
// 预测步骤:
x_pred = F * x_prev + B * u
P_pred = F * P_prev * F^T + Q
// 更新步骤:
K = P_pred * H^T * (H * P_pred * H^T + R)^(-1)
x_upd = x_pred + K * (z - H * x_pred)
P_upd = (I - K * H) * P_pred
这里面的Q和R,是调参的关键。Q代表你对运动模型的信任程度,R代表你对传感器观测的信任程度。我曾经在一个项目中,因为Q设得太大,导致跟踪轨迹一直在抖动;后来把Q调小,轨迹就平滑多了。
4.4 匈牙利匹配:解决“谁是谁”的问题
现在,我们有了上一帧的跟踪列表,和这一帧的检测列表。问题来了:这一帧检测到的障碍物,对应上一帧的哪一个?
这就是数据关联问题。匈牙利匹配算法,就是用来解决这个“一对一匹配”问题的。
它的思路很巧妙:
- 计算每个检测框和每个跟踪框的“代价”,比如位置距离、IOU(交并比)等。
- 然后找一个全局最优的匹配方案,使得总代价最小。
我给你们一个简单的匹配流程:
// 匈牙利匹配伪代码
function hungarianMatch(detections, tracks):
// 构建代价矩阵
costMatrix = []
for each detection in detections:
row = []
for each track in tracks:
cost = computeIOU(detection, track) // 或者马氏距离
row.append(cost)
costMatrix.append(row)
// 执行匈牙利算法,找到最小代价匹配
matches = hungarianAlgorithm(costMatrix)
// 处理未匹配的检测(新出现的障碍物)
// 处理未匹配的跟踪(消失的障碍物)
return matches
这里有个坑,我踩过。如果两辆车并排行驶,它们的IOU可能很大,匈牙利匹配容易把ID搞混。后来我加了一个“马氏距离”的约束,结合运动方向,才把这个问题解决。
4.5 总结与思考
好了,今天的内容就到这里。我们走了一遍完整的流程:
- 点云聚类:从无序点云中找出物体。
- 目标检测:给物体贴上类别标签。
- 卡尔曼滤波:让物体“动起来”,平滑跟踪。
- 匈牙利匹配:解决帧与帧之间的“谁是谁”问题。
这四个模块环环相扣,缺一不可。你想想看,如果聚类没做好,后面检测就会漏掉目标;如果跟踪没做好,规划层就没法预测障碍物的未来轨迹。
下一讲,我们会深入讲一下“运动预测与行为意图估计”,也就是怎么猜障碍物接下来要干什么。到时候,今天讲的跟踪结果就是输入。
好,今天就到这里。有问题随时问我。