4. 目标检测基础 (Camera)
各位同学,欢迎来到第四讲。
前面我们聊了传感器标定和时间同步,算是把感知系统的「骨架」搭好了。今天开始,我们要往骨架里填「血肉」——目标检测。
说白了,目标检测就是让车「看见」并「认出」周围的东西。摄像头是感知系统里信息最丰富的传感器,没有之一。我个人习惯把摄像头检测分成两派:2D检测和3D检测。2D检测告诉你「那里有辆车」,3D检测告诉你「那辆车在哪儿、有多大、朝哪开」。
嗯,咱们一个一个来。
4.1 2D目标检测:YOLO系列
2D检测,就是在一张图片上画出框,标出类别。YOLO系列是工业界最流行的方案,没有之一。为什么?因为它快,而且足够准。
YOLO的核心思想:把检测当成回归问题。一张图分成S×S个网格,每个网格负责预测中心点落在它里面的物体。每个网格预测B个框,每个框包含5个值:x, y, w, h, confidence。再加上C个类别的概率。
我刚开始接触YOLOv3时,觉得这设计太粗暴了。但后来在项目里跑起来,发现这种「端到端」的设计,部署起来真省心。你想想看,不需要像Faster R-CNN那样搞两阶段,一个网络搞定所有。
YOLOv3的关键改进:
- 多尺度预测(3个尺度,分别检测大、中、小物体)
- 用Darknet-53做骨干网络(比ResNet-152更快,精度相当)
- 用逻辑回归替代softmax做分类(支持多标签)
到了YOLOv5,其实已经不是原作者的作品了,但社区把它发扬光大了。YOLOv5在工程上做了很多优化:自动锚框计算、Mosaic数据增强、CSP结构。我在实际项目中,YOLOv5s在Jetson Orin上能跑到30FPS以上,mAP也有40+,够用了。
我的经验:YOLOv5的Mosaic增强对小物体检测提升明显。但要注意,训练后期最好关掉Mosaic,否则模型容易过拟合到拼接痕迹上。我曾经因为这个踩过坑,后来加了余弦退火+逐步关闭Mosaic,效果好了不少。
YOLOv8是Ultralytics的最新作品,把anchor-free和anchor-based统一了。我个人觉得,YOLOv8最大的贡献是那个统一的训练框架,换模型跟换衣服一样简单。
4.2 3D目标检测:FCOS3D与SMOKE
2D检测只能告诉你「有什么」,但自动驾驶需要知道「在哪儿」。3D检测要输出物体的长宽高、中心点坐标、朝向角。这里我重点讲两个基于单目相机的方案:FCOS3D和SMOKE。
为什么是单目? 因为便宜。激光雷达太贵了,纯视觉方案是降本的关键。但单目3D检测有个天然难题:深度估计不准。你想想看,一张2D图片丢失了深度信息,要恢复3D位置,本质上是个病态问题。
4.2.1 FCOS3D
FCOS3D是FCOS的3D版本。FCOS本身是anchor-free的2D检测器,FCOS3D在它的基础上,额外预测了3D信息。
它的输出头包括:
- 2D中心点偏移 + 2D框尺寸
- 3D中心点投影偏移(相对于2D中心)
- 深度(直接回归,或用分类+偏移的方式)
- 3D尺寸(长宽高)
- 朝向角(用MultiBin方式,分成多个角度区间)
核心技巧:FCOS3D用「中心度」来抑制低质量检测。这个中心度不是2D的,而是3D中心点在2D投影上的偏移程度。我在项目里发现,加上这个中心度后,误检率降低了约15%。
FCOS3D在nuScenes数据集上表现不错,但有个问题:它对遮挡物体的深度估计很差。为什么会这样?因为遮挡物体的可见像素太少,深度线索不足。
4.2.2 SMOKE
SMOKE走的是另一条路:它直接预测3D框的8个角点投影到2D图像上的位置,然后通过PnP算法解算出3D信息。
SMOKE的结构更简洁:
- 一个Keypoint分支:预测物体中心点的2D位置
- 一个Regression分支:预测3D尺寸、朝向角、以及中心点深度偏移
我个人更喜欢SMOKE的设计,因为它把3D检测拆成了「2D关键点 + 3D属性回归」,逻辑更清晰。但SMOKE对2D中心点的检测精度要求很高——中心点偏了,整个3D框就歪了。
避坑指南:我曾经在雨雾天气测试SMOKE,发现检测框疯狂抖动。后来排查发现,是2D中心点检测不稳定导致的。解决方案是加了时序滤波,用卡尔曼滤波平滑中心点位置。嗯,这个后面讲跟踪时会细说。
4.3 检测输出格式:BBox/类别/置信度
不管用哪种检测器,最终输出格式都是统一的。这是感知融合的基础——如果输出格式都不统一,后面的融合模块就没法写。
标准的检测输出格式如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| class_id | int | 类别ID(0: car, 1: pedestrian, 2: cyclist...) |
| confidence | float | 置信度(0~1) |
| bbox_2d | [x1, y1, x2, y2] | 2D框左上角和右下角坐标(像素) |
| bbox_3d | [x, y, z, w, h, l, yaw] | 3D中心点坐标、尺寸、朝向角(米/弧度) |
| velocity | [vx, vy] | 速度(可选,部分检测器输出) |
在实际工程中,我习惯用结构体来封装:
struct Detection {
int class_id;
float confidence;
std::vector<float> bbox_2d; // [x1, y1, x2, y2]
std::vector<float> bbox_3d; // [x, y, z, w, h, l, yaw]
float velocity[2]; // [vx, vy]
uint64_t timestamp_ms; // 时间戳,用于跟踪
};
注意:3D框的朝向角yaw定义要统一。有的用弧度,有的用角度;有的定义在全局坐标系,有的定义在车身坐标系。我在项目里吃过这个亏——融合模块和检测模块的yaw定义差了90度,结果跟踪框一直在转圈。后来我强制要求所有模块用弧度制,且yaw=0表示车头朝正前方。
还有一个容易被忽略的点:置信度阈值怎么设?设高了漏检,设低了误检。我一般用两个阈值:一个低阈值(比如0.1)用于保留候选框,一个高阈值(比如0.5)用于最终输出。中间的那些框,留给跟踪模块去判断。
4.4 本章知识体系
下面这张图总结了本章的核心逻辑:
好了,这一章的内容就是这些。2D检测是基础,3D检测是进阶,统一输出格式是工程落地的关键。下一章我们会聊激光雷达的目标检测,到时候你会发现,激光雷达的检测思路和摄像头完全不同。
可可精品资料库