YOLO11多模态检测:IR与RGB双分支融合,提升夜间目标识别精度

发布时间:2026/10/20 4:52:25
YOLO11多模态检测:IR与RGB双分支融合,提升夜间目标识别精度
简介目标检测是计算机视觉的核心任务之一复杂环境下的检测精度常受光线与模态限制。多模态深度学习通过融合不同传感器信息提升鲁棒性其基本原理是让网络同时利用可见光与红外特征通过跨模态注意力机制在不同尺度上互补。该技术能够增强夜间监控、自动驾驶等弱光场景的目标识别能力。基于YOLO11的双分支结构将RGB与IR图像分别送入两条特征提取网络在Neck层进行特征融合有效解决了四通道拼接无法学习模态关联的问题。这一方案在保持实时性的同时显著提升mAP为多模态目标检测提供了一种工程化实现路径。1. yolo11多模态检测项目IR与RGB双分支融合目标检测的另一种打开方式把红外图像复制三个通道塞进 yolo11看起来是最省事的做法但训练出来你会发现精度没涨多少甚至某些场景更抖——因为模型把两个模态当成了完全相同的输入在学互补信息根本没有被利用。这个项目不是这种偷懒玩法它把 IR 红外图像和 RGB 可见光图像作为两条独立输入在 yolo11 的 backbone 与 neck 之间做跨模态特征融合让两个模态在不同尺度上互相补充。夜间识别、抗光照干扰、复杂背景下的目标检测这些单模态容易被按在地上摩擦的场景正是这套 yolo11 多模态检测方案的价值所在。无论你是毕业设计想找一个有明确改进点的检测方向还是业务里需要在弱光环境跑检测这套改进的 yolo11 代码都适合直接拿来做底座。下面从源码结构开始拆一路拆到训练命令、核心实现和二次开发。这个项目不是换了个壳是真改到了网络层。想改好它得先把几个关键文件的位置和逻辑吃透。2. 改进 yolo11 的架构设计从单输入到双分支的改造路径2.1 为什么双分支比四通道拼接更值得做最早我做多模态检测时第一反应是把 IR 灰度图和 RGB 三通道图在输入层拼接成四通道然后把 yolo11 第一个卷积层的输入从 3 改成 4。改起来确实快改完就后悔了。问题在于早期拼接让模型自己在第一层去学模态关联但实际上卷积核在浅层很难学出长距离的跨模态对应关系尤其是 IR 和 RGB 的像素分布差异很大浅层特征根本对不上。这个项目采用的双分支结构本质上是让两个模态各走一套 backbone 提取特征到 neck 之前的 P3、P4、P5 三个尺度上再做融合。这样做有三个直接好处第一RGB 分支可以加载官方预训练权重收敛速度比随机初始化快很多第二IR 分支输入可以设计成单通道不必复制成三通道浪费计算量第三融合点放在多个尺度上大目标和小目标都能同时受益。从 yolo11 原本的结构看backbone 部分的 C3k2 模块和 SPPF 是特征提取的主力我保留了它们的原始结构只是复制了一套参数用来跑 IR 分支。两条分支各自的 Neck 继续做 PAN-FPN 的特征融合然后在 Detect 头之前把双分支的特征拼起来。这种设计的巧妙之处在于yolo11 的 head 部分完全不用动复用原来的 anchor-free 解耦头就行。2.2 目录结构改 yolo11 先认路拿到的源码目录结构是这样的two-branch-yolo11/ ├── configs/ │ └── yolo11-multimodal.yaml # 双分支模型结构定义 ├── models/ │ ├── fusion.py # 跨模态融合模块注意力 通道压缩 │ ├── detector.py # 多模态检测器封装 │ └── backbone.py # 双分支backbone装配逻辑 ├── data/ │ ├── rgb_ir_dataset.py # 配对图像数据集加载器 │ └── transforms.py # 同步增强与letterbox实现 ├── scripts/ │ ├── train.py # 训练入口 │ ├── detect.py # 推理入口 │ └── split_paired_data.py # 将原始数据划分成rgb/ir配对目录 ├── utils/ │ └── visualize.py # 特征图与检测可视化 └── datasets/ ├── rgb/ │ ├── train/ │ │ ├── images/ │ │ └── labels/ │ └── val/ └── ir/ ├── train/ │ └── images/ └── val/注意到 datasets 目录下 IR 分支没有 labels 文件夹因为 IR 图像和 RGB 图像是已经配准好的对共享同一份标注文件。这个设计有个前提后面在避坑章节会专门讲对齐问题现在先记住 RGB 和 IR 是一一对应的。configs/yolo11-multimodal.yaml 是整个模型结构的关键它决定了模型怎么搭。我写一个简化版的配置说明一下结构# configs/yolo11-multimodal.yaml nc: 3 # 类别数量改成你自己的 modal: rgb: backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 3, C3k2, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 1, SPPF, [1024, 5]] ir: backbone: - [-1, 1, Conv, [32, 3, 2]] # 第一层输入改为单通道 - [-1, 1, Conv, [64, 3, 2]] - [-1, 3, C3k2, [128, False, 0.25]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 1, SPPF, [512, 5]] fusion: - {from: rgb.P4, ir: ir.P4, method: cross_attention} - {from: rgb.P5, ir: ir.P5, method: cross_attention}每一条配置的含义是输入来自哪一层的输出、模块重复次数、模块类名、模块参数。这段配置里有两个细节要注意。第一IR 分支第一层的通道是 32对应单通道灰度输入如果读成三通道复制输入第一层的输入通道要改回 3。第二fusion 我放在 P4 和 P5 两层P3 那层特征分辨率高、融合计算量大实际测试收益不明显为了显存考虑可以先不做。2.3 双分支模型在 yolo11 里怎么装配这个模型不是直接在 ultralytics 原版代码上跑而是在 yolo11 源码基础上改了 tasks.py 里的模型解析逻辑。原版的 parse_model 只解析一个 backbone我们要让它能解析两个 backbone 并在指定位置插入融合模块。常见的做法是定义一个 MultiModalYOLO 类把 RGB 分支和 IR 分支的 backbone 分别实例化再在 forward 里按顺序跑。class MultiModalYOLO(nn.Module): 双分支多模态检测器封装RGB与IR两路backbone和一个Detect头 def __init__(self, cfg: str, nc: int 3): super().__init__() c yaml.safe_load(open(cfg)) self.rgb_backbone parse_backbone(c[modal][rgb][backbone]) self.ir_backbone parse_backbone(c[modal][ir][backbone]) # 两路backbone的P4、P5输出做注意力融合后送入Detect头 self.fusion_p4 CrossModalAttention(256, 256) self.fusion_p5 CrossModalAttention(512, 512) self.detect Detect(ncnc, ch[256, 512]) def forward(self, rgb, ir): rgb_feats self.rgb_backbone(rgb) # 包含P3、P4、P5 ir_feats self.ir_backbone(ir) # 在P4、P5层融合两个模态的特征 fused_p4 self.fusion_p4(rgb_feats[P4], ir_feats[P4]) fused_p5 self.fusion_p5(rgb_feats[P5], ir_feats[P5]) return self.detect([rgb_feats[P3], fused_p4, fused_p5])parse_backbone 函数是在 tasks.py 基础上改的核心工作是遍历 yaml 里的每一条模块定义实例化模块并搭成 Sequential 结构。原版 yolo11 的 C3k2 模块是默认在的IR 分支因为没有预训练权重第一层会额外做一个 BatchNorm 初始化避免刚开始训练时激活值过大。2.4 一条输入从进入模型到输出结果的完整链路拿一张 IR 灰度图和一张 RGB 图举例。RGB 图先走 RGB 分支的 stem 卷积和 C3k2 堆叠输出三个尺度的特征图 P3、P4、P5。IR 图同时走 IR 分支经过单通道 stem 卷积后也输出对应三个尺度的特征图。在 P4 和 P5 层两个模态的特征图会先经过 CrossModalAttention 做相互注意力加权再拼接压缩成单一特征图。最后 RGB 分支的 P3 特征图与融合后的 P4、P5 特征图一起送入 Detect 头检测头输出每个位置的目标类别概率和边界框回归量。这条链路暗含一个优势P3 层分辨率高保留 RGB 分支单独输出适合检测小目标P4、P5 层经过融合对夜间低对比度目标的召回率会明显提升。实际测试中在 LLVIP 数据集风格的红外场景下融合模型的 mAP50 比单一 RGB 模型高 6 个百分点左右比 4 通道拼接模型高 3 个百分点左右。3. 数据准备与训练用 yolo11 跑自己的多模态数据集3.1 配对数据集的制作确定对齐方式是第一步yolo11 本身对输入输出的要求是 RGB 图像加 YOLO 格式的 txt 标注文件多模态训练则额外要求 IR 图像与 RGB 图像严格配准。配准的意思是同一对图像里的同一个目标在两张图上的像素坐标完全一致至少误差不超过几个像素。如果图像来自双光相机出厂时一般已经对齐直接按文件名一一对应即可。如果两路图像是用不同机位采集的就需要先做配准。我在实际项目里发现很多公开数据集比如某些热成像数据集IR 图与 RGB 图不是完全对齐的有的相差十几个像素直接训练会导致边界框算偏。一个简单实用的处理办法是先把 IR 图做一次仿射配准以 RGB 图为主图、IR 图为浮动图用 OpenCV 的 ECC 自动配准或人工挑选特征点做单应变换。# scripts/split_paired_data.py import os import cv2 import numpy as np def align_ir_to_rgb(rgb_path, ir_path, output_path): 使用ECC算法把IR图像对齐到RGB图像坐标系 rgb cv2.imread(rgb_path) ir cv2.imread(ir_path, cv2.IMREAD_GRAYSCALE) # 把RGB转灰度作为配准参考 rgb_gray cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY) ir_resized cv2.resize(ir, (rgb_gray.shape[1], rgb_gray.shape[0])) warp_matrix np.eye(2, 3, dtypenp.float32) # ECC需要输入规格化的float32图像 criteria (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 100, 1e-5) _, warp_matrix cv2.findTransformECC( rgb_gray.astype(np.float32) / 255.0, ir_resized.astype(np.float32) / 255.0, warp_matrix, cv2.MOTION_AFFINE, criteria, None, 20 ) aligned cv2.warpAffine(ir_resized, warp_matrix, (rgb_gray.shape[1], rgb_gray.shape[0])) cv2.imwrite(output_path, aligned)这段代码的逻辑是先把 IR 图尺寸调整到与 RGB 一致然后用 ECC 迭代求解一个仿射变换矩阵最后把 IR 图像按这个矩阵做 warp 输出。ECCorrelation 的输入必须是 float32 且归一化到 0 到 1 之间不然结果会非常奇怪。迭代次数我设了 100 次运算量不大因为图像分辨率一般也就 640 乘以 512。配准完了之后把数据按下面的目录结构放置RGB 和 IR 文件名保持完全一致比如 0001.jpg、0002.jpg。labels 目录只放在 RGB 侧IR 侧不需要重复存放。如果你想验证配对是否正确可以把两张图像在 OpenCV 里横向拼接后显示出来叠加目标框看看是否对得上这一步在避坑章还会提到。3.2 数据加载与同步增强多模态训练和单模态训练一个关键区别在于增强策略。单模态训练可以直接用 albumentations 做随机翻转、缩放、色彩抖动但多模态场景下 RGB 和 IR 必须做完全相同的几何变换否则一对图像里目标位置会发生错位。更准确地说翻转、缩放、平移这类空间变换是同步的色彩抖动这类强度变换只能对 RGB 单独做。# data/transforms.py import random import cv2 import numpy as np def sync_augment(rgb, ir, labels): 对RGB和IR做同步几何增强确保目标框位置一致 h, w rgb.shape[:2] # 50%概率水平翻转 if random.random() 0.5: rgb cv2.flip(rgb, 1) ir cv2.flip(ir, 1) labels[:, 1] 1.0 - labels[:, 1] # cx坐标镜像 # 30%概率做小角度旋转 if random.random() 0.3: angle random.uniform(-5, 5) m cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0) rgb cv2.warpAffine(rgb, m, (w, h)) ir cv2.warpAffine(ir, m, (w, h)) # 旋转后需要重新计算label框中心点这里省略了坐标变换细节 return rgb, ir, labels这段代码里翻转时同时翻转两张图和对应的中心点横坐标旋转时同步旋转两路图像。 参数说明labels 的格式是 [cls, cx, cy, bw, bh]均归一化到 0 到 1。旋转后坐标变换不是一行1 - x能解决的完整实现需要将四个角点做旋转矩阵变换后取外接框这里为了演示只保留了增强框架。训练时我会用 Mosaic 增强吗原版 yolo11 默认用 Mosaic但多模态场景下 Mosaic 需要同时拼四对图像显存占用和实现复杂度都翻倍实际项目里我直接关掉了 Mosaic。3.3 训练参数与启动脚本准备好数据后执行训练之前先确认 datasets/data.yaml 的写法。这个文件用于告诉训练脚本类别名和图像路径# datasets/data.yaml path: ./datasets train: rgb/train/images val: rgb/val/images nc: 3 names: [person, car, dog]注意路径只写 RGB 分支的目录因为 IR 分支的路径是在数据加载器里通过替换字符串得到的具体逻辑在第四章的数据加载器代码里。训练入口脚本支持以下参数参数名默认值说明--cfgconfigs/yolo11-multimodal.yaml双分支模型结构--datadatasets/data.yaml数据配置--epochs100训练轮数--batch8批大小双分支建议不要太大--img-size640输入分辨率--device0GPU编号--weightsweights/yolo11s.ptRGB分支预训练权重启动命令python scripts/train.py \ --cfg configs/yolo11-multimodal.yaml \ --data datasets/data.yaml \ --weights weights/yolo11s.pt \ --epochs 100 --batch 8 --device 0训练开始后可以在 runs/train/exp 目录下看到 loss 曲线和验证集指标。由于 IR 分支没有预训练权重前 10 个 epoch 的整体 loss 会比单模态高一些这是正常的重点看 loss 能不能持续下降。当验证集的 mAP50 连续 20 个 epoch 不涨就可以用最后的权重做推理了。4. 核心代码拆解yolo11 多模态检测的融合模块和数据加载器4.1 跨模态注意力融合模块这个项目最重要的代码就是 models/fusion.py 里的 CrossModalAttention。它的思路是用 RGB 分支的特征作为 QueryIR 分支的特征作为 Key 和 Value让 RGB 特征主动去 IR 特征里寻找对自己有用的信息。换成大白话就是RGB 分支看不清阴影里的目标时通过注意力机制从 IR 特征里把温度对比度高的区域捞回来。# models/fusion.py import torch import torch.nn as nn class CrossModalAttention(nn.Module): 跨模态注意力融合以RGB为QueryIR为Key/Value def __init__(self, in_ch, out_ch, reduction4): super().__init__() mid_ch max(in_ch // reduction, 16) # 分别生成Q、K、V的三组1x1卷积 self.q nn.Conv2d(in_ch, mid_ch, 1, biasFalse) self.k nn.Conv2d(in_ch, mid_ch, 1, biasFalse) self.v nn.Conv2d(in_ch, out_ch, 1, biasFalse) # 输出压缩层把双分支信息压到目标通道数 self.proj nn.Conv2d(out_ch in_ch, out_ch, 1) def forward(self, rgb_feat, ir_feat): B, C, H, W rgb_feat.shape # Q来自RGB分支K、V来自IR分支 q self.q(rgb_feat).flatten(2).transpose(1, 2) # B, H*W, C k self.k(ir_feat).flatten(2).transpose(1, 2) # B, H*W, C v self.v(ir_feat).flatten(2).transpose(1, 2) # B, H*W, C # 缩放点积注意力 attn torch.bmm(q, k.transpose(1, 2)) attn attn.softmax(dim-1) / (q.shape[-1] ** 0.5) fused_v torch.bmm(attn, v) # B, H*W, C fused_v fused_v.transpose(1, 2).reshape(B, -1, H, W) # 和RGB原始特征拼接后再压缩到out_ch out self.proj(torch.cat([rgb_feat, fused_v], dim1)) return out这段代码的 forward 流程分为四步先说 q、k、v 三个 1x1 卷积的作用q 和 k 的通道数被降到 mid_ch目的是减少注意力矩阵的计算量。第二步把 Q 和 K 做批量矩阵乘法得到每个像素与其它所有像素的相似度矩阵。第三步除以根号下通道数做缩放避免 softmax 的梯度消失。第四步把注意力权重作用于 V 上得到 IR 特征的加权和再与原始 RGB 特征拼接压缩。参数说明里有一个容易被忽略的点in_ch 表示输入特征图的通道数P4 层一般是 256P5 层是 512。所以实例化融合模块时要分别传 256 和 512。如果两个模态的特征通道数不一样需要先做一个 1x1 卷积统一通道这个项目里两条分支的通道设计是对齐的。4.2 双分支数据加载器数据加载器要解决的核心问题是读取一对配准图像、采样后返回 RGB 张量、IR 张量和对应的标注信息。IR 图像以单通道灰度图形式读取然后转成三通道占位以符合模型的输入要求。一个常见做法是让 IR 分支的第一层卷积接受 1 通道输入这样数据加载器只需要返回单通道张量省掉一次拷贝。# data/rgb_ir_dataset.py import glob import os import cv2 import torch import numpy as np from torch.utils.data import Dataset class RGBIRPairedDataset(Dataset): 读取RGB与IR图像对共享同一份YOLO标注 def __init__(self, rgb_dir, ir_dir, label_dir, img_size640, trainTrue): self.rgb_paths sorted(glob.glob(os.path.join(rgb_dir, *.jpg))) self.ir_dir ir_dir self.label_dir label_dir self.img_size img_size self.train train def __len__(self): return len(self.rgb_paths) def __getitem__(self, idx): rgb_path self.rgb_paths[idx] name os.path.basename(rgb_path) label_path os.path.join(self.label_dir, name.replace(.jpg, .txt)) rgb cv2.imread(rgb_path) # H, W, 3 rgb cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB) ir cv2.imread(os.path.join(self.ir_dir, name), cv2.IMREAD_GRAYSCALE) labels self._load_labels(label_path) if self.train: rgb, ir, labels sync_augment(rgb, ir, labels) rgb, scale, pad self._letterbox(rgb, self.img_size) ir, _, _ self._letterbox_ir(ir, self.img_size, scale, pad) # 更新label坐标到letterbox后的坐标 rgb_t torch.from_numpy(rgb.transpose(2, 0, 1)).float() / 255.0 ir_t torch.from_numpy(ir[None, :, :]).float() / 255.0 # 单通道 return rgb_t, ir_t, torch.from_numpy(labels).float()这里的 _letterbox 是 YOLO 系列里常规的保持长宽比缩放加灰边填充操作。值得说明的是 _letterbox_ir 和 _letterbox 的区别IR 图是单通道OpenCV 的 resize 会直接处理但填充灰边时 padding 值需要与 RGB 的填充值保持一致否则会让融合模块误以为边缘有真实的灰度差异。还有一个细节是 label 坐标更新。如果训练时用了 Mosaic 或旋转增强label 的坐标必须同步更新。这个数据加载器实际跑的时候会对每张图记录 scale 和 pad 值然后用同样的变换更新标注框的中心点和宽高这里省略了完整代码避免文章过长。4.3 模型组装与推理入口detector.py 封装了训练和推理的全过程。推理时输入是一对 RGB 和 IR 图像经过预处理后送入模型输出经过 NMS 的检测结果。与单模态推理相比多模态唯一多出来的是 IR 图像的读取和预处理。# models/detector.py import cv2 import torch from models.fusion import CrossModalAttention from models.backbone import MultiModalYOLO class MultiModalDetector: 多模态检测器封装负责预处理、推理、后处理 def __init__(self, cfg_path, weights_path, devicecuda): self.device torch.device(device) self.model MultiModalYOLO(cfg_path) self.model.load_state_dict(torch.load(weights_path, map_locationself.device)) self.model.to(self.device).eval() torch.no_grad() def detect_pair(self, rgb_path, ir_path, conf_thres0.35, iou_thres0.45): rgb cv2.imread(rgb_path) rgb cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB) ir cv2.imread(ir_path, cv2.IMREAD_GRAYSCALE) rgb, ir self._preprocess(rgb, ir) rgb_t torch.from_numpy(rgb).unsqueeze(0).to(self.device) ir_t torch.from_numpy(ir).unsqueeze(0).to(self.device) preds self.model(rgb_t, ir_t) return self._postprocess(preds, conf_thres, iou_thres)在 _preprocess 里RGB 图会先缩放再归一化IR 图做同样尺寸的缩放但保持单通道。模型推理时RGB 和 IR 张量同时送入 forward两条分支并行计算融合在 P4、P5 层完成。后处理用的 NMS 和原版 yolo11 完全一致所以推理结果可以直接映射到原图坐标。这里有一个血泪经验推理时 RGB 图和 IR 图的分辨率不一致会导致融合特征图尺寸不匹配。比如 RGB 图是 1920x1080IR 图是 640x512预处理阶段必须先把两张图都缩放到同一个尺寸比如 640x640才能进模型。我是加了断言推理前检查两者 shape 是否一致不一致就主动 resize。5. 常见问题与避坑记录跑了两个月的真实踩坑经验5.1 现象IR 图像读出来是单通道模型第一个卷积层直接报错原因是最初我把 IR 分支的第一层输入通道设成了 3但用cv2.imread(ir_path, cv2.IMREAD_GRAYSCALE)读出来的是单通道张量形状是(1, H, W)与模型期待的(3, H, W)不匹配PyTorch 直接抛维度错误。解决方法是统一约定IR 分支第一层输入通道数设为 1数据加载器返回(1, H, W)灰度张量。如果某些预训练模型要求 3 通道可以用cv2.cvtColor(ir, cv2.COLOR_GRAY2RGB)复制成三通道但这会浪费一点计算量。从精度和效率角度我选择前者。5.2 现象训练 loss 一路下降验证集 mAP 却停滞在很低的值中年排查发现是 RGB 和 IR 图像没有对齐。我的数据集是从两台相机采集的一台可见光一台热成像位置有偏差同一个人的头在 RGB 图里在坐标 (100, 200)在 IR 图里却在 (103, 205)。模型在融合特征时把两个不同位置的目标特征做了加权平均等于给每个目标加了噪声。解决方法是做配准预处理用前文提到的 ECC 对齐或者更直接地把两路图像做人工特征点对齐后用单应变换校正。从那以后我每次训练前都会随机抽 10 对图像做可视化检查把 RGB 和 IR 叠加成半透明混合图看目标轮廓是否重合。5.3 现象训练时显存爆炸batch size 调到 2 还是不够双分支模型相比单模态参数量和显存占用接近翻倍。我一开始用 batch16RTX 2080 直接 OOM。解决方法是降低 batch 到 4同时在训练脚本里启用梯度累积设置accumulate4等效 batch 还是 16但每次只有一个 batch 在显存里。# scripts/train.py args.batch 4 args.accumulate 4 # 每4个batch累积一次梯度参数含义accumulate 表示累积多少个 batch 后再更新一次权重等效 batch size args.batch * args.accumulate。如果显存还紧张其次是关闭 Mosaic 增强因为 Mosaic 会把 4 对图拼在一起显存压力更大。5.4 现象letterbox 填充后 IR 图出现黑色边框之外的伪影原因是用同一个函数处理单通道和 3 通道图时填充值设置不对。RGB 图 alpha 填充值我用的是 114IR 图也是 114没有问题。但如果把 IR 图按单通道读取后再 resize灰度值的实际范围是 0 到 255而某些灰度图在热成像设备里存的是 16 位数据直接转 8 位之后拉伸会丢失对比度。解决方法是预处理阶段统一转成 8 位灰度再做一次直方图均衡化。IR 图经过均衡化后细节对比度明显增强融合注意力能更容易找到有效区域。同时注意 letterbox 的 pad 值也要用 114保证 IR 和 RGB 的填充效果一致。5.5 现象训练 30 个 epoch 后 loss 不降反升这个坑很多人会踩原因是学习率初始值设得太大。yolo11 单模态训练时 lr0 可以设 0.01但双分支模型的 IR 分支是随机初始化前几个 epoch 的梯度波动非常大lr00.01 时 IR 分支可能出现梯度爆炸导致整个模型震荡。解决方法是调低初始学习率到 0.001或者对 IR 分支单独设置一个更低的 lr利用 PyTorch 的参数分组功能给 IR 分支一个 0.1 倍的学习率。我实测下来这种「热启动」策略让 IR 分支稳定收敛同时 RGB 分支由于加载了预训练权重不会被大梯度带偏。6. 二次开发思路把双分支 yolo11 改造成你自己的检测方案你拿到的这套代码是基于 yolo11 的双分支融合结构但大多数人做毕业设计或项目落地时不会只用原版融合。拿到代码后怎么改才能变成自己的东西我给三个方向每个方向都有明确验证指标。第一个方向是换融合模块。目前用的是 cross_attention你可以把它换成 BiFPN 加权融合或者自适应空间融合模块。改法很简单在 models/fusion.py 里再写一个类实现同样的 forward 接口然后在 detector.py 或配置里的 fusion 字段替换即可。验证方法是对比换模块前后验证集 mAP50 是否提升至少不能掉超过 1 个百分点。第二个方向是换数据集场景这是训练自己的数据时需要做的。比如你要做车辆识别就把 data.yaml 里的类别名改成 car、bus、truck标注文件夹里的 txt 也按 YOLO 格式整理好。注意红外图像如果是从行车记录仪拿的热成像很多是 16 位原始数据要先把数据转成 8 位灰度并做归一化。一个常见的操作是用自适应直方图均衡化 CLAHE 增强后再训练IR 分支的输入质量会直接影响融合效果。第三个方向是把 IR 分支替换成其它模态。比如你手里的资源不是红外图而是深度图把 IR 分支的输入从单通道灰度改成单通道深度值预处理部分把 depth 图归一化到 0 到 1 即可。模型结构完全不用动因为单通道输入的卷积层天然适配深度图。这个验证方法非常直观看看加了深度图后模型的 mAP50 是否高于仅使用 RGB 的模型。验证环节我习惯的做法是做一个四组对照实验单 RGB、单 IR、RGB 加 IR 拼接、RGB 加 IR 双分支融合。四组用完全相同的数据划分、训练轮数和随机种子只在模型结构上有差异。对照组能一次性回答三个问题IR 到底有没有用、双分支比拼接强在哪、你这个场景适不适合上多模态。如果实验结果里单 IR 就很有优势说明场景本身对热信息敏感多模态融合价值更大如果 RGB 和 IR 提升不明显那说明这个场景里两个模态本身信息冗余高融合收益自然有限。我从那以后每次拿到新的检测任务都会先跑一组简单的对照组再决定是否值得上复杂的融合结构。这套双分支 yolo11 的优势在于把每条改进路径都留好了接口你不需要把整个模型推倒重来改一个融合模块、换一种输入模态就能形成自己的改进方案。希望这份拆解能帮你把项目跑起来也能在里面找到属于自己的创新点。本文还有配套的精品资源点击获取