多传感融合学习笔记写到第五篇,终于轮到图像目标识别这个关键环节。前面的笔记大多在聊传感器模型、坐标变换、点云数据结构,摄像头这块一直没正式展开。原因很简单:图像原始数据如果不先转换成“哪里有什么目标”的结构化信息,摄像头在多传感融合系统里基本等于摆设。这篇笔记的核心就这么一件事,利用YOLO完成图像目标识别,把每一帧画面里的车、人、自行车、红绿灯都框出来。做完这一步,后面才能拿检测结果去和激光雷达点云、毫米波雷达测距数据对齐,做真正的多传感器融合。
这篇笔记适合两类人。一类是刚开始接触多传感融合、想知道YOLO在整条感知链路里到底站在什么位置的;另一类是已经能用YOLO跑出检测框,但不知道2D框怎么和3D点云结合起来的。我会把原理、实操、融合接口和排坑经验一起串起来讲,尽量让不同基础的读者都能跟得上。
1. 融合框架里为什么必须先搞定图像识别
1.1 把图像变成目标列表:摄像头在融合里负责什么
先说清楚一个前提:多传感融合不是把传感器数据随意拼在一起,而是让不同传感器互补短板。典型配置是三件套:摄像头、激光雷达、毫米波雷达。摄像头信息稠密、有颜色和纹理,但缺少深度,受光照影响大;激光雷达能给出精确的3D坐标点云,但点云本质上是稀疏的坐标集合,没有语义信息,机器不知道这一堆点是一个人还是一棵树;毫米波雷达有速度和距离信息,但分辨率低,很难单独区分相邻目标。
在这种配置下,融合的第一级任务往往是让摄像头先“看明白”。只有图像能把物体类别讲清楚。所以YOLO要解决的核心问题就是:把一帧图像变成一条结构化的目标列表,列表里每一项包含目标类别、2D边界框位置和置信度。有了这个列表,激光雷达点云才有对象去对应,融合后端才知道哪些点云属于同一个目标,进一步估计目标相对自车的距离和速度。
这也是整个感知链路的第一步。摄像头视频流进来后,先做目标识别,然后YOLO输出的2D框作为“先验区域”,引导后续点云处理。如果这一步漏检或误检,后面所有融合环节都会被带偏。
1.2 实时性考量:为什么课程选YOLO而不是两阶段检测器
入门目标检测时,最先接触的往往是两阶段检测器,比如RCNN系列。这类模型的思路是先借助区域提议网络找出可能包含目标的候选框,再对每个候选框做分类和位置回归。这样做精度确实高,但速度慢得离谱,一帧图像在普通GPU上也要几百毫秒起步,完全没法满足车载场景对实时性的要求。
YOLO走的是另一条路。它把目标检测当成一个回归问题来处理,一次前向推理直接把边界框坐标、目标类别、置信度全部输出。少了区域提议这个大环节,速度优势非常明显。在GPU上对一段视频做推理,YOLO通常能把每帧处理时间压到几十毫秒级别,这才让“摄像头每秒处理30帧、实时形成目标列表”成为可能。
课程选YOLO还有一个很现实的原因:它的开源生态成熟、预训练权重丰富。无论是官方提供的COCO预训练模型,还是社区里一堆现成的推理脚本,拿过来就能用。做多传感融合项目的人,模型只是一个组件,不值得花大量时间从底层训练一个检测器。把成熟轮子用起来,把精力放在融合链路本身,才是正确的投入方向。
1.3 YOLO在感知架构中的定位
我在这个模块里跑通的感知流程大概是这样的:
- 摄像头视频流按帧送入YOLO,得到2D目标框列表。
- 激光雷达点云通过联合标定的外参矩阵投影到图像平面。
- 投影后落在某个检测框区域内的点云被裁剪出来,作为该目标的ROI点云。
- 对ROI点云做聚类和测距,拿到这个目标相对自车的距离、尺寸、运动状态。
- 把目标级数据交给后续的跟踪和决策模块。
从这个流程能看出,YOLO输出的2D检测框在这里不完全是一个终点结果,它更像是一个“空间索引”。更直白地说,融合系统利用2D框把全量点云切成若干有语义标签的局部点云块,从而把“识别目标”和“测量目标”两件事绑定在一起。这是YOLO在多传感融合里最核心的用途。
2. YOLO检测原理与输出张量的正确打开方式
2.1 一次前向推理直接给框:单阶段思想
YOLO的核心思想可以用一句话概括:把检测任务当成回归问题,整张图像只看一次,直接得到所有目标的位置和类别。
具体做法是把输入图像划分成S×S的网格。每个网格负责预测那些中心点落在该网格内的目标。每个网格会产生B个候选边界框,每个候选框包含五个量:中心坐标x、中心坐标y、宽度w、高度h,以及置信度confidence。同时每个网格还会预测C个类别的概率分布,表示如果这个网格内有目标,它属于各类别的可能性。
置信度这个量初学者容易混淆。它由两部分组成:一是这个网格里有没有目标,二是预测框和真实框的重合程度。数学上写作Pr(Object) × IoU(pred, truth)。也就是说,置信度既要表达“这里有没有东西”,又要表达“框得准不准”。最终判断一个候选框是不是真目标时,还要把类别概率和置信度乘起来,得到一个综合得分,再拿这个得分去和阈值比较。
速度快的根源就在这里。两阶段检测器要把“找出候选区域”和“识别区域”拆成两步,YOLO把这两件事压缩进同一个网络,并且一次性输出所有目标的坐标和类别。代价也有:在极密集、极小目标的场景下,YOLO的精度会略逊于精心调优的两阶段模型。但在实时融合系统里,这个取舍是合理的。
2.2 网格、锚框和多尺度输出:张量形状怎么读
以YOLOv3的模型结构为例,它是最容易帮助理解多尺度检测的版本。输入一张416×416的图像,网络输出不是单一的结果,而是三个不同尺度的特征图,分别对应大目标、中目标和小目标。
| 检测尺度 | 特征图尺寸 | 感受野特点 | 主要负责的目标 |
|---|---|---|---|
| 大目标尺度 | 13×13 | 感受野大,全局信息充分 | 车、公交车等大面积目标 |
| 中目标尺度 | 26×26 | 感受野中等 | 行人、骑行者等常规目标 |
| 小目标尺度 | 52×52 | 边缘细节保留多 | 远处的行人、小物体 |
多尺度输出的意义在于,浅层特征图保留了更多空间细节,适合锁定小目标;深层特征图经过更多卷积和池化,语义信息更丰富,适合判断大目标。让模型同时在多个尺度上做检测,能显著缓解小目标漏检的问题。
锚框(anchor box)是另一个初学者容易懵的概念。简单理解,锚框是预先设定的一组边界框尺寸先验。YOLO并不直接预测目标的绝对宽高,而是预测“相对某个锚框的偏移量”。这么做相当于给回归任务一个初始参考点,训练更容易收敛。通常锚框尺寸由聚类算法在训练集上统计得到,不同数据集、不同目标比例下,锚框设置会不一样。
如果你看到输出张量形状是(batch, 255, 13, 13),其中255代表3×(5+类别数),这里3就是每个网格位置预测的锚框数量,5是x、y、w、h和置信度。解码时要把这个张量拆解成若干个候选框,再经过筛选才能得到最终检测框。理解这个解码过程,比背一个模型结构图要有用得多。
2.3 置信度阈值和NMS怎么设置
模型输出的候选框数量非常多,大部分都是低置信度或者重叠的框。后处理阶段通常做两步操作。
第一步,按综合得分过滤。所有得分低于置信度阈值的候选框直接丢弃。阈值调得高,误检少但漏检多;阈值调得低,漏检少但画面会很乱。以我的实测经验,在通用场景下置信度阈值设在0.4左右比较合适。跑官方COCO权重、画面比较干净时,0.35到0.5之间都合理,具体要看应用对漏检和误检的容忍度。
第二步,对剩余候选框做非极大值抑制(NMS)。思路是如果两个框都框住了同一个目标,且交并比(IoU)超过阈值,就保留得分更高的那个,抑制另一个。NMS的IoU阈值如果设得偏大,比如0.7,两个相近的框容易被合并成一个,相邻的不同目标可能被误认为同一个;设得偏小,同一个目标周围可能出现多个重复框。我用下来比较稳定的区间是0.4到0.5,密集行人场景可以进一步下调到0.3左右。
边界情况也要注意:如果画面里目标本来就非常密集,比如行人成群走过,NMS会特别纠结。这时候与其反复调阈值,不如从训练数据层面增加密集场景样本,让模型自己学会区分相邻目标。
3. 从2D框到3D点云:融合的关键接口
3.1 坐标标定:像素坐标是怎么算出来的
图像目标检测得到的是像素平面上的2D框,点云是3D空间中的坐标点,把两者对应起来靠的是传感器标定。整个标定链路的公式大致可以写成:
z_c × [u, v, 1]^T = K × [R | t] × [x_lidar, y_lidar, z_lidar, 1]^T
这里[u, v]是像素坐标,K是相机内参矩阵,它描述焦距、主点等相机自身属性。[R | t]是外参矩阵,描述雷达坐标系相对于相机坐标系的旋转和平移。标定的本质,就是求出这一组旋转和平移参数。
实际操作中,一般会使用标定板,在相机画面里提取角点,在雷达点云里找到对应的3D特征,构造多组对应点对后求解PnP问题。整个过程说复杂也复杂,说简单也简单,最怕的是“标完一次就扔着不管”。融合系统里设备支架稍微动过几毫米、相机换过镜头、车体重新装配过,外参都可能偏移,投影结果就会歪掉。
这个公式也解释了为什么融合实验里“投影错位”是高频问题。很多时候不是YOLO检测的问题,而是K矩阵和[R | t]矩阵本身没对齐好。所以做融合项目,一开始就把标定流程规范化,远比临时抱佛脚调检测参数要重要。
3.2 检测框变成点云ROI:算力和噪声一起降
YOLO检测出2D框之后,下一步通常是把2D框扩展到三维空间,形成一个视锥区域,也就是从相机光心出发,经过2D框四个角点向外延伸的四棱锥体。激光雷达点云中只有落在这个视锥范围内的点,才被保留下来作为这个目标的候选点云。
这个操作的价值是双重的。第一,计算量大幅下降。全量点云可能有几十万个点,对每个点做聚类分析成本很高;裁剪成若干ROI后,只需要在局部点云上做处理,实时性压力小多了。第二,噪声减少。远处杂散的反射点、地面点,往往因为不在检测框投影范围内而被过滤掉,点云质量更干净。
实际实现时并不一定要严格构造三维锥体。更常见的做法是这么几步:
- 拿到检测框四个角的像素坐标。
- 把雷达点云通过标定矩阵投影到图像平面,得到每个点对应的像素位置。
- 判断这个像素位置是否落在检测框内,是则保留,否则丢弃。
- 对保留的ROI点云做聚类,用聚类结果计算目标距离、尺寸等物理量。
这个流程在课程里被我简称为“先框后聚”。它有个天然优势:因为2D框提供了目标的语义身份,ROI点云天然携带“这是行人还是车辆”的标签,后续不用再做物体分类,省掉一个分类模型。
如果在某帧里发现某个检测框内点云特别少,先别急着怪天气或传感器故障。有可能是检测框太紧,把目标的真实边缘切成两半。也有可能是时间对齐出问题,导致点云和图像不是同一时刻的数据。这两个因素在融合实验里都很常见。
3.3 时间同步:30帧相机和10Hz雷达怎么对齐
传感器融合还有一个工程上绕不开的问题:时间同步。摄像头通常工作在30FPS,激光雷达常见工作频率是10Hz,两路数据传输到处理器的时间也不一样。如果直接把“最近一帧图像”和“最近一帧点云”强行配对,运动中的目标会出现框和点错位。
解决思路是给每帧数据打上时间戳,各自维护一个缓存队列。图像帧到达时,去点云队列里找时间戳最接近的那一帧点云;如果两者时间差超过预设阈值,比如50毫秒,就宁可丢弃这次配对,等下一帧,也不要拿陈旧数据硬算。对于高速运动目标,这种方式能明显减少框和点云之间的错位。
这个细节看起来不起眼,但我在实验里吃过亏。最初没做时间戳匹配,只是简单用“最新接收到的雷达数据”,结果自车前方目标稍有移动,ROI就偏出去一两米。后来改成时间戳对齐,效果立刻正常了。工程问题有时候就是这么朴实,解决方案不玄学,但确实影响结果。
4. YOLO实操过程记录:环境、命令、参数与现场效果
4.1 环境准备与模型选型
我是按YOLOv5的代码仓库来做实验的,原因很简单:文档全、依赖安装简单、训练推理一条命令搞定。环境搭建步骤如下:
conda create -n yolo python=3.8 conda activate yolo pip install torch torchvision pip install opencv-python git clone 官方YOLOv5仓库 cd yolov5 pip install -r requirements.txt如果机器有NVIDIA显卡,建议装对应版本的CUDA版PyTorch,CPU跑推理也能出结果,但视频实时性很难保证。我最初在纯CPU环境跑,一帧要几百毫秒,基本没法用。换到GPU环境后,速度提升一个量级。所以环境准备的首个建议是:确认你的机器能调用GPU,再开始玩目标检测。
模型选型方面,YOLOv5系列给了s、m、l、x等尺寸选项,对应不同的模型深度和宽度。我在课程实验里默认用yolov5s,优先保证跑得动、跑得快。几个型号对比大致是这样:
| 模型 | 精度 | 推理速度 | 显存占用 | 适用场景 |
|---|---|---|---|---|
| yolov5s | 适中 | 最快 | 最低 | 实车实验、入门复现 |
| yolov5m | 较好 | 较快 | 中等 | 一般实验项目 |
| yolov5l | 好 | 慢 | 较高 | 离线分析、高精度需求 |
| yolov5x | 最好 | 最慢 | 很高 | 离线处理、服务器 |
选型原则很朴素:能用小模型解决问题就不上大模型。检测精度不足时,先尝试提高输入分辨率、增加训练数据,最后再考虑增大模型体积。盲目上大模型,显存先撑不住,帧率也掉得厉害。
4.2 推理命令与关键参数
用官方预训练权重做推理,一条命令就够了:
python detect.py \ --source road.mp4 \ --weights yolov5s.pt \ --conf-thres 0.4 \ --iou-thres 0.45 \ --img 640这里每个参数都有讲究。--source可以是视频文件、图片目录、单张图片或者摄像头ID;--weights指定模型权重文件;--conf-thres是置信度阈值;--iou-thres是NMS的IoU阈值;--img是输入图像尺寸。运行完之后,检测结果视频会输出到runs/detect/xxx目录下。
第一次跑的时候我直接用了默认参数,图上全是低置信度的框,远处自行车、墙上的海报、路牌都被勾了出来,画面乱成一团。把--conf-thres从0.25调到0.4之后,结果干净许多。这个调整不是玄学,它就是在漏检率和误检率之间做权衡。想要暴力一点就设低阈值,保证所有疑似目标都被框出来;想要画面干净、直接拿去做融合任务,就适当调高。
还有一个容易忽视的点:--img分辨率。同样一个目标,在640分辨率下可能只有十几个像素,检测器根本没法识别;把它喂到1280分辨率下,小目标就会清晰很多。代价是推理时间变长、显存占用变大。实用建议是,先按视频内容尝试两档分辨率,观察小目标漏检有没有明显改善,再决定要不要长期使用高分辨率。
4.3 自定义数据集微调流程
课程实验的最后阶段需要在自己的数据集上做微调。整个流程可以概括为:采集或整理图像、标注、配置数据集描述文件、启动训练。
标注格式用的是YOLO格式,每个标注文件是一行一行的文本,每行包含类别编号、归一化后的中心点x坐标、中心点y坐标、宽度w、高度h。比如一张图像对应一个txt文件,内容是“0 0.5 0.4 0.2 0.3”这种格式。目录结构通常这样组织:
data/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml里写明路径、类别数量和类别名称。配置正确之后,一句命令就能开始训练:
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100这里必须说一个容易犯的错误:不要从零开始训练,一定要用官方预训练权重作为起点。目标检测模型的骨干网络部分(用于提取特征)在大规模数据集上已经学得很好了,微调阶段只需要让它适应你的数据分布。从零训练既慢又容易不收敛,完全没必要。
训练时要关注的指标不仅有最终的mAP,还有训练曲线是否平稳。如果loss曲线震荡得厉害,优先检查学习率是否过大、batch size是否太小、数据标注是否一致。我见过不少新手数据漏标了一堆目标,训练过程看起来在跑,其实模型学的全是噪声。
4.4 实测效果观察
我把一段白天城市道路视频喂给模型做实测。车辆检测很稳定,普通轿车、SUV、大客车都能框上;行人检测整体不错,但远处小目标和被部分遮挡的行人会偶尔漏检;骑行者在某些帧会被误判成行人,因为轮廓确实有点接近。推理速度方面,GPU环境下每帧大约40毫秒左右,做融合实验够用。
换到夜间视频后,问题立刻就暴露了:行人漏检明显增加,低照度环境下图像对比度不足,很多目标在图像里灰蒙蒙一片,检测器识别不出来。这个结果不是YOLO的缺陷,而是摄像头传感器的物理限制。这也顺带证明了融合系统的必要性:当摄像头在夜间“看不见”时,激光雷达和毫米波雷达依然能提供有效数据,补上检测链路的空缺。多传感融合不是做不做的问题,而是必须做的问题。
5. 排坑实录:我从这个模块踩过的坑
5.1 小目标漏检的连锁反应
小目标漏检是目标检测的老大难。在融合场景里,这个问题会被放大:YOLO没框出来,后面的点云ROI裁剪根本不会发生,目标直接就从整个感知系统里消失了。
排查思路从几个方向入手。先看输入分辨率,如果检测时用的--img是640甚至更低,远处的车辆可能只有十几个像素,换到1280分辨率会有明显改善。再看置信度阈值,有些目标其实被检测出来了,只是得分略低于阈值而被丢弃,适当调低阈值就能捞回来。最后看训练数据,模型如果没见过大量小目标样本,推理时自然对它们不敏感,这种就得靠数据增强和收集小目标样本解决。
在融合层面也有一个补救办法:对没有检测框、但点云中有明显聚类特征的空间区域做“无标签检测”,再用雷达数据反推目标的存在。但这只能作为辅助手段,不能替代检测器。目标级融合最终需要语义标签,能靠检测器解决的问题,最好在检测器层面解决。
5.2 重叠目标的NMS问题
两个行人并肩走时,模型经常只输出一个框,或者两个框来回跳。这个问题的根源通常在NMS的IoU阈值上。阈值设得偏大时,两个重叠度高的目标框会被当成同一个目标,互相抑制,只剩一个;阈值偏小,相同目标周围又会有多个重复框。
针对行人这类细长目标,我习惯把NMS的IoU阈值调低一些,比如0.3到0.4。另一个思路是在训练时增加重叠目标的样本占比,让模型自己学会区分相近目标。运行时的后处理参数能解决一部分问题,但真正靠谱的方案还是让模型在大量密集场景上见过足够多样的情况。
5.3 显存不足与推理延迟
课程实验用的机器显存有限,最初想直接跑yolov5x模型,结果前向推理刚跑起来就显存溢出,程序直接崩掉。换成yolov5s之后,问题立刻消失,显存占用大概在2G到3G之间,推理速度也回到了实时范围。
如果你的机器显存紧张,几个立竿见影的调整方式:
- 模型尺寸从x或l降到s或m,显存成倍下降。
- 推理时增加--half参数,开启半精度浮点计算,显存占用进一步降低。
- batch size在推理场景下保持为1即可,没必要追求更大。
- 关闭可视化窗口和视频流实时渲染,减少额外的GPU开销。
显存告急时优先跑通功能,后续再考虑用模型压缩或推理加速框架来提升性能。不要一开始就在最重的模型上卡住,先把整个融合链路跑顺,比单模块堆精度重要得多。
5.4 融合投影整体错位
这个坑在我实验里耗的时间最长。单看YOLO检测结果没有任何问题,边界框框得很准;单看点云也没有问题,激光雷达数据正常。可一旦把点云投影到检测框里,框是正的,点却整体偏了半米。这就是融合链路里典型的标定或同步问题。
排查顺序有讲究。先做静态检查:单独投影几帧点云到图像上,观察点云轮廓和实际物体的边缘是否对齐。如果整体偏了,优先怀疑外参矩阵,检查旋转矩阵和平移向量是否有符号或方向错误。再查时间戳:图像和点云是不是同一时刻的数据。最后检查相机内参:镜头畸变校正参数如果缺失,远端目标投影会明显偏移。
我最后定位到的问题是外参矩阵中的平移向量符号写反了。排查过程很磨人,但教训很清晰:融合链路里一旦出现“单模块都好、合起来不对”的情况,八成在接口层,不是算法层。先把输入数据的坐标系、时间戳、矩阵维度这些基础项核对干净,再回头怀疑算法。
5.5 检测置信度始终偏低
有时候模型能框出正确目标,但得分始终只在0.3到0.4徘徊,上不去。这个问题的根子通常在训练数据,而非网络结构。目标类别在数据集中出现频次太低,模型对它的特征学习不充分;或者目标经常处于遮挡状态,完整特征没机会暴露;或者训练集和推理场景之间的光照、视角差异太大。
解决办法有几个方向。数据层面,扩充困难样本,对低置信度目标做过采样,用图像增强模拟更多遮挡和光照变化。训练层面,可以适当调整类别损失权重,让模型在训练时更关注弱势类别。运行层面,把置信度阈值调低,把这类目标先捞出来再说,代价是误检率上升。
如果模型在验证集上表现正常,一到实际场景就掉点,那不是参数问题,是数据分布问题。重新采集场景数据、重新标注,远比在推理脚本里折腾阈值更有效。
这个模块学完之后,我最直观的感受是:YOLO本身已经是很成熟的工具,真正难的是把它和整个传感器系统缝合起来。我在实验里花时间最多的不是改网络结构,而是调置信度阈值、对时间戳、检查外参矩阵。做出一个能跑的检测脚本很容易,做出一个能和点云稳定配对的检测模块,需要的是对传感器接口细节的耐心。下一步我打算把YOLO检测结果接到目标跟踪模块里,处理连续帧之间目标ID保持的问题,等跑通了再来更新这个系列的笔记。