3、目标检测基础:视觉2D检测(YOLO系列简介)、雷达点云聚类(DBSCAN)、检测输出格式(BBox、速度、RCS)
好,咱们正式开始聊融合感知的核心——目标检测。
这一章,我打算把视觉和雷达各自怎么“看”懂世界,给你拆开讲清楚。你想想看,一个摄像头,一个雷达,它们俩看到的原始数据完全不一样。摄像头是像素矩阵,雷达是点云。怎么从这些原始数据里提取出“车”、“人”、“自行车”?这就是目标检测要干的事。
3.1 视觉2D检测:YOLO系列简介
视觉这块,现在工业界用得最多的,就是YOLO系列。我个人习惯叫它“你只看一次”(You Only Look Once)。为什么叫这个名?因为它把检测任务当成了一个回归问题,一张图扔进去,直接输出边界框和类别,一步到位。
早期的Faster R-CNN那种两阶段方法,先提候选框,再分类回归,精度是高,但速度慢。YOLO呢?它把图像分成S×S的网格,每个网格负责预测中心点落在它里面的物体。说白了,就是“谁的地盘谁做主”。
YOLO的核心思想: 将目标检测视为一个端到端的回归问题,直接从图像像素映射到边界框坐标和类别概率。
我记得在2020年做的一个项目中,我们用的是YOLOv3。那时候v3刚出来,多尺度预测的特性让我印象很深。它用了三个不同尺度的特征图来检测不同大小的物体。大特征图检测小物体,小特征图检测大物体。这个设计很巧妙,解决了之前版本对小目标不敏感的问题。
后来YOLOv4、v5、v8一路迭代,速度越来越快,精度也越来越高。现在很多量产车上用的都是轻量化版本,比如YOLOv5s或者YOLOv8n。为什么?因为嵌入式芯片算力有限,你得在帧率和精度之间找个平衡点。
我的经验: 在实际工程中,别盲目追求最高精度的模型。YOLOv8x虽然mAP高,但推理速度慢。我建议你先用YOLOv8n跑通流程,再根据硬件资源逐步升级。很多时候,v8n配合好的后处理,效果已经够用了。
视觉检测的输出,就是2D边界框。格式一般是 [x_center, y_center, width, height] 或者 [x1, y1, x2, y2]。这个后面会细讲。
3.2 雷达点云聚类:DBSCAN
雷达这边,情况就完全不一样了。雷达返回的是点云——一堆离散的点,每个点包含距离、方位、速度(多普勒)和RCS(雷达散射截面积)。
怎么从这堆点里找出一个个目标?聚类。
常用的聚类算法有K-Means、欧式聚类、DBSCAN。我个人最推荐DBSCAN。为什么?因为它不需要预先指定聚类数量,而且能处理任意形状的簇。你想想看,一辆大卡车在雷达视野里可能是一大片点云,形状不规则。K-Means硬要把它分成圆形簇,效果肯定不好。
DBSCAN的核心是两个参数:eps(邻域半径)和 minPts(最小点数)。
- eps:决定了两个点算不算“邻居”。设得太小,一个目标可能被拆成好几块;设得太大,两个目标可能粘在一起。
- minPts:一个区域要成为核心区域,至少需要多少个点。设得太小,噪声点容易被当成目标;设得太大,小目标可能被漏掉。
我曾经在一个项目中吃过亏。当时用的是77GHz毫米波雷达,点云密度比较高。我一开始把eps设成了0.5米,结果一辆停在路边的车被分成了三四个簇。后来我改成1.2米,效果就好了很多。嗯,这里要注意:参数调优没有固定公式,得根据你的雷达特性和场景来试。
避坑指南: 我曾经在雨天测试时发现,DBSCAN把雨滴噪声也聚成了目标。后来我在聚类前加了一个RCS阈值过滤,把RCS小于-10dBsm的点直接扔掉,问题就解决了。雷达点云的预处理,比你想的重要得多。
下面是一个简单的DBSCAN聚类代码示例,我用的是scikit-learn库:
from sklearn.cluster import DBSCAN
import numpy as np
# 假设points是雷达点云,形状为(N, 3),每行是[x, y, velocity]
points = np.array([[10.2, 3.1, 5.0],
[10.5, 3.3, 4.8],
[9.8, 2.9, 5.2],
[20.1, 8.5, -2.0],
[20.4, 8.7, -1.8]])
# DBSCAN聚类
clustering = DBSCAN(eps=1.5, min_samples=3).fit(points[:, :2]) # 只用x,y做空间聚类
labels = clustering.labels_
# 输出每个点的簇标签
for i, label in enumerate(labels):
print(f"点{i}: 坐标({points[i,0]:.1f}, {points[i,1]:.1f}) -> 簇{label}")
# 注意:label=-1表示噪声点
聚类完成后,每个簇就是一个候选目标。接下来要提取这个目标的属性,比如位置(簇中心)、速度(簇内点的平均速度)、RCS(最大值或平均值)。
3.3 检测输出格式:BBox、速度、RCS
视觉和雷达各自检测完了,输出什么?格式必须统一,不然没法融合。
我习惯定义一个统一的目标结构体,包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 目标唯一ID(跟踪后赋值) |
| type | int | 目标类型:0=未知, 1=车, 2=人, 3=自行车... |
| bbox_2d | float[4] | 2D边界框 [x1, y1, x2, y2](像素坐标) |
| position | float[3] | 3D位置 [x, y, z](米,车体坐标系) |
| velocity | float[3] | 3D速度 [vx, vy, vz](米/秒) |
| rcs | float | 雷达散射截面积(dBsm) |
| confidence | float | 置信度(0~1) |
| source | int | 来源:0=视觉, 1=雷达, 2=融合 |
这里有几个细节我想强调一下:
- 速度:视觉检测本身不输出速度,得靠帧间跟踪才能算出来。但雷达直接输出多普勒速度,这是它的天然优势。所以在融合时,雷达的速度信息权重通常更高。
- RCS:这是雷达独有的特征。不同目标的RCS差异很大——卡车可能有+20dBsm,行人可能只有-5dBsm。这个值可以用来辅助分类。我做过一个实验,单纯靠RCS阈值就能把行人和车辆区分开,准确率能达到80%以上。
- 置信度:视觉的置信度来自模型输出(比如YOLO的objectness score),雷达的置信度可以基于簇内点数、点云分布方差等来设计。这个在后续的融合策略中非常关键。
一个小技巧: 在实际代码中,我建议用Python的dataclass或者C++的struct来定义这个结构体。这样后续的序列化、调试、日志打印都很方便。别用字典,字典在工程迭代中容易出bug。
好了,这一章的内容就这些。视觉检测、雷达聚类、统一输出格式——这三步是融合感知的基石。下一章,我会带你看看怎么把这些检测结果对齐到同一个时空坐标系下。嗯,那才是真正头疼的地方。