基于Transformer与YOLOv5的多光谱目标检测实践

📅 2026/8/27 6:42:19
基于Transformer与YOLOv5的多光谱目标检测实践
简介目标检测是计算机视觉的核心任务但在低照度、雾天等复杂环境下单模态可见光图像信息不足导致检测性能大幅下降。多光谱成像通过融合可见光与近红外等不同波段信息为模型提供更丰富的特征表达。近年来基于Transformer的注意力机制被引入目标检测领域以增强全局上下文建模能力弥补传统卷积网络在长距离依赖上的不足。将YOLOv5的工程高效性与Transformer的全局感知能力相结合多光谱目标检测在智慧安防、自动驾驶、工业巡检等场景中展现出显著的实用价值。本文从数据配准、模型改造、训练调参与部署优化等环节出发系统介绍基于Transformer与YOLOv5的多光谱目标检测实践帮助开发者少走弯路。 从去年下半年开始我一直在折腾一个挺有意思的方向多光谱目标检测。起因是手上有个项目场景单靠可见光相机在低照度、雾天、强阴影这些条件下误检和漏检实在压不下去。后来试了试把近红外和可见光两路图像喂进YOLOv5再在主干里插入Transformer模块效果确实超出了我的预期。这个项目从数据处理、模型改到调参部署前前后后花了不少时间踩过的坑也不少。标题里提到的“基于TransformerYOLOv5的多光谱目标检测”本质上并不是一个全新的框架而是把两种成熟思路做了融合一端用YOLOv5的anchor-based检测头保证回归精度和工程落地能力另一端用Transformer的自注意力机制来补足CNN在全局上下文建模上的短板。如果你正在做目标检测尤其是手头有红外、多光谱这类多模态数据的需求这篇内容应该能帮你省下不少试错的时间。下面我会把项目的整体设计、数据准备、代码改造、训练调参和常见问题整个拆开来讲。1. 项目整体设计与思路拆解1.1 为什么是多光谱单模态的瓶颈在哪先聊一个最基础的问题既然YOLOv5在COCO上都跑得那么好为什么还要折腾多光谱我个人的体会是现实场景里的目标检测大部分情况下并不会像公开数据集那么“配合”。光照变化就是最大的敌人太阳直射下一个深色衣服的行人和背景融为一体夜间行车时路灯照不到的区域全靠暗部细节雨雾天气里可见光图像对比度急剧下降。这些场景下模型能用的有效信息太少了就算把YOLOv5的Backbone换得再粗也很难从“信息缺失”的图像里变出特征来。多光谱的思路很简单既然可见光不够用那就再加一路别的光。近红外NIR对光照变化不敏感在低照度下依然能保留物体轮廓和材质差异热红外LWIR则直接响应物体温度人和车辆在夜间会“自发光”。把这两路信息叠在一起等于给模型开了一双能穿透光照限制的眼睛。无人机遥感里的植被监测、水面搜救、工业巡检里的发热点定位本质都是这个逻辑。1.2 为什么选择YOLOv5作为检测基座多光谱融合方法其实有很多种框架可选Faster R-CNN、SSD、YOLOv5、甚至最新的YOLOv8都能拿来当基座。我最后选了YOLOv5主要看中三点工程成熟度YOLOv5的预训练权重、数据增强策略、训练脚本、导出工具都完整出了问题能找到大量现成经验推理性能项目后期要部署到嵌入式设备上YOLOv5的anchor-based检测头配合CSP结构在精度和速度上平衡得很好结构可塑性强YOLOv5代码模块化程度高改Backbone、改Neck、改Head都比其他版本顺手得多想换成Transformer块直接改yaml就行。1.3 Transformer模块在其中的角色多光谱数据融合之后特征图的通道数变多了信息量也变大了。但YOLOv5原本的Backbone是纯卷积结构卷积的感受野始终是局部的。局部卷积的问题在于目标周围如果全是干扰纹理模型就很容易把注意力放到错误的地方。Transformer的作用就是解决这个“只看局部”的问题。具体来说我在Backbone的深层比如P5层用C3TR模块替换了原来的C3模块。C3TR的做法是把C3里的一串Bottleneck替换成Transformer Block让深层特征图上的每个位置都能和其他位置的像素做全局交互。这样一来一个被遮挡一半的行人也能通过远处道路边缘、车辆轨迹这些上下文信息被“脑补”出来。这个组合的直观理解可以这样打比方YOLOv5像是熟练的巡警对常见的目标特征非常敏感跑得快但要看“眼前”Transformer像是站在高楼上的瞭望员能一眼看全局但比较慢。多光谱就是把两套巡检信息叠加再让瞭望员在关键楼层值班。1.4 数据融合层级早期融合还是晚期融合多光谱融合有一个绕不开的决策点在哪个阶段把可见光和红外/近红外合在一起。这个项目里我采用了两路输入在输入端直接拼接的方式也就是通道维度的早期融合。可见光输入是RGB三通道近红外是单通道拼接后变成4通道输入。模型的第一个卷积层从3通道变成4通道之后所有特征提取都在融合后的特征上进行。早期融合的优势是结构简单同一套特征提取网络同时处理两路信息训练时不需要额外的对齐监督。晚期融合双流网络通常是指两路数据各走一个Backbone到Neck或者Head才合并效果也更好但参数量翻倍训练成本高部署难度也大。对于大多数中小型项目来说早期融合的性价比最高。如果你的数据是可见光热红外33通道也是同理拼接成6通道。2. 多光谱数据集准备与预处理要点2.1 数据采集与配准最容易翻车的环节多光谱目标检测项目的第一步不是写模型而是搞定数据。我建议你在采集阶段就严格保证可见光和近红外/热红外图像的空间对齐。如果两路图像的视场角不同或者相机之间存在平移和旋转那拼接后的4通道图像就会出现“同一目标在两个位置”的问题模型会学出非常奇怪的特征。最稳妥的做法是使用硬件上已经做过配准的双目/多光谱相机比如一些工业级的可见光近红外一体机出厂时已经对齐了像素坐标。如果是自己搭建的采集系统一定要做标定用棋盘格或点阵标定板对两路相机进行内参和外参标定通过重投影把红外图像映射到可见光坐标系下得到对齐后的图像对。配准这块有一个值得注意的点标定要在不同对焦距离下各做一次因为相机变焦或调焦后外参会改变。我试过只在一个距离标定换到远距离场景时两路图像就出现了几个像素的偏移目标边缘直接出现重影训练出来的模型精度掉了好几个点。后来重新标定并固定焦距问题才消失。2.2 标注格式转换与类别设计配准完成后你可以直接在可见光图像上进行目标标注然后把标注框直接复用给近红外图像。这是因为两路图像已经配准好了像素坐标一一对应。标注工具我用的LabelImg和X-AnyLabeling格式导出为YOLO的txt格式每行是 class_id x_center y_center width height前四个值是归一化后的坐标。这个项目里的类别设计我建议先小后大。最开始只做两类比如person和vehicle把baseline跑通再逐步扩展。多光谱数据本身就比单模态数据难标注因为近红外图像里人眼看起来不明显标注员容易产生疲劳导致漏标。如果一开始就做十几个类数据质量很难保证。2.3 数据增强策略不能对两路图像用同样的随机裁剪YOLOv5自带了很多数据增强比如随机平移、缩放、翻转、HSV变换。但多光谱数据有一个容易踩的大坑HSV增强是按RGB三通道来的它对近红外通道没有意义如果直接套用就相当于对NIR通道做了一次无规则的亮度扰动可能反而破坏物理信息。我的做法是修改数据增强流水线把可见光的HSV扰动和近红外的亮度对比度扰动分开做或者干脆关闭HSV增强改用对两路都一致的几何增强翻转、平移、旋转。核心逻辑是几何变化必须确保两路图像在空间上仍然对齐光谱域的增强则需要对每个通道单独设计且幅度不能太大。常用增强组合可以参考这个列表Mosaic增强YOLOv5默认开启对多光谱数据也有效但要注意拼接的四张图的可见光和近红外必须来自同一组图像对随机水平翻转p0.5可见光和近红外同时翻转随机平移/缩放同样对两路同步进行注意补边值保持一致随机亮度和对比度分别在两路上做幅度控制在±20%以内高斯噪声加入少量噪声有助于模型鲁棒性但标准差建议不超过0.02。2.4 数据集划分与校验多光谱数据的划分不能只按图像随机分最好按“场景/时间段”划分。比如同一地点白天、夜晚、黄昏的采集数据不要同时出现在训练集和验证集里。否则模型会通过背景记忆来“作弊”换一个新场景直接掉点。划分比例我一般用7:1.5:1.5训练/验证/测试。划分完之后建议写个脚本做一次强校验随机抽样100对图像检查两路图像是否配准、标注框是否落在目标上、类别ID是否越界。这一步虽然枯燥但能提前排除掉大量脏数据后面训练起来会省心很多。3. 代码改造YOLOv5 Transformer 多光谱输入的实现细节3.1 环境搭建与依赖安装项目基于PyTorch实现我用的版本组合是Python 3.8 PyTorch 1.10 CUDA 11.3YOLOv5代码基于v6.0分支。这里特别提醒一下YOLOv5版本迭代很快网上能找到的教程很多是基于老版本的一定要核对代码里的模块名和配置文件结构最好直接clone官方仓库后切换到你需要的tag再加自己的改动。安装依赖的常用命令git clone https://github.com/ultralytics/yolov5.git cd yolov5 git checkout v6.0 pip install -r requirements.txt如果是在国内网络环境pip安装时我习惯加一个清华镜像源否则下载大包容易超时pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple3.2 多光谱输入改造修改第一层卷积YOLOv5默认输入是RGB三通道我们要把它改成4通道RGBNIR或者6通道RGB热红外。需要改动的文件有两个模型yaml和数据集yaml。首先在数据集配置文件里增加一个字段声明通道数。然后在模型的yaml文件里把Backbone第一层Conv的输入通道数改掉。YOLOv5的模型定义并不直接写在yaml里而是通过parse_model函数解析yaml中的参数后动态构建。我们需要找到common.py和yolo.py在parse_model中增加对ch参数的处理。关键改动位置在yolo.py中的parse_model函数它接收参数后通过ch [ch]记录每一层的输出通道数然后是逐层构建。当第一层的输入通道是4时我们只需要在加载yaml文件前把模型的第一层卷积输入改为4即可。具体做法可以重写一个Model类的构造函数在实例化时检测数据集的通道数字段然后修改第一层Conv的in_channels。3.3 插入C3TR模块把Transformer块放进BackboneYOLOv5官方其实没有C3TR这是社区里在C3模块基础上改造出来的变体。核心逻辑是C3模块原本由多个Bottleneck组成C3TR则把这些Bottleneck替换成Transformer Block。Transformer Block包含多头自注意力Multi-Head Self-Attention和前馈网络Feed-Forward Network并且带残差连接和LayerNorm。在common.py中增加两个类class TransformerBlock(nn.Module): def __init__(self, c1, c2, num_heads4, num_layers1, dropout0.1): super().__init__() self.conv nn.Conv2d(c1, c2, 1, biasFalse) self.linear nn.Linear(c2, c2) self.norm nn.LayerNorm(c2) self.dropout nn.Dropout(dropout) self.attn nn.MultiheadAttention(c2, num_heads, dropout, batch_firstTrue) self.mlp nn.Sequential( nn.Linear(c2, c2 * 2), nn.GELU(), nn.Linear(c2 * 2, c2), ) def forward(self, x): x self.conv(x) B, C, H, W x.shape x x.flatten(2).permute(0, 2, 1) x x self.dropout(self.attn(self.norm(x), self.norm(x), self.norm(x))[0]) x x self.dropout(self.mlp(self.norm(x))) x x.permute(0, 2, 1).reshape(B, C, H, W) return x class C3TR(C3): def __init__(self, c1, c2, n1, shortcutTrue, g1, e0.5): super().__init__(c1, c2, n, shortcut, g, e) c_ int(c2 * e) self.m nn.Sequential(*(TransformerBlock(c_, c_) for _ in range(n)))然后在yolo.py的parse_model中把C3TR加入类型的映射表。之后在模型的yaml文件中把深层的C3换成C3TR。这样一来模型在最后一个检测层之前就能用Transformer块做全局关系建模同时不会影响YOLOv5的anchor解码和NMS逻辑。这里有一个关键参数值得单独说明num_heads和num_layers并不是越大越好。我试过num_heads8、num_layers2在公开数据集上能提升几个点但在我们自建的规模较小的多光谱数据集上反而过拟合了。对于小型数据集4个头、1层Transformer通常比较稳妥。3.4 修改配置文件以YOLOv5s为基础建议直接基于yolov5s.yaml改我最后用的配置类似这样nc: 2 # 类别数量 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [10, 13, 16, 30, 33, 23] - [30, 61, 62, 45, 59, 119] - [116, 90, 156, 198, 373, 326] backbone: - [-1, 1, Conv, [64, 6, 2, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C3, [128]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 6, C3, [256]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 9, C3TR, [512]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 3, C3TR, [1024]] - [-1, 1, SPPF, [1024, 5]] ...注意我在P4和P5两个尺度上都用了C3TR。P3层保留普通C3因为浅层特征图空间分辨率高直接上Transformer的计算量会很大而且小目标的局部纹理信息主要依靠卷积就能提取。3.5 数据集配置与多光谱加载器改造YOLOv5的LoadImagesAndLabels默认按RGB三通道读取图像。我们需要重写或扩展这个类让它每次读取一对图像可见光近红外然后沿通道维度拼接。核心思路是在dataset.py中把每个样本的路径改为一个元组(img_path, nir_path)在__getitem__里分别读取两个文件分别做归一化和resize再用torch.cat([img_rgb, img_nir], dim0)拼接。注意在训练时用到的Mosaic增强里需要同时加载四组对应的图像对并保证mosaic拼接时两路的坐标变换一致否则训练会直接崩。这里有一个容易忽略的细节图像读取时RGB和NIR的位深可能不同。可见光一般是8位工业近红外有些是12位或16位。我建议统一转成8位PNG或JPG保存否则加载时要自己写位深转换容易出错。可以把16位NIR数据先做直方图均衡或者百分比拉伸再保存成8位这也有助于提升对比度。4. 训练配置、调参与评估实录4.1 训练命令与关键参数多光谱模型训练的基本命令和单模态YOLOv5没有本质区别但有几个参数我做了调整python train.py --data dataset.yaml --cfg models/yolov5s_ms.yaml --weights yolov5s.pt --batch-size 16 --epochs 300 --img 640 --device 0 --multi-scale值得关注的几个参数img size多光谱输入因为通道数变多显存占用更大。我最初用640batch size只能开到8后来因为显存限制改成了512输入、batch size 12。个人建议如果显存只有8G先用512训练测试时再用640multi-scaleYOLOv5的多尺度训练会随机在0.5~1.5倍之间缩放输入尺寸。多光谱图像本身存在配准误差如果缩放太狠重影会更明显所以我只在最后50个epoch开启multi-scale预训练权重直接用yolov5s.pt的话第一层卷积权重会因为输入通道数变化而无法加载程序会打印跳过信息。我的做法是使用YOLOv5在RGB图像上的预训练权重只跳过第一层不匹配的权重其余层全部预训练初始化。这样模型依然能继承大部分特征提取能力batch size小的batch size在Transformer模块中会影响LayerNorm的统计量建议最好不低于16。如果显存实在不够可以把num_heads调低减少参数量和内存占用。4.2 损失观察与收敛判断YOLOv5的损失由三部分组成box回归损失CIoU分类损失BCEWithLogits目标置信度损失BCEWithLogits。训练时我主要看box_loss和obj_loss两条曲线。多光谱模型初始训练时obj_loss往往比单模态模型高一些这很正常因为模型一开始对融合特征还不适应。前20个epoch损失震荡是正常的但如果到50个epoch左右obj_loss还高于单模态模型的初始水平就要检查通道拼接是否错了、两路图像是否配准。建议每5个epoch保存一次权重训练结束后再根据验证集mAP挑选最终模型。不要指望最后一个epoch是最好的我做过多次训练之后发现最佳权重一般出现在训练中后段。4.3 消融实验Transformer和多光谱各自贡献多少做这种融合项目一定要做消融实验否则很难说服自己也说服不了别人每个模块的贡献。我做了四个实验对照组YOLOv5s只用可见光RGB训练实验AYOLOv5s使用可见光近红外拼接的4通道输入实验BYOLOv5sC3TR只用可见光RGB训练实验CYOLOv5sC3TR多光谱4通道输入完整版。验证集mAP0.5的结果大致如下实验输入模态BackbonemAP0.5mAP0.5:0.95对照组RGBC382.453.1实验ARGBNIRC387.658.2实验BRGBC3TR84.155.0实验CRGBNIRC3TR91.363.7可以看到单独加Transformer提升约2个点单独加多光谱提升约5个点两者叠加有不错的互补效果最终达到91.3。这个增幅在目标检测任务里已经相当可观了尤其是对小目标和遮挡目标提升更加明显。4.4 测试时增强与置信度阈值调整推理阶段YOLOv5的默认置信度阈值是0.25NMS IoU阈值是0.45。多光谱模型在低照度场景下预测置信度普遍比可见光模型高但也会有少数困难样本置信度压在0.2~0.25区间。我的经验是测试时先用0.25阈值跑一遍看漏检率。如果漏检主要集中在遮挡严重的目标就把阈值降到0.15同时适当提高NMS IoU阈值到0.5避免产生过多重复框。反过来如果误检很多就把阈值提升到0.35。阈值没有绝对标准可以根据你业务里面漏检和误检的代价来权衡。5. 常见问题与排查技巧实录5.1 训练loss异常升高或出现NaN出现NaN通常有几个原因学习率过大、数据里有异常值、或者Transformer模块的LayerNorm在数值上不稳定。我排查的顺序是先检查数据是否有个别图像是纯黑或纯白纯色图像经过LayerNorm后容易出现数值不稳定再把学习率降低一个量级比如从0.01降到0.001如果还不行检查TransformerBlock里是否在残差连接前做了dropoutdropout在训练时可能放大激活值最后给注意力权重加上一个很小的epsilon1e-6来防止除零。5.2 两路图像配准误差导致收敛变慢这个是最常见的坑。如果两路图像有2~3个像素的偏移浅层卷积尚可容忍但Transformer在做全局注意力时会把这种错位信息放大导致模型一直在拟合错误的关联关系。解决办法是如果硬件配准无法做到完美可以在数据预处理时加入随机微小偏移作为增强比如对NIR通道随机平移0~2个像素让模型学会适应这种误差。实际测试下来这个方法能让部署阶段的稳定性提升不少。5.3 Transformer模块在小数据集上的过拟合如果你手头只有几千张多光谱图像C3TR模块很容易过拟合。我试验过几种缓解手段减小C3TR中的n参数Transformer层数从2降到1在TransformerBlock中加大dropout从0.1提到0.2冻结Backbone前期层只训练后期和检测头等模型稳定后再解冻使用更小的输入尺寸减少可学习的空间位置数量。5.4 显存不足与训练速度优化多光谱拼接后输入通道翻倍显存占用确实会增加不少激活值占用的显存也会相应上升。几个实用的优化思路使用梯度累积batch size 8不变每2步累积一次梯度等效于batch size 16开启YOLOv5的--cache选项把图像预加载到内存减少数据读取瓶颈使用AMP混合精度训练PyTorch自带的自动混合精度能省将近一半显存而且速度更快如果训练数据量不大把workers调高到8或者12避免GPU等数据。5.5 部署时速度比预期慢Transformer模块的计算效率一般低于卷积尤其在没有针对自注意力做优化的设备上。如果在Jetson或者RK系列板子上部署建议这么做把P4层的C3TR换回C3只保留P5层Transformer精度损失通常小于1个点但速度能提升20%左右把模型导出为TensorRT或者ONNX用TensorRT的attention插件优化注意输入tensor是4通道有些推理框架对第一层卷积的通道数有优化假设需要确认算子是否支持。4通道输入在部署时容易遇到一个问题很多硬件加速库为RGB输入做了专门优化但4通道的卷积算子可能缺少优化实现。我最终在部署时采用的是“分而治之”的方案把4通道切成31分别跑两个小卷积再在特征层面拼接这样能绕过算子的通道限制速度反而更快。6. 可扩展方向与项目经验总结6.1 尝试Swin Transformer替换C3TR如果你对Transformer部分有更高的性能要求可以尝试Swin Transformer替代C3TR。Swin的核心是窗口自注意力移位窗口机制它把全局注意力的计算复杂度从O(N^2)降到了O(N)对高分辨率特征图更友好。不过Swin的实现要比单层Transformer复杂不少权重初始化、窗口划分这些细节都要小心。如果项目时间紧建议优先把C3TR跑通再考虑升级。6.2 朝向anchor-free与小目标检测的方向YOLOv5的anchor-based检测头在小目标上的表现有时不够理想因为 anchor 尺寸是在训练集上聚类得到的。如果你的项目主要是小目标比如无人机视角下的行人、车辆可以考虑把检测头改成anchor-free的方案比如YOLOX的Decoupled Head或者FCOS风格。Transformer本身对小目标的全局上下文很有帮助配合anchor-free检测头能够在密集小目标场景下获得更好的F1分数。6.3 多模态扩展把热红外和文本语义加进来多光谱不是只有可见光近红外还能扩展出更多模态。比如加上热红外构成“RGBNIRLWIR”三模态或者把语义分割的地图信息作为额外通道输入。模态越多模型理论上越强但数据采集和对齐的成本也会指数上升。我的建议是先做两模态验证收益再逐步叠加不要想着一步到位。回到开头的话题我现在做多光谱目标检测时最深的体会是这种项目的核心难点其实不在于模型用Transformer还是YOLOv5而在于怎么把不同模态的数据“对齐”到同一个空间里然后让网络自己学会从互补的信息里提取共同和独有的特征。数据配准的耐心程度直接决定了模型效果的天花板。最后再分享一个小技巧训练结束后别急着删除训练日志。YOLOv5会保存每轮的各类loss和mAP指标我后来写了一个小脚本把这些数据自动导出成csv方便做长期对比。你换了融合策略、数据增强或者Transformer的头数之后直接拉曲线对比比看任何日志都直观。本文还有配套的精品资源点击获取