新闻详情

新闻详情

首页 / 资讯中心 / 详情

YOLOv11多任务框架:检测、分割、计数一模型搞定

发布时间:2026/9/30 5:45:57来源:尧图网络
YOLOv11多任务框架:检测、分割、计数一模型搞定
简介PDF 文档围绕 YOLOv11 单阶段检测算法系统讲解如何基于同一模型同时完成目标检测、图像分割与目标计数三项任务适合已有一定 YOLO 基础的研究者、算法工程师与高校学生。文档共 48 页从 YOLOv11 网络结构与创新点引入逐步展开多任务学习原理、共享特征层与任务特定分支设计、多任务损失函数组合方法以及检测、分割、计数三类任务的融合策略针对联合训练实践还梳理了数据集选择与标注、数据增强、优化器与学习率调整以及 mAP、mIoU、MAE 等评估指标的应用要点。全文涵盖智能交通、工业质检、安防监控、农业、医疗、零售、教育等九类应用场景便于读者对照迁移。资源包共 1 个文件PDF 格式约 2.2MB支持目录章节跳转与阅读器左侧大纲快速定位。目前已有 123 人学习适合需要搭建多任务一体化解决方案、关注检测-分割-计数联合训练细节的读者。1. 多任务框架与YOLOv11为什么同时做检测、分割、计数能替代三套模型做工业质检或者密集目标统计的时候最头疼的不是单个模型精度而是检测、分割、计数各跑一套流程。检测模型给框分割模型给掩码计数再做一遍连通域分析三个模型串联链路延迟翻三倍误差还一路叠加。多任务框架的 YOLOv11 就是把检测和分割的 backbone 共享再挂一个计数分支用一份联合训练的数据让一个模型同时输出框、掩码和数量。这方案适合输出指标既要位置、又要像素级轮廓、还要总数的场景比如细胞统计、料盘针脚检测、颗粒计数、自动易拉罐计数。它最大的价值不是省了一个模型而是让检测和分割的特征互相补充尤其是小目标密集场景比单独训练两个模型更稳。2. YOLOv11 多任务改法网络结构、头部分叉与特征共享2.1 从 YOLOv11-Seg 看多任务头怎么长出来先说一个常见的误解多任务不是把三个模型塞进一个可执行文件里而是让一个主干网络Backbone和特征金字塔Neck共享计算图只在最后分叉出不同粒度的输出头。YOLOv11 的网络结构沿用 Backbone Neck Head 的经典设计Backbone 由 C3k2 和 SPPF 堆叠输出不同尺度的特征Neck 用 PAN-FPN 做特征融合检测头在不同特征层上做 anchor-free 的边框回归和分类。如果你看过 YOLOv11 的分割版本会发现它并没有重新设计一个主干而是在检测头的旁边并列了一个分割头检测头出的是框和类别分割头出的是图像掩码的原型prototypes和每个实例对应的系数coefficients推理时用矩阵乘法把两者组合成原始尺寸的掩码。这个思路是我们可以直接复用的。为什么要复用 backbone因为检测框和分割掩码本来就需要同一批底层语义边缘、纹理、遮挡边界。检测头教给 backone 的知识是“目标在那里”分割头教的是“目标占据哪些像素”两个损失从不同角度提供梯度对小目标的召回往往比单头更好。计数头如果再复用同一份特征那三个任务等于在同一个信息瓶颈上做多次读取代价是特征需要更宽所以实际结构里会把 P4、P5 层的通道数适当加大我一般会在 width_multiple 上从 0.5 调整到 0.75。2.2 计数头的两种挂载方式Box 回归头与密度图头计数头的挂载方式直接影响训练难度。第一种最简单直接统计检测头的有效框数量不需要额外分支但计数质量完全取决于检测框的召回重叠目标经过 NMS 后会被吞掉密集场景容易漏。第二种是给分割头再并一个 1x1 卷积输出一张密度图每个像素代表该位置的目标密度对整张图求和就是总数也可以直接回归一个 count 标量但从工程上看密度图更容易训练而且能感知空间分布不会像标量回归那样只看整体数。在联合训练框架中我推荐密度图头。原因是它的梯度能传到特征图的每个位置对 backone 的贡献比单点 count 回归更充分。密度图计数还有一个额外收益当检测框因为遮挡漏检时密度图还在持续贡献梯度模型不会完全放弃那个区域。如果遇到的是高密度场景比如细胞或者粉末颗粒密度图常是唯一能保住计数的方案如果场景是料盘针脚这种可分离目标直接统计检测框也很稳。具体怎么选后面第 5 章会给一张对比表。2.3 用 YAML 配置定义多任务模型结构常见的做法是直接在 YOLOv11 的模型定义上改良。YOLOv11 本身用 yaml 描述结构检测头的注册名是 Detect分割版的注册名是 Segment计数头没有现成定义需要改 task code。下面给一个可复用的 PyTorch 多任务头示意以单层特征图为例实际使用时需要对 P3/P4/P5 三层各做一次import torch import torch.nn as nn class MultiTaskHead(nn.Module): def __init__(self, in_channels, num_classes, mask_proto32, density_feat64): super().__init__() # 先用一个卷积做特征提纯减少任务之间的梯度冲突 self.shared nn.Sequential( nn.Conv2d(in_channels, in_channels, 3, padding1, biasFalse), nn.BatchNorm2d(in_channels), nn.ReLU(inplaceTrue), ) # 检测分支回归框和类别 self.box nn.Conv2d(in_channels, 4, 1) self.cls nn.Conv2d(in_channels, num_classes, 1) # 分割分支生成掩码原型和系数 self.proto nn.Conv2d(in_channels, mask_proto, 3, padding1) self.coef nn.Conv2d(in_channels, mask_proto, 1) # 计数分支密度图回归 self.density nn.Sequential( nn.Conv2d(in_channels, density_feat, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(density_feat, 1, 1), ) def forward(self, x): feat self.shared(x) box_out self.box(feat) # [B, 4, H, W] cls_out self.cls(feat) # [B, C, H, W] proto self.proto(x) # [B, mask_proto, H, W] coef self.coef(feat) # [B, mask_proto, H, W] density self.density(feat) # [B, 1, H, W] return box_out, cls_out, proto, coef, density逻辑说明这里用一个 shared 卷积先做特征提纯目的是让三个任务在共享分支上先完成一次信息融合避免检测头的梯度直接把计数头的梯度冲散。分割头的 proto 和 coef 在推理时通过 Einstein 求和还原成掩码例如mask torch.einsum(bphw,bcp-bcHW, coef, proto)。参数说明mask_proto是原型通道数通常取 32大了显存涨得快小了掩码边缘糊density_feat取 64 够用密度图分支本身不追求高分辨率因为它的监督是高斯核生成的目标图精细到像素没有意义。3. 联合训练数据管道检测框、掩码与计数标签的对齐3.1 三种标注的来源和归一化联合训练的前提是同一张图上同时存在框、掩码和计数标签这三类数据的来源往往不同。常见的情况是你有一份目标检测标注x1, y1, x2, y2一份实例分割标注polygon 或者 RLE计数标签则可以自动从掩码实例数里算出来。怎么对齐我的顺序是优先保留实例分割标注因为从多边形可以直接算外接矩形得到检测框再从掩码数量得到 count。反过来不行检测框无法还原像素级掩码。所以数据管道的入口是语义分割 / 实例分割标注文件而不是检测框文件。归一化时要注意坐标系。COCO 的 polygon 坐标是绝对像素YOLO 系列的 txt 标注要求归一化到 0~1。如果 mask 是多边形转 YOLO-seg 格式时要除以图像宽高如果 mask 是 RLE需要先解码成 0/255 的掩码图再通过 cv2.findContours 转成多边形。计数标签不需要归一化但建议做一步 clip如果掩码因为遮挡或者标注边界切碎一个目标可能被标成两段直接按掩码实例数算会多计这一步最好人工检查一遍。3.2 转成 YOLOv11 多任务训练格式的脚本下面是我常用的一段转换脚本输入是 COCO 风格的 JSON输出是 YOLOv11 训练用的 txt 和掩码目录。它同时生成检测框标签和掩码多边形并顺带算出每张图的count标签import json, cv2, numpy as np from pathlib import Path def coco_to_yolo_multitask(coco_json, image_dir, out_dir): with open(coco_json, r, encodingutf-8) as f: coco json.load(f) img_id_to_info {img[id]: img for img in coco[images]} ann_by_img {} for ann in coco[annotations]: ann_by_img.setdefault(ann[image_id], []).append(ann) cat_id_to_cls {cat[id]: i for i, cat in enumerate(coco[categories])} for img_id, anns in ann_by_img.items(): img img_id_to_info[img_id] h, w img[height], img[width] lines [] count 0 for ann in anns: cls cat_id_to_cls[ann[category_id]] # segmentation 是 polygon 列表或 RLE seg ann[segmentation] if isinstance(seg, list): poly np.array(seg[0], dtypenp.float32).reshape(-1, 2) else: mask coco_rle_to_binary(seg, w, h) # 需另写解码函数 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) poly contours[0].squeeze().astype(np.float32) # YOLO 格式归一化坐标 poly_norm poly / np.array([w, h], dtypenp.float32) xs poly_norm[:, 0].tolist() ys poly_norm[:, 1].tolist() line [str(cls)] [f{xs[i]:.6f} for i in range(len(xs))] \ [f{ys[i]:.6f} for i in range(len(ys))] lines.append( .join(line)) # 同时计算检测框从归一化多边形取外接矩形 x_c (poly_norm[:, 0].min() poly_norm[:, 0].max()) / 2 y_c (poly_norm[:, 1].min() poly_norm[:, 1].max()) / 2 bbox_w poly_norm[:, 0].max() - poly_norm[:, 0].min() bbox_h poly_norm[:, 1].max() - poly_norm[:, 1].min() # 注意这里将 box 也写入另一个文件便于检测头读取 box_line f{cls} {x_c:.6f} {y_c:.6f} {bbox_w:.6f} {bbox_h:.6f} save_box(out_dir, img[file_name], box_line) count 1 save_seg_txt(out_dir, img[file_name], lines) save_count(out_dir, img[file_name], count) # 每图一个txt内容是数字逻辑说明这段脚本的核心是让三个标签从同一个 annotation 生成保证框、掩码、计数天然对齐。参数说明img[file_name]只是主键最终保存时统一用image_id做文件名避免不同子目录重名。额外的 box txt 文件是给只读检测头的分支用的如果你直接去掉 box 层、完全从掩码生成框也可以省掉这一步但不推荐因为检测头需要有独立监督否则会退化成随分支。3.3 数据增强必须同步掩码边界坑如果数据增强只对图像做左右翻转和缩放而掩码和框不跟着变那训练时模型看到的监督就是错的。常见做法是用 albumentations 的 Compose 同时传入bboxes和masks它保证同一种几何变换应用到所有目标。但这还不够计数标签也需要重新算。翻转和缩放不会改变实例数量但随机裁剪会裁剪掉半个目标掩码可能只剩面积小于阈值的碎片这时候你怎么计数我的习惯是在增强函数里对每个 mask 计算变换后的实例数只有掩码面积大于原始 30% 的才保留并且把 count 重新赋值为保留实例数。import albumentations as A def get_train_transform(target_size640): return A.Compose([ A.RandomSizedBBoxSafeCrop(widthtarget_size, heighttarget_size, erosion_rate0.2), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]), mask_paramsA.MaskParams(masks_as_masksTrue))逻辑说明RandomSizedBBoxSafeCrop保证裁剪区域至少包含一个完整的目标避免有的图被裁成空图。mask_params会让 albumentations 对掩码应用和我们图像完全一致的变换这一点是手工同步最容易出 bug 的地方。参数说明erosion_rate0.2表示允许裁剪区域损失部分目标边缘但不允许整个目标被切没masks_masks_as_masksTrue表示输入的 mask 是 0/255 的单通道数组不要转成 float否则后续计算实例数时阈值会出错。4. 联合训练配置与损失平衡让三个任务不打架4.1 多任务损失组合与初始权重联合训练最典型的失败是检测收敛得很好但分割 mask 只有一团噪点count 则一直震荡。问题出在损失函数搭配。常见的 YOLOv11 检测损失是box_CIoU cls_bce分割分支可以加mask_bce mask_dice计数分支加count_mse或density_mae。把它们直接相加时检测损失的量级通常远大于 mask 损失和 count 损失梯度被检测主导后面两个任务学不动。我一般会用这样的初始公式total_loss ( 1.0 * (box_loss cls_loss) 0.5 * mask_loss 0.2 * density_loss )逻辑说明检测任务作为主任务权重最高分割其次计数最低。原因是计数结果在多数业务里是一种汇总统计允许推理后处理纠正但框和掩码错了整个输出就没法用。参数说明density_loss权重从 0.2 起步如果观察到 mask 质量好但 count 偏差大可以逐步提到 0.5如果 mask 一直不收敛说明训练阶段 mask_loss 被抑制需要先提高它到 1.0让每一步梯度都能穿透。4.2 动态 loss 权重从 GradNorm 到不确定性加权固定权重只能碰运气因为训练到后期三个任务的难度变化不同。常见的做法是用不确定性加权拿噪声方差作为置信度损失越大的任务自动降权。但实现稍麻烦实战中更稳的是分阶段手动调整前 20 个 epoch 用固定权重后 20 个 epoch 把 count 权重逐渐提上来因为那时检测和分割已经稳定不会抢梯度。如果你想把权重也纳入训练可以用 GradNorm 的思想计算三个任务损失的梯度范数然后乘一个权重让各任务的梯度范数接近。我踩过这个坑GradNorm 在小 batch size 下非常不稳定训练前 500 步 loss 直接飞掉。后来我改成用验证集上每个任务的 mAP 和 Dice 动态调整权重哪个任务涨得慢就把它的权重往上调 10%。这属于“土办法”但比纯理论可靠而且能解释给项目上的人听。4.3 训练超参调整batch size、学习率与 YOLOv11 默认值差异YOLOv11 的默认训练配置是为单任务检测设计的搬到多任务时只改权重还不够。我用过的实际参数对比表如下参数单任务 YOLOv11 常见值多任务联合训练建议原因imgsz640640 或 768太小掩码边缘模糊太大显存爆炸batch size168 或 4显存被 mask prototype 和密度图占掉lr00.010.005多任务梯度冲突更频繁学习率要降低epochs100150 以上分割和计数需要更多 epoch 才稳定optimizerSGDAdamWAdamW 对多任务梯度的二阶动量更友好warmup epochs35给三个头更充分的热身期参数说明多任务训练时 batch size 降低直接影响 BN 的统计量所以我会把batch_norm_epsilon稍微调大一点或者固定前 20 个 epoch 的 BN 参数。还有一个常见做法是先用单任务 YOLOv11 检测预训练权重冻结 backbone 跑 10 个 epoch等分割头和计数头的随机输出不再把 loss 拉崩再解冻联合训练。5. 计数分支避坑三种计数方案与 5 个常见翻车点5.1 方案选型框计数、连通域计数、密度图计数的边界同样是计数三条路线各有适用范围选错代价很大。我把它们列成一张对比表方案适用场景精度瓶颈实际代价统计检测框数量目标稀疏、不重叠NMS 抑制掉相邻框实现成本最低直接复用检测头掩码连通域计数目标之间有空隙粘连掩码被连成一个域需要用分水岭或边界 loss 辅助密度图计数高密度、大量堆叠高斯核半径影响峰值合并需要额外计算密度标签我的经验是如果场景是 opencv 硬币检测与计数那种大小统一、边界分离的直接统计检测框就够了还省一个分支。如果是轨道零件、电子元件这种有规则间隔的用掩码连通域计数配合测距修正重复计数。如果是细胞、颗粒这种大量贴合堆积的密度图是唯一能稳定收敛的方案但要花大量时间调高斯核半径。5.2 现象模型只收敛检测分割和计数纹丝不动原因检测 loss 量级太大梯度被主任务吞掉。解决先冻结检测头单独让分割头和计数头用 mask loss 和 density loss 训练 2 个 epoch观察 loss 有没有下降然后再打开检测头用 4.1 的权重做联合训练。如果冻结后分割 loss 也不降问题就出在 backone 特征没解冻设置 backone 的 lr 乘 0.1 但不要冻结。5.3 现象训练正常推理时掩码粘连导致分割计数少算原因训练数据的 mask 边界标注粗糙两个紧挨目标的 mask 边缘重叠连通域分析把两个实例合并成一个。解决在 mask loss 里加一个边界惩罚项对 mask 边缘像素的监督权重大于内部像素推理时不要直接对整张 mask 做连通域先做形态学腐蚀去掉桥接再用cv2.connectedComponentsWithStats按面积阈值过滤。5.4 现象数据增强后 count 标签和实际目标数对不上原因随机裁剪把目标切成两半但 count 依然用原始标签里的实例数。解决增强后重新统计掩码实例数并丢弃面积小于阈值的碎片。我在 3.3 已经说过这一步要写在 transform 之后而不是原始数据读取时。5.5 现象推理时检测框数量和计数分支输出差一大截原因计数分支是回归任务训练数据统计量分布不均匀时它倾向于输出训练集的平均总数而不是当前图的真实数。解决不要拿计数分支的输出当最终结果把它作为辅助信号最终 count 用检测框统计或掩码连通域数。如果一定要用密度图每条图单独做一次 min-max 归一化再求和减少整体亮度对计数结果的影响。6. 推理部署与后处理把三个输出统一成一份结果6.1 推理 pipelineNMS、掩码还原与连通域计数模型训练完后部署阶段的坑一点也不比训练少。YOLOv11 的输出头在不同特征层上检测框需要先解码再 NMS分割掩码需要从 prototype 和系数生成计数密度图需要从下采样尺寸 resize 回原图。下面是一段精简的推理后处理流程import cv2, torch, numpy as np def postprocess(outputs, orig_shape, conf_thres0.25, iou_thres0.45): box_out, cls_out, proto, coef, density outputs # 1. 解码检测框YOLOv11 是 anchor-free直接用特征图映射到原图 boxes, confs, clss decode_boxes(box_out, cls_out, orig_shape) keep nms(boxes, confs, clss, conf_thres, iou_thres) boxes, confs, clss boxes[keep], confs[keep], clss[keep] # 2. 生成掩码coef 是每个实例的系数proto 是全局原型 masks torch.einsum(bphw,bcp-bcHW, coef, proto) masks torch.sigmoid(masks) # resize 到原图并二值化 final_masks [] for i in range(masks.shape[1]): mask masks[0, i].cpu().numpy() mask cv2.resize(mask, (orig_shape[1], orig_shape[0])) final_masks.append((mask 0.5).astype(np.uint8)) # 3. 使用连通域统计作为最终计数 counts [] for mask in final_masks: num, labels, stats, _ cv2.connectedComponentsWithStats(mask, connectivity8) valid [s for s in stats[1:] if s[4] 50] # 过滤噪声 counts.append(len(valid)) # 4. 密度图求和作为辅助验证 density_map density[0, 0].cpu().numpy() density_map cv2.resize(density_map, (orig_shape[1], orig_shape[0])) density_count float(density_map.sum()) return boxes, final_masks, counts, density_count逻辑说明NMS 之前的所有操作在模型特征层坐标系解码时要把下采样倍率乘以特征图尺寸掩码 resize 用双线性阈值不用传统的 0.5而是 0.45因为分割网络的预测边界不是硬边界取低一点能让掩码覆盖更完整。参数说明connectedComponentsWithStats的stats第 4 列是像素面积过滤阈值 50 是按业务调的如果用预训练权重建议按验证集统计面积分布再定。6.2 计数结果校准按置信度阈值与业务绝对数校准多任务框架的 count 输出和业务口径往往不完全一致。比如“数量”是按完整目标算但模型预测出许多半截切片连通域统计会多算。我的终校准习惯是在验证集上做一次density_count和真实总数的线性回归得到一个比例系数推理时把密度图求和再乘以系数。这个系数在不同相机角度下会变所以不要只标定一次。另外一个容易踩的坑是保存推理结果时只写模型 raw output不做后处理。实际部署项目里用户要的是最终掩码图和总数不是一个 tensor。我会把检测框、掩码、密度图、count 全部写回 JSON并附上图像尺寸和模型版本避免后面换模型后对不上。6.3 从 ONNX 到 TensorRT多任务头导出注意事项多任务模型的导出最大的问题是输出头数量多且掩码生成有矩阵乘法。ONNX 导出时要把einsum拆成MatMul否则一些推理框架不支持。TensorRT 部署时建议把密度图头固定成单通道输出不要画蛇添足加 softmax。我自己在 Orin 和 Jetson 上部署 YOLOv11 分割版的经验是多任务头导出 fp16 后性能下降不大但 mask 分支的反卷积对显存要求高建议在 low_complexity 模式下把 mask_proto 降到 16能省接近一半显存。最后说个我的教训一开始把计数分支做成标量回归训练时 loss 降得很快但在验证集上总数就是比人工数老是差 2 和 3因为标量无法感知目标的空间分布。后来换成密度图再用连通域做最终统计计数才真正稳定。这个方向想落地不要迷信模型输出要在后处理上留一手。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

IEEE 802.3cm 400G多模光纤标准解读:SR4.2与SR8物理层参数及设计指南 2026/9/30 7:36:48

IEEE 802.3cm 400G多模光纤标准解读:SR4.2与SR8物理层参数及设计指南

简介:IEEE Std 802.3cm-2020 是 IEEE 发布的以太网修订标准,聚焦多模光纤上 400Gb/s 的物理层与管理参数,面向光模块研发、数据中心网络架构及高速以太网测试工程师。标准新增 Clause 150,定义了 400GBASE-SR8 与 400GBASE-SR4.2 …

阅读更多 →
计算机三级网络技术备考:IP地址规划与路由设计核心知识框架 2026/9/30 7:36:47

计算机三级网络技术备考:IP地址规划与路由设计核心知识框架

简介:这份计算机三级网络技术备考资料面向准备全国计算机等级考试三级网络技术科目的考生,尤其适合需要系统梳理网络原理与工程实践的中高级学习者。资料以PDF文档形式呈现,共1个文件,压缩包约3.35MB,内容围绕网络系统…

阅读更多 →
基于Django+Python的新能源汽车数据分析系统开发实战 2026/9/30 7:36:47

基于Django+Python的新能源汽车数据分析系统开发实战

做毕业设计最怕的不是“难”,而是项目做完你自己都说不清它到底解决了什么问题。这几年我带过的毕设里,凡是做得顺、答辩不被老师追着问、最后还能拿出一套完整作品的,基本都服从同一个规律:选题落点小、数据可获取、技术能闭环。…

阅读更多 →
根分区磁盘空间告急?从诊断清理到LVM扩容全攻略 2026/9/30 7:36:46

根分区磁盘空间告急?从诊断清理到LVM扩容全攻略

挂载根的磁盘空间太小,这次咱们一次性解决只要跑过Linux服务器的人,基本都被“挂载根”的分区容量告警折磨过。df -h一敲,红字跳出来,根分区使用率冲到95%以上,紧接着就是服务无响应、日志写不进去、SSH卡到怀疑人生。…

阅读更多 →
Linux终端复用神器tmux:告别窗口多开焦虑,配置实战全解析 2026/9/30 7:36:46

Linux终端复用神器tmux:告别窗口多开焦虑,配置实战全解析

告别“窗口多开”焦虑:Linux 终端神器 tmux,让你的效率翻倍(附超全实战配置)在 Linux 下干活时间久了,特别是天天泡在终端里的人,基本都会碰到这么几个场景:SSH 连到服务器,跑着一个…

阅读更多 →
基于Django的证券分析系统开发实战:数据采集到K线展示全解析 2026/9/30 7:36:39

基于Django的证券分析系统开发实战:数据采集到K线展示全解析

去年帮一个学弟远程调试这套基于Django的证券分析系统时,我第一次认真审视"毕设全套源码"这类项目的水有多深。他拿到手的源码压缩包超过1GB,解压后光模型迁移文件就有几十个,数据库却是空的,依赖装了三遍还是报错&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉