ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

YOLOv11多任务学习:目标检测与实例分割工程实践

YOLOv11多任务学习:目标检测与实例分割工程实践 简介这份PDF文档面向计算机视觉方向的学习者与工程开发者聚焦多任务学习框架下YOLOv11同时实现目标检测与实例分割的完整工程实践适合具备一定深度学习基础、希望掌握多任务模型融合设计与落地流程的中高级读者。文档共39页以单个PDF文件形式打包压缩包约2.19MB支持目录章节跳转、阅读器左侧大纲显示与章节快速定位查阅体验较为顺畅。内容从多任务学习、目标检测与实例分割基础讲起梳理YOLO系列发展脉络与YOLOv11骨干、颈部、头部架构进而展开特征共享机制、检测与分割双分支设计及多任务损失权衡并覆盖环境搭建、数据准备、模型训练、评估与部署全流程配合代码实现解析与COCO及自定义数据集实验结果分析还整理了常见问题与解决方案。目前已有99人学习可为读者提供从原理到工程落地的系统参考。1. 从一次产线误检说起这份 YOLOv11 多任务文档到底解决什么问题上个月帮朋友看一条包装线质检相机拍到的是堆叠的零件既要框出每个零件的位置又要给出每个零件的像素轮廓来算面积。他们原本跑两个模型一个检测、一个分割两张卡各跑一个推理延迟叠起来接近 90ms节拍根本压不住。后来换成单模型多任务一次前向同时出框和掩码延迟直接砍到 40ms 出头。这份《多任务学习框架-YOLOv11同时实现目标检测与实例分割的工程实践》讲的就是这条路——用 YOLOv11 的共享骨干加双分支头把目标检测和实例分割塞进一次推理里。它适合三类人正在被「检测分割两套模型」拖慢推理的工程同学想入门多任务学习但不知道从哪下手的算法新手以及需要一份能照着改配置、跑训练、做部署的完整流程参考的从业者。文档 39 页带目录跳转和左侧大纲结构是「基础理论 → 框架概述 → 融合设计 → 工程步骤 → 代码解析 → 实验分析 → 常见问题」不是纯论文落点在工程实践。下面我按自己拆项目的习惯把它拆成能直接抄的部分和容易翻车的部分。2. 多任务学习为什么选硬参数共享YOLOv11 双分支的融合逻辑2.1 检测和分割为什么能塞进一个模型目标检测输出的是边界框加类别实例分割输出的是每个实例的像素掩码。表面看是两个任务但底层依赖的特征高度重合边缘、纹理、目标轮廓这些既是定位需要的也是分割需要的。文档里把这种关系讲得很直白——实例分割可以看作在检测基础上对目标的进一步精细化处理。既然底层特征通用就没必要让两个网络各算一遍。多任务学习有两种主流做法。硬参数共享是共享前面的网络层每个任务挂自己独立的输出头软参数共享是各任务独立建网靠正则化约束参数接近。YOLOv11 走的是硬参数共享原因很实际共享骨干和颈部网络能实打实减少计算量推理时只跑一次特征提取两个头并行出结果。软参数共享虽然灵活但参数量和显存占用都上去了对产线这种延迟敏感场景不划算。提示硬参数共享的前提是任务相关。检测和分割相关性足够强共享才有效如果硬把两个不相关的任务绑一起反而会互相拖累。2.2 共享在哪、独立在哪骨干与颈部的分工文档把共享机制拆成两层。骨干网络负责提取通用特征早期卷积层抓边缘纹理深层抓语义这些特征检测和分割都要用。颈部网络用 FPN 加 PAN 做多尺度融合FPN 自顶向下把高层语义传下来PAN 再加一条自底向上的路径增强传递融合后的多尺度特征同样被两个任务共享。独立的部分在头部。检测分支预测边界框偏移、类别概率和置信度分割分支预测每个实例的掩码。文档给了一段简化的骨干网络代码能看清共享层长什么样import torch import torch.nn as nn class BasicConv(nn.Module): def __init__(self, in_channels, out_channels, kernel_size, stride1): super(BasicConv, self).__init__() # padding 取 kernel_size//2保证 stride1 时特征图尺寸不变 self.conv nn.Conv2d(in_channels, out_channels, kernel_size, stride, kernel_size // 2, biasFalse) self.bn nn.BatchNorm2d(out_channels) self.relu nn.LeakyReLU(0.1) # YOLO 系列常用 LeakyReLU负半轴保留小梯度 def forward(self, x): x self.conv(x) x self.bn(x) x self.relu(x) return x class Backbone(nn.Module): def __init__(self): super(Backbone, self).__init__() self.conv1 BasicConv(3, 32, 3, 1) # 浅层抓边缘纹理 self.conv2 BasicConv(32, 64, 3, 2) # 下采样扩大感受野 def forward(self, x): x self.conv1(x) x self.conv2(x) return x input_image torch.randn(1, 3, 416, 416) backbone Backbone() features backbone(input_image) print(features.shape)这段代码里BasicConv封装了卷积、BN、激活三件套biasFalse是因为后面接了 BN偏置会被 BN 的均值抵消省掉能少一点参数。Backbone只搭了两层做演示真实 YOLOv11 骨干要深得多但共享逻辑是一样的features这个输出会同时喂给检测头和分割头。参数上stride2那层把 416 下采样到 208特征图尺寸减半、通道翻倍这是标准的金字塔下采样节奏。2.3 颈部融合FPN 加 PAN 怎么把多尺度特征拼起来颈部是共享机制里最容易被忽略但最影响小目标效果的一环。文档给的 FPN 简化实现值得逐行看import torch import torch.nn as nn class FPN(nn.Module): def __init__(self, in_channels_list, out_channels): super(FPN, self).__init__() self.lateral_convs nn.ModuleList() # 横向 1x1 卷积统一通道数 self.top_down_convs nn.ModuleList() # 融合后的 3x3 卷积平滑特征 for in_channels in in_channels_list: self.lateral_convs.append(nn.Conv2d(in_channels, out_channels, 1)) self.top_down_convs.append(nn.Conv2d(out_channels, out_channels, 3, padding1)) def forward(self, features): # 先把各层特征用 1x1 卷积压到同一通道数 laterals [lateral_conv(feature) for lateral_conv, feature in zip(self.lateral_convs, features)] last_lateral laterals[-1] outputs [last_lateral] # 自顶向下高层上采样后与低层相加 for i in range(len(laterals) - 2, -1, -1): last_lateral nn.functional.interpolate(last_lateral, scale_factor2, modenearest) last_lateral last_lateral laterals[i] last_lateral self.top_down_convs[i](last_lateral) outputs.insert(0, last_lateral) return outputs in_channels_list [64, 128, 256] out_channels 256 fpn FPN(in_channels_list, out_channels) input_features [torch.randn(1, c, 52, 52) for c in in_channels_list] output_features fpn(input_features) for feature in output_features: print(feature.shape)lateral_convs用 1x1 卷积把不同通道数的特征统一到out_channels这是融合的前提通道不一致没法直接相加。interpolate用modenearest而不是双线性是因为最近邻上采样在检测任务里更常用速度快且不会引入额外插值伪影。outputs.insert(0, ...)保证输出顺序是从高分辨率到低分辨率方便后面按尺度分配锚框。参数上in_channels_list要和你骨干实际输出的通道对齐写错了直接报维度不匹配这是新手最常见的翻车点之一。3. 从零跑通训练环境、数据、配置三步落地3.1 环境搭建CUDA 版本和 PyTorch 的对应关系文档把环境搭建放在工程实践第一步顺序是硬件选型、软件安装、代码仓库获取。硬件上训练建议单卡显存 12G 起步分割分支比纯检测多吃显存batch size 要相应调小。软件这块最容易卡住的是 CUDA 和 PyTorch 的版本对应装错了要么torch.cuda.is_available()返回 False要么训练中途报算子不支持。常见做法是先确定显卡驱动支持的 CUDA 上限再选对应的 PyTorch 版本。比如驱动支持到 CUDA 12.1就装 cu121 版本的 PyTorch不要硬上更新的。装完用下面这段验证python -c import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))三行输出分别确认 PyTorch 版本、CUDA 是否可用、显卡型号。如果第二行是 False先别急着重装用nvidia-smi看驱动是否正常再看 PyTorch 装的是不是 CPU 版。很多人pip install torch默认拉的是 CPU 轮子这是血泪经验里排第一的坑。3.2 数据准备标注格式和划分比例数据准备分四步选数据集、标注、划分、增强。检测加分割的标注比纯检测麻烦因为每个实例除了框还要有掩码。常见格式是 COCO 的 polygon 或 RLE标注工具用 Labelme 或 CVAT 都行导出时注意选对格式。划分比例上文档没给死数字但工程上一般按 8:1:1 或 7:2:1 切训练、验证、测试。小数据集可以上交叉验证但多任务场景下交叉验证成本高不太推荐。数据增强要注意翻转、缩放、色彩抖动对检测和分割都安全但涉及几何形变的增强比如随机裁剪要保证掩码同步变换否则掩码和图像对不上训练直接学歪。注意增强后的掩码如果出现全零或者越界训练时 loss 会异常。建议增强后抽样可视化几张确认框和掩码还贴合。3.3 配置文件修改与训练启动YOLOv11 的训练入口是配置文件加训练脚本。配置文件里要改的关键项包括数据集路径、类别数、模型规模、batch size、学习率。类别数改错是最隐蔽的坑——检测头输出维度对不上报错信息还不一定直白。# 训练启动示例参数按自己数据集调整 yolo tasksegment modetrain modelyolo11n-seg.yaml datacustom.yaml epochs100 imgsz640 batch8tasksegment表示走分割任务YOLOv11 的分割模型本身就带检测输出所以一条命令同时训两个头。model选yolo11n-seg.yaml是从头训如果做迁移学习就换成预训练权重.pt。imgsz640是输入分辨率小目标多可以提到 1280但显存和速度要重新权衡。batch8是保守值显存够可以往上加加到报 OOM 再退一档。训练过程监控主要看三个量box loss、seg loss、mAP。两个 loss 应该同步下降如果一个降一个不降说明任务权重失衡需要调损失函数的权衡系数。文档在损失函数设计那节专门讲了多任务损失的权衡检测用 GIoU 类损失分割用掩码 BCE 或 Dice两者加权求和权重比是超参。4. 代码结构拆解数据、模型、训练、评估、部署五个模块4.1 数据处理模块加载、预处理、DataLoader文档把代码分成五个模块数据处理排第一。数据集加载要同时读图像、框、掩码预处理阶段做归一化和尺寸对齐DataLoader 负责批处理和打乱。关键点是掩码的 resize 必须用最近邻用双线性会把二值掩码插值成灰度训练时标签就不对了。from torch.utils.data import Dataset, DataLoader import cv2 import numpy as np class SegDataset(Dataset): def __init__(self, img_paths, label_paths, img_size640): self.img_paths img_paths self.label_paths label_paths self.img_size img_size def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img cv2.imread(self.img_paths[idx]) img cv2.resize(img, (self.img_size, self.img_size)) img img[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 # BGR转RGB、归一化 mask cv2.imread(self.label_paths[idx], cv2.IMREAD_GRAYSCALE) # 掩码必须用最近邻保持二值性质 mask cv2.resize(mask, (self.img_size, self.img_size), interpolationcv2.INTER_NEAREST) return img, mask dataset SegDataset([img1.jpg], [mask1.png]) loader DataLoader(dataset, batch_size4, shuffleTrue, num_workers2)img[:, :, ::-1]把 OpenCV 默认的 BGR 转成 RGB顺序错了颜色通道就反了模型照样能训但精度会掉。transpose(2, 0, 1)把 HWC 转成 CHW这是 PyTorch 的要求。num_workers2是数据加载的并行进程数设太大会抢内存设 0 会拖慢训练。4.2 模型定义模块骨干、颈部、头部怎么拼模型定义模块把骨干、颈部、头部串起来。骨干提特征颈部做融合头部出双分支结果。文档给的头部代码里检测分支和分割分支是并列的两个卷积头共享颈部输出。这里要注意的是分割分支的输入——文档用的是基于检测结果从特征图里抠对应区域再送掩码预测器这种设计叫「检测引导分割」好处是掩码只算在目标区域省算力。class MaskPredictor(nn.Module): def __init__(self, in_channels, num_classes): super(MaskPredictor, self).__init__() self.conv1 nn.Conv2d(in_channels, 256, 3, padding1) self.relu nn.ReLU() self.conv2 nn.Conv2d(256, num_classes, 1) # 每个类别一张掩码 def forward(self, x): x self.conv1(x) x self.relu(x) mask_pred self.conv2(x) return mask_predconv2输出通道等于类别数意味着每个类别独立预测一张掩码图最后按检测到的类别取对应通道。这种设计在类别多的时候参数量会涨但逻辑清晰调试方便。4.3 训练与评估模块损失、优化器、指标计算训练模块的核心是损失函数和优化器。检测损失用 GIoU 加分类交叉熵分割损失用掩码 BCE两者加权。优化器一般用 SGD 加动量或 AdamWYOLO 系列传统上用 SGD收敛稳但慢AdamW 收敛快但需要调 weight decay。评估模块算 mAP 和 mIoU。mAP 衡量检测mIoU 衡量分割。文档提到评估结果不稳定常见原因是验证集太小或者 batch 里样本分布不均。解决办法是增大验证集或者评估时固定随机种子。# 评估指标计算示意 def compute_iou(pred_mask, gt_mask): intersection (pred_mask gt_mask).sum() union (pred_mask | gt_mask).sum() return intersection / (union 1e-6) # 加小量防除零1e-6是防止 union 为零时除零报错这种小保护在工程代码里很常见不加的话遇到空掩码直接崩。5. 避坑与排查训练和部署里最容易翻车的五件事5.1 损失不收敛两个任务互相拖累现象box loss 下降但 seg loss 震荡或者反过来训练几十轮 mAP 卡住不动。原因多任务损失权重没调好一个任务的梯度压过了另一个。解决先把两个损失权重设成 1:1 跑几轮看趋势哪个任务收敛慢就适当调高它的权重或者用不确定性加权让模型自己学权重。文档在损失函数权衡那节给了思路但具体系数要按数据集试。5.2 过拟合训练集 mAP 高验证集掉现象训练集指标一路涨验证集到某个点开始跌。原因数据量不够或者模型太大。解决先上增强翻转、缩放、色彩抖动都加上再考虑换小模型比如从 yolo11l 降到 yolo11s还不行就加 dropout 或 weight decay。多任务场景下过拟合比单任务来得晚因为两个任务互为正则但不能因此掉以轻心。5.3 掩码和框对不上增强同步问题现象训练时 loss 正常但推理出来的掩码偏移或者形状不对。原因数据增强时图像和掩码变换不同步尤其是随机裁剪和旋转。解决用支持同步变换的增强库或者自己写增强时保证图像和掩码走同一套变换矩阵。这个坑很隐蔽因为训练 loss 不一定报错但模型学到的就是错的对应关系。5.4 模型转换失败导出格式和算子支持现象训练好的权重转 ONNX 或 TensorRT 时报算子不支持。原因分割分支里有些操作比如动态 shape 的 interpolate在目标格式里没有对应实现。解决导出时固定输入尺寸把动态操作换成静态或者查目标推理框架的算子支持列表提前避开不支持的算子。文档在部署那节提到模型转换失败这是最常见的原因。5.5 部署平台性能不足显存和延迟双超现象模型在服务器上跑得好好的挪到边缘设备上延迟翻倍甚至 OOM。原因边缘设备算力和显存都有限原模型没做量化或剪枝。解决先做 FP16 量化精度掉得不多但显存和延迟都能降还不够就上 INT8但要校准数据集再不行就换更小的模型规模。部署平台选择那节讲了平台适配但量化这步文档没展开实际工程里绕不开。6. 进阶技巧用不确定性加权稳住多任务损失再验证一遍多任务学习里最玄学的是损失权重。手工调权重靠试换数据集就得重调。进阶做法是用不确定性加权让模型自己学每个任务的权重。原理是把每个任务的损失除以该任务的不确定性参数再加 log 项做正则训练时这个参数也跟着更新。这样收敛慢的任务会自动获得更高权重不用人肉调。import torch import torch.nn as nn class UncertaintyWeighting(nn.Module): def __init__(self, num_tasks2): super(UncertaintyWeighting, self).__init__() # 每个任务一个可学习的 log 方差参数 self.log_vars nn.Parameter(torch.zeros(num_tasks)) def forward(self, losses): total 0 for i, loss in enumerate(losses): # 精度项loss / (2 * sigma^2)正则项log(sigma) precision torch.exp(-self.log_vars[i]) total precision * loss self.log_vars[i] return total # 用法检测损失和分割损失传进来自动加权 weighting UncertaintyWeighting(num_tasks2) det_loss torch.tensor(1.2) seg_loss torch.tensor(0.8) total_loss weighting([det_loss, seg_loss]) print(total_loss)log_vars初始化为零对应 sigma 为 1训练初期两个任务权重接近。precision * loss是精度加权log_vars[i]是正则项防止方差无限增大。这个方法的代价是多了一组参数要学训练前期可能不稳定建议先用手工权重跑通再换。验证方法上训练完别只看最终 mAP要分尺度看。文档在实验结果分析里分了不同尺度目标的检测结果这个习惯值得抄。小目标 mAP 低是常态但如果小目标 mAP 掉得特别厉害回头查颈部融合和输入分辨率。推理速度验证要在目标部署平台上测别在训练服务器上测完就下结论两者差一倍都正常。从那以后我每次跑多任务训练都强制先用手工权重 1:1 跑 10 轮看两个 loss 的趋势确认没有互相拖累再上不确定性加权。这个习惯帮我省了不少返工时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表