yolo26双流模型设计
yolo26双流模型设计
ytkz不过,在动手修改 ultralytics 源码之前,我们需要先认清一个现实问题: 如果 Global 图像是局部区域放大 3~5 倍再下采样回来的,那么原本在 Local 图中清晰可见的目标,在 Global 图里可能会变成只有几个像素的“噪点”。如果你依然用精确的边界框(BBox)作为 Global 标签去强制模型检测,效果往往很差,甚至会带偏网络。
因此,针对你要在华北地区进行大规模旱地提取这类的任务,我建议将 Global 标签设计为“场景级分类标签(Scene Classification)”或“粗粒度掩码(Coarse Mask)”,而不是 BBox。
最简单的“Global 影响 Local”的模型逻辑是:全局输出一个概率权重,直接惩罚或增强局部的置信度。 比如,Global 分支判断这张大图所在的 5km 范围内根本没有农田(概率 0.01),那么 Local 分支里任何疑似农田的检测框置信度都会被乘以 0.01,直接过滤掉虚警。
在 ultralytics 这种高度封装的框架里,硬改数据流(让 forward 接收两个输入)会牵扯到 DataLoader、Trainer、Validator 等几百处修改,非常痛苦。
这里教你一个实战中极其好用的“Hack”技巧:通道拼接(Channel Stacking)。
第一步:数据层面的“偷梁换柱”(通道拼接)
在数据准备或 DataLoader 阶段,不要传入两张 [3, 512, 512] 的图,而是将它们在通道维度拼接,变成一张 [6, 512, 512] 的图(前 3 个通道是 Local,后 3 个通道是 Global)。
这样,ultralytics 的所有数据增强(Mosaic、随机翻转、缩放)都会完美且同步地作用在这 6 个通道上,你一行底层代码都不用改。
至于 Global 标签,你可以把它作为一个额外的值附带在目标的 cls 后面,或者在计算 Loss 的时候根据 Local 标签动态生成(如果 Local 有目标,Global 就设为 1)。
第二步:修改网络结构(自定义 Module)
我们需要在 ultralytics/nn/modules/block.py 中新增一个处理 6 通道输入的双流融合模块。
以下是基于你“Global 结果影响 Local 结果”理念的简单模型代码:
Python
import torch
import torch.nn as nn
class DualStreamStem(nn.Module):
"""
放置在 YOLO 网络最前端的 Stem 层。
输入为 6 通道图像 [B, 6, H, W]
"""
def __init__(self, c1=6, c2=64):
super().__init__()
# 处理 Local 的 3 通道
self.local_stem = nn.Sequential(
nn.Conv2d(3, c2, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(c2),
nn.SiLU()
)
# 处理 Global 的 3 通道 (甚至可以下采样得更狠一点)
self.global_stem = nn.Sequential(
nn.Conv2d(3, c2 // 2, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(c2 // 2),
nn.SiLU()
)
# 将特征融合回标准的 YOLO 通道数,供后续 Backbone 使用
self.fusion = nn.Conv2d(c2 + c2 // 2, c2, kernel_size=1)
def forward(self, x):
# 拆分通道:前3个是局部,后3个是全局
x_local = x[:, :3, :, :]
x_global = x[:, 3:, :, :]
feat_local = self.local_stem(x_local)
feat_global = self.global_stem(x_global)
# 将全局特征拼接到局部特征上
out = torch.cat([feat_local, feat_global], dim=1)
return self.fusion(out)
如果想在检测头(Head)末端让 Global 的“结果”直接影响 Local 的“结果”,我们可以设计一个并行的 Global 分类头:
Python
class GlobalGuidedDetect(nn.Module):
"""
放置在检测头部分的后处理/融合模块。
假设我们提取了深层的 Global 特征,让它输出一个场景概率。
"""
def __init__(self, global_feat_dim, num_classes):
super().__init__()
# Global 分支直接输出各个类别的“场景存在概率”
self.global_classifier = nn.Sequential(
nn.AdaptiveAvgPool2d(1), # 全局池化
nn.Flatten(),
nn.Linear(global_feat_dim, num_classes),
nn.Sigmoid() # 输出 0~1 的概率
)
def forward(self, local_preds, global_feats):
"""
local_preds: YOLO 原本输出的检测框和分类置信度
global_feats: Global 分支提取的特征图
"""
# [B, num_classes] -> [B, num_classes, 1] 以便广播相乘
scene_prob = self.global_classifier(global_feats).unsqueeze(-1)
# 核心逻辑:用 Global 的场景概率去抑制 Local 的置信度
# 假设 local_preds 的结构中,包含了类别分数(视具体 YOLO 版本格式而定)
# 这里仅为逻辑演示:
# local_class_scores = local_class_scores * scene_prob
return local_preds, scene_prob # 将 scene_prob 也返回,用于计算 Global Loss
第三步:修改 YAML 配置文件
你需要在你的 yolo.yaml 中,把第一层(0层)替换为你自定义的 DualStreamStem,并在最后把 Head 替换为能处理 Global 特征的结构。
第四步:修改 Loss 函数 (ultralytics/utils/loss.py)
因为你说过 Global 也需要标签,所以你需要修改损失函数:
- 提取出
scene_prob(Global 结果)。 - 从 targets 中提取出 Global 的标签(比如整图是否包含该类别的 0/1 向量)。
- 增加一个
BCE Loss (Binary Cross Entropy):loss_global = nn.BCELoss()(scene_prob, global_targets)。 - 总 Loss =
Loss_YOLO + alpha * loss_global。
通过这种“6通道输入 + 早期特征融合 / 晚期概率抑制”的方式,你可以避开 ultralytics 底层复杂的流转逻辑,用最少的代码修改实现双流网络。




