news 2026/10/5 4:02:50

YOLOv11多任务联合训练:检测分割计数一体的数据与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv11多任务联合训练:检测分割计数一体的数据与工程实践

简介:面向计算机视觉算法工程师与研究者的YOLOv11多任务联合训练方案PDF文档,充分发挥YOLOv11单阶段检测速度快、精度高的特性,系统讲解如何在一个框架内同时完成目标检测、图像分割与目标计数。文档共48页,从YOLOv11网络结构、多尺度融合和检测头设计讲起,深入分析多任务学习的特征共享、任务分支与损失函数组合,并覆盖数据集标注、预处理、模型训练、评估指标及调优细节,理论与实践并重。包内为单个PDF文件,资源包2.2MB,支持章节跳转与大纲定位,阅读方便;目录涵盖YOLOv11概述、多任务框架设计、检测分割计数融合、联合训练、实验对比和智能交通/工业质检/安防监控等应用案例。实验部分包含mAP、mIoU与计数误差指标的对比分析,有助于读者理解任务间的相互促进效果。目前已有123人学习下载,适合正在探索YOLOv11实战落地的开发者作为完整技术参考。

1. 多任务YOLOv11联合训练:检测、分割、计数放进同一个模型,难的不是网络,是数据装配

第一次接到“既要框出目标、又要给出轮廓、还要统计数量”这种需求时,我下意识以为要外挂三个模型,被性能和部署成本直接劝退。实际上 YOLOv11 的多任务框架在模型侧已经把检测和分割合在了一条推理链里,backbone 和 neck 全程共享,分割头在检测头旁并行展开;所谓的计数,在绝大多数项目里根本不是独立网络分支,而是对检测框或分割掩码做的一次严格统计。把三件事拼进同一个模型,真正的成本集中在数据装配、类别均衡和掩码后处理上。这篇笔记按“标签怎么做—训练怎么调—计数怎么验—坑在哪里”的顺序讲一遍,适合做工业缺陷检测、车辆计数、医学息肉计数这类“检测+分割+数量”同时要出结果的工程。

2. 数据装配是第一步:把检测框、分割掩码与计数需求写进一份训练集

多任务方案里最容易翻车的不是训练参数,而是数据本身。YOLOv11 的检测和分割虽然是联合训练,但标签格式并不兼容:检测只要一个框,分割要一条闭合多边形。更麻烦的是,计数需求往往会逼你在“每个目标都要有掩码”这个前提下回看旧数据集——很多公开集只给了检测框,比如车辆检测数据集 BDD100K,拿来训检测绰绰有余,要做分割就得自己补掩码。所以第一步不是跑通模型,而是把三份需求统一到一套数据文件里。

2.1 标签文件的长相:YOLO检测标签与YOLO分割标签的差异

先看最基础的检测标签。YOLO 格式的 txt 里每一行对应一个目标,结构是:

class_id cx cy w h

四个坐标值全部是相对图片宽高的归一化比例,cx 和 cy 是中心点,w 和 h 是框的宽高。注意这里没有绝对像素值,一旦出现大于 1 的数,说明标注工具或转换脚本没有归一化,训练时 loss 会忽大忽小,但你很难第一时间定位到是标签的问题。

分割标签在同一份 txt 里换了一种排列方式:

class_id x1 y1 x2 y2 ... xn yn

首元素仍然是类别 id,后面跟着的是目标轮廓多边形的顶点坐标,依然是归一化比例。顶点按顺时针或逆时针排都可以,但必须沿轮廓连续走,不能跳点。这种格式携带了两份信息:多边形本身画出了掩码轮廓,而多边形的外接矩形天然就是检测框的监督信号,这就是 YOLOv11 能同时训检测头和分割头的原因。

我一般会在训练前跑一遍全量标签脚本,先肉眼确认格式没坏:

from pathlib import Path import numpy as np def inspect_seg_label(txt_path: Path) -> bool: """检查一个YOLO分割标签的基本合法性,返回True表示通过""" lines = [l.strip() for l in txt_path.read_text().splitlines() if l.strip()] if not lines: return False # 空标签,允许存在但别意外 for idx, line in enumerate(lines): parts = line.split() cls_id = int(parts[0]) coords = np.array(parts[1:], dtype=float) # 至少3个点才能构成多边形,即坐标数量 >= 6 if coords.size < 6: print(f"{txt_path.name} 第{idx+1}行点数不足: {coords.size // 2}个点") return False if coords.min() < 0 or coords.max() > 1: print(f"{txt_path.name} 第{idx+1}行坐标越界: {coords.min():.2f} ~ {coords.max():.2f}") return False if cls_id < 0: print(f"{txt_path.name} 第{idx+1}行类别id为负") return False return True

这个脚本只做三件事:点数是否够形成闭合多边形、坐标是否在 0~1 之间、类别 id 是否非法。跑下来不是百分百验证,但能过滤掉最常见的标注导出翻车问题。检测和分割标签混在一个文件里时,最值得警惕的是坐标维度不齐,因为 YOLO 检测标签是 4 个坐标,分割标签是偶数个坐标,有些脚本错把检测标签当成分割标签读,会直接让训练崩溃。

2.2 从VOC/COCO转成YOLOv11分割格式:坐标归一化与多边形点序的边界

如果你的掩码标注来自 COCO 或 LabelMe,拿到的是绝对像素坐标的多边形。转换时最常踩的坑是:直接套用检测框的归一化公式,把整条多边形除以图片宽高,这个方向对,但细节没到位。COCO 里有些目标的掩码是多个环组成的,比如一个被遮挡的目标被人工补了两个分离的轮廓,这种情况下每个环都会单独占一行的标注,如果合并成一行,多边形会自交,YOLOv11 读取时可能不报错,但生成的掩码会变成一块错乱的马赛克。

转换脚本的核心逻辑大致是:

import json from pathlib import Path def coco_polygon_to_yolo_seg(img_width: int, img_height: int, polygon: list) -> str: # polygon是COCO annotations里的segmentation,通常是绝对像素坐标的列表 pts = [] for i in range(0, len(polygon), 2): x = polygon[i] / img_width y = polygon[i + 1] / img_height x = min(max(x, 0.0), 1.0) y = min(max(y, 0.0), 1.0) pts.append(f"{x:.6f} {y:.6f}") return " ".join(pts) def convert_coco_json(json_path: str, out_dir: str) -> None: with open(json_path) as f: data = json.load(f) for ann in data["annotations"]: seg = ann["segmentation"] if isinstance(seg, dict): # RLE格式,需要先decode成多边形,这里只处理polygon格式 continue if ann["iscrowd"]: continue # crowd标注在计数场景必须丢弃,否则会重复数人 polygon = seg[0] # 只取第一个环,多环目标要拆成多行 line = f"{ann['category_id']} " + coco_polygon_to_yolo_seg( data["images"][ann["image_id"]]["width"], data["images"][ann["image_id"]]["height"], polygon, ) img_info = data["images"][ann["image_id"]] out_txt = Path(out_dir) / (Path(img_info["file_name"]).stem + ".txt") with open(out_txt, "a") as f: f.write(line + "\n")

为什么强调多环要拆行?因为 YOLOv11 的分割标签按“每行一个目标实例”读,多环合并会让两个不相连的轮廓被当成一个目标,计数差一倍。这正好引出计数场景的一个铁律:在标注环节就要想清楚“一个目标可能被遮挡后不连通,但你仍然只数一次”。如果项目允许,我会在标注规范里约定“被遮挡严重的只标可见主轮廓”,宁可舍弃边缘目标,也不让标注员硬补点,因为补出来的环大概率破坏计数口径。

2.3 计数的数据层准备:类别均衡与验证集划分,避免训练集聪明验证集翻车

多任务框架下,检测框的分类、掩码的轮廓质量和计数的准确性共用同一套标签,但不同任务的难度差异很大。检测框可以容忍略微偏移,掩码对边界误差更敏感,而计数直接受 conf 阈值影响。因此验证集必须按“图”划分,不能按“目标”随机抽,否则同一张图出现在训练集和验证集,掩码一眼看去挺准,但真实场景泛化完全不行。

我在切分数据集时会额外做一次分层检查:确保每个类别的样本图像在 train 和 val 中的分布比例接近整体分布。工业场景像轴承缺陷检测、齿轮检测这类需求,如果某类样本本身就少,又随机切分,很容易出现 val 里全是稀有小类,mAP 看起来还行,计数却完全失控。

import random from pathlib import Path from collections import defaultdict def split_by_class_stratified(img_paths: list, labels_dir: Path, val_ratio: float = 0.2): class_to_images = defaultdict(list) for img in img_paths: label_file = labels_dir / (img.stem + ".txt") if not label_file.exists(): continue classes = set() for line in label_file.read_text().splitlines(): if line.strip(): classes.add(int(line.split()[0])) for c in classes: class_to_images[c].append(img) # 按类别维度独立计算val集,防止某类在val中缺失 val_set = set() for c, imgs in class_to_images.items(): random.shuffle(imgs) val_count = max(1, int(len(imgs) * val_ratio)) val_set.update(imgs[:val_count]) train_list = [str(p) for p in img_paths if p not in val_set] val_list = [str(p) for p in img_paths if p in val_set] return train_list, val_list

这段代码的核心价值在于:同一个图像文件可能包含多个类别,只要它出现在某一个类别的候选集里,就会进入 val,保证数量少的那一类在验证阶段不会被彻底漏掉。这一步做完,再谈训练参数才有意义。数据层面最后一件事是确认数据装载路径里没有新旧缓存打架,ultralytics 会在数据集目录下生成 labels.cache,改了标签后忘记删它,训练会读旧缓存,这是多说无益但每次都会坑一批人的经典问题。

3. 联合训练的模型配置与超参数:用yolo11s-seg让检测头和分割头同时训收敛

数据就位后才轮到模型。YOLOv11 在网络结构上的演进重点集中在 backbone 和注意力模块,网上能找到各种版本的 yolo11 网络结构图,实际到多任务场景,我们更关心的是它怎么把检测和分割组织成一条链路。简单地说,分割头在检测头的位置之外额外输出了一层掩码分支,这层分支不改变检测头的输出结构,只是在训练时多算一份分割损失,所以你可以理解为“检测能力没被牺牲,分割是叠加的红利”。

3.1 模型选型:为什么从分割预训练权重开始而不是从检测权重硬转

ultralytics 官方发布 YOLOv11 分割权重时,后缀带 seg,例如 yolo11n-seg.pt、yolo11s-seg.pt,它们是在 COCO 上同时训练过检测和分割的权重。我做过测试:从 yolo11s-seg.pt 做迁移学习,第一个 epoch 的掩码轮廓就基本成型;如果从纯检测权重改 network head 硬转,前几个 epoch 分割 loss 降得很慢不说,还容易把已经收敛的检测头拉回震荡状态。

工业用户常问“用 yolo11n 不就行了吗”,这里有个严肃权衡。n 系列的参数量小,检测框也许够用,但掩码质量对参数量极其敏感,分割头输出的掩码分辨率由 backbone 下采样倍率决定,小模型学到的轮廓精细度明显差一截。肉眼可能看不太出来,但计数场景依赖掩码面积计算占比时,粗糙边缘造成的面积抖动会很麻烦。我一般把 n 系列留给边缘设备原型验证,真正落地从 s 起步,工业缺陷检测这类对轮廓有要求的项目甚至直接上 m 系列。

环境配置这块网上有大量超详细的教程,我自己踩过一遍之后的建议就是:锁定 ultralytics 的版本号再装其他依赖,跟做 0 基础入门教程走没问题,但千万别用最新版 Python 跑老版本包。

3.2 训练命令与关键参数:batch、imgsz与优化器对两个头的影响

准备好 data.yaml 之后,训练命令本身并不复杂:

# datasets/defect_count/data.yaml path: ./datasets/defect_count train: images/train val: images/val # 注意:这个names的顺序就是训练时的类别id,对应标签txt里的第一个数字 names: 0: scratch 1: dent 2: stain
yolo segment train \ data=datasets/defect_count/data.yaml \ model=yolo11s-seg.pt \ epochs=120 \ imgsz=640 \ batch=8 \ device=0 \ workers=4 \ optimizer=SGD \ lr0=0.01 \ lrf=0.001 \ patience=20 \ project=runs/segment \ name=multi_task_defect

逐条说关键参数的含义。imgsz 默认 640,但它直接影响掩码的精细度;输入分辨率越高,分割头的输出 mask 保留的空间细节越多,代价是显存占用非线性上涨。batch 需要根据显存调整,我用 24GB 显存跑 s 系列 640 分辨率,batch 通常开 8~12,再大就触发 OOM,损失反而跳得厉害。optimizer 我选 SGD 而不是 AdamW,原因有两个:SGD 在这种小数据集上不容易过拟合,而且多任务下 AdamW 的学习率敏感度高,一旦调不合适,box 和 seg 的收敛互相压制。lr0 按 batch 做线性缩放,batch 翻倍学习率可以跟着翻倍。

还有一个常被忽略的参数是 patience,多任务训练特别容易在一百多个 epoch 里出现“先涨后跌再涨”的波浪,没有合适的早停条件会白白烧显卡时间。patience 设 20 的意思是连续 20 个 epoch 验证集指标没刷新记录就停。

3.3 训练日志怎么读:box、cls、dfl、seg四条损失曲线代表什么

训练过程中会同时打印四条 loss:box_loss、cls_loss、dfl_loss、seg_loss。前三个是检测头在框回归、目标分类、框分布三个维度上的损失,seg_loss 是分割头对掩码预测的损失。不要盯着总 loss 看,因为默认情况下它们的量级不一致,总 loss 下降不代表每一个子任务都在收敛。

我判断联合训练是否健康有一套顺序:第一个看 cls_loss 是否在前 20 个 epoch 内迅速下降,这是分类器在适应你的类别分布;第二个看 box_loss 是否稳定下降,震荡剧烈说明学习率偏大或标签有误;第三个再看 seg_loss,掩码收敛本来就慢,它在下行趋势里偶尔反弹是正常的,但持续 30 个 epoch 不降就得怀疑标注多边形有噪声。四条曲线里最容易被忽略的是 dfl_loss,它反映的是框边缘的置信度分布,多任务框架下框的质量会直接影响 mask 对齐,dfl_loss 一直不降时,先检查数据集里有没有大量小目标。

训练完了别只看最后的 mAP50-95,要单独把分割能力抽出来验证,用 val 集里的一张图直接看 mask 渲染结果,这一步能暴露很多指标无法反映的问题,比如目标被截断时掩码只剩半截。

3.4 中断续训与早停:多任务训练的“后悔药”和止损线

训练中断在长周期多任务项目里不是小概率事件。显存不够被系统杀掉、机房断电、远程终端断开导致进程退掉,几百块显卡的工程几乎都会碰到。ultralytics 的 run 目录里会持续保存 last.pt,中断后只需要在原命令基础上加一行参数:

yolo segment train \ data=datasets/defect_count/data.yaml \ model=runs/segment/multi_task_defect/weights/last.pt \ resume=True

resume 模式会从上次断点继续,epoch 计数、优化器状态、学习率调度都会还原。我在实际项目里把这条命令当成保命手段,训练脚本永远带着 resume=True 启动,哪怕没有中断它也会从最佳断点继续跑。另外要说的是早停和断点的关系:ultralytics 的 best.pt 保存的是验证集上指标最优的权重,而不是 final epoch 的权重,所以在 120 epoch 的配置里耐心让早停机制裁掉后段过拟合,最终产物质量反而更稳。

4. 推理与计数落地:从分割掩码到精确计数的后处理链路

联合训练结束,模型权重已经能同时输出检测框、类别置信度和掩码,但这距离“计数”交付还有一步。很多人拿到 best.pt 直接 len(boxes) 就宣称计数完成,这在稀疏场景下可能没问题,到了密集遮挡环境必翻车。计数本质上是一个后处理工程,它的准确度跟你如何消费掩码、如何设置阈值、如何过滤碎片直接相关。

4.1 保存推理结果:带掩码可视化与原始mask数据的区别

先理清推理结果长什么样。用一行的预测脚本:

from ultralytics import YOLO model = YOLO("runs/segment/multi_task_defect/weights/best.pt") results = model.predict( source="datasets/defect_count/images/val/0001.jpg", conf=0.35, save=True, # 保存带框和掩码渲染的jpg save_txt=True, # 保存检测框的txt,格式和训练标签一致 save_conf=True, # 在txt里追加置信度列 ) result = results[0] boxes = result.boxes masks = result.masks print(f"检测框数量: {len(boxes)}")

这里的关键区别是:save=True 保存的是一张“画好了”的渲染图,用于人眼检查;真正要用来做后处理的不是这张图,而是 result.masks 里的原始数据,它是一个张量,每个目标对应一个 H×W 的 float 矩阵,每个像素取值范围 0~1。你可以单独把每个目标的 mask 取出来存成 PNG,也可以直接转成二值掩码做连通域计算。

保存推理结果的另一个细节是 save_txt 生成的文件用的是检测框坐标,不是掩码多边形,如果需要保存分割结果,要调用 result.masks.xy 拿到每个目标的多边形顶点,再按训练标签的格式写回文件。很多人在这一步以为 save_txt 已经输出 mask 了,实际上输出的仍然是检测框,后续做数据迭代时容易拿错标注。

4.2 计数的三种做法:检测框计数、掩码实例计数、轨迹去重计数

第一种是检测框计数,直接统计 boxes 的个数。这种做法在目标稀疏、重叠可控的场景下完全够用,比如工业零件逐个经过镜头。它的缺点是依赖 conf 阈值,两张图中同一个目标因为光线变化置信度从 0.4 掉到 0.3,恰好越过阈值差值,计数就会少一个。

第二种是掩码实例计数,统计 masks 的张量数量。因为分割头输出的每个 mask 对应一个实例感知的目标,在目标互相重叠时它能区分“一个目标被部分遮挡”和“两个目标连成一块”。但掩码计数对分割质量要求高,如果两个目标挨得太近且掩码边缘捱在一起,分割头可能把它们合并成一个 mask 实例,计数直接少一。

第三种是轨迹去重计数,适合视频流场景。模型在每一帧给的计数结果要跨帧去重,否则同一个目标重复进出画面就重复计数,通常做法是配一个轻量跟踪器,把检测框关联成轨迹,按轨迹 ID 计数。这部分我一般加在项目后期,前两种方案撑不住时上跟踪。值得警惕的是不要在一张静态图里强行上跟踪方案,那是绕远路。

4.3 计数准确性的验证:用连通域分析过滤碎片掩码

分割掩码输出并不是直接拿来数数就完事,掩码里常常混着预测置信度低于阈值的碎片以及背景误激活的区域。这些碎片面积可能只有几十像素,肉眼在渲染图上根本注意不到,但计数时会被算成一个实例。

常见做法是对每个目标的掩码做一次连通域过滤,面积小于设定值的目标直接淘汰:

import cv2 import numpy as np def filter_masks_by_area(masks, min_area=64): """输入masks.data,返回过滤后的有效掩码索引和面积信息""" valid_indices = [] areas = [] for i in range(len(masks)): mask = masks.data[i].cpu().numpy() # (H, W),值在0~1之间 binary = (mask > 0.5).astype(np.uint8) # 按置信度二值化 # 连通域分析,标记相邻像素,返回每块的面积 num_labels, labels, stats, _ = cv2.connectedComponentsWithStats( binary, connectivity=8 ) # stats[0]永远是背景,统计从1开始 largest_area = 0 for j in range(1, num_labels): if stats[j, 4] > largest_area: largest_area = stats[j, 4] if largest_area >= min_area: valid_indices.append(i) areas.append(largest_area) return valid_indices, areas

这个脚本做的事情是:把每个目标的连续掩码按 8 连通拆解成若干区域,取其中最大的一块面积作为这个目标的“有效面积”。如果最大面积仍然小于 min_area,认为它是不合格的碎片,剔除计数。参数 min_area 没有万能值,我通常先拿验证集里最小真实目标的像素面积做参考,设定成它的 1/4 或 1/2。

最后强调一个验证思路:业务上要的是“数准”,不是指标高。我建议单独做一个计数验证集,记录每张图的真实数量,然后统计预测数量的平均绝对误差。mAP50 再高,如果 MAE 偏高,说明你模型的框置信度分布和业务阈值不匹配,这时候优先调 conf 而不是调训练参数,这是多任务方案里最容易走偏的一个环节。

5. 多任务方案最容易踩坑的六个地方:标注、类别、显存、小目标与重复计数

联合训练项目做得多了,你会发现模型本身相对稳定,真正让你加班到深夜的往往是数据、缓存和显存这些看起来跟 AI 没有直接关系的细节。

5.1 多边形标注不规范导致训练中断

现象:训练到第二个 epoch 突然报错,提示 segmentation label 格式不正确,程序退出。翻日志能看到某一行标签读取失败。

原因:标注导出时混入了点数不足三个的多边形,或者多边形坐标没有归一化,写成像素值。检测标签即使坐标不标准也能硬训,但分割标签一旦出现畸点多边形,RLE 变换会直接失效。

解决:训练前跑一遍 2.1 节的检查脚本,异常文件单独归档重标。别指望训练过程自动跳过,ultralytics 在数据装配阶段就会整体崩掉。另外注意多实例环,COCO 标注里 iscrowd 字段为 1 的目标在转格式时直接丢弃,否则行人密集场景会数出两个连体目标。

5.2 类别id错位引发分割掩码串类

现象:训练完成后发现 scratch 类的目标有一部分被分割成 dent 类的掩码,检测框的类别是对的,但掩码区域着色明显张冠李戴。

原因:data.yaml 的 names 顺序和标签 txt 里的 class_id 对不上。常见触发点是有人在训练中途重排过 names,而旧标签文件没同步更新;另一种从 COCO 转格式时直接沿用原 dataset 的 category_id,COCO 的背景类 id 通常从 90 或某一个大数开始,被簇拥进了训练集。

解决:把 data.yaml 的 names 顺序作为唯一事实来源,标签里的 id 必须逐一对应。写一个全数据集标签扫描脚本,将出现过的类别 id 集合打印出来,跟 names 做 diff,这一步能拦截绝大多数错位问题。

5.3 显存溢出导致训练中断

现象:batch 设 16 训练正常,换成单卡后同样参数直接 CUDA out of memory,训练进程被杀。

原因:批大小和输入分辨率是显存占用的两个最大变量。训练日志能跑完前几步不代表没有隐患,评估阶段在最后几个 epoch 临时加载更大的特征图,才会在验收前一小时爆显存。

解决:先用自动批大小探测一次,观察日志里它建议的 batch 值,再手动下调一档。也可以加梯度累积逻辑,等效增大了 batch 又不涨显存,但注意学习率要按等效 batch 缩放。显存不足时千万别通过降低 imgsz 硬扛,因为掩码质量直接被打折,宁肯缩小 batch。

5.4 小目标掩码被下采样吃掉

现象:小目标检测框能出来,计数也能数对,但 mask 边缘锯齿严重,小缺陷目标比如轴承表面的细小划痕,掩码几乎变成一块噪声点,面积完全失真。

原因:YOLOv11 分割头输出的掩码分辨率受 backbone 下采样倍率限制,640 输入下,一个小目标投影到特征图上可能只剩几个像素,多任务头的掩码分支很难还原细节。网上讨论 yolov11 小目标优化,多数方案都围绕同一件事:提高输入分辨率或者切图推理。

解决:这类场景把 imgsz 提到 1280 试一版,代价是训练和推理时间显著上升;更可控的做法是用切片推理,把大图切成无重叠的小块分别预测再合并,结合 3.2 节的脚本控制分数阈值。注意切图推理的拼接区会出现同一目标被切成两半后计数重复,要加一次"跨图去重",或让切片之间保留少量重叠,重叠区域内按 IoU 合并预测框。

5.5 同一目标被重复计数

现象:密集场景里模型输出两个高度重叠的框,内部实际上是一个目标,计数多一。静态图里展示得最明显,一个行人的肩膀位置出现第二个 0.6 置信度的框。

原因:NMS 对类别内重复的框确实会抑制,但跨类别的重叠框是漏网之鱼——同一个目标同时被判断成 scratch 和 dent,NMS 不会对跨类别的两个框做抑制,计数自然翻倍。

解决:在计数后处理里加一次跨类别去重,计算不同类别框之间的 IoU,如果 IoU 大于 0.5 且其中一个置信度明显更高,就丢弃另一个。本质上这是业务规则,不是训练参数,我一般把它放进推理脚本的过滤逻辑里,不放到训练阶段去改 loss。

5.6 验证集mAP不错但计数准确度差

现象:训练日志里 mAP50 已经到 0.92,看起来很不错,但用业务测试图一跑,计数误差偏离真实值 30% 以上。

原因:mAP 是各类别在所有 IoU 阈值下的均值指标,反映了"检测框是否框对了位置",而计数只关心"查全率和置信度阈值"两个维度。模型对目标置信度普遍偏低时,mAP 可能不差,但业务代码里设了固定 conf=0.4,大量 0.35~0.4 的目标被过滤掉。

解决:不要拿 mAP 当计数的验收标准。单独构造计数验证集,统计不同 conf 阈值下的计数 MAE,选 MAE 最低的那个 conf 作为业务阈值。这个操作极其朴素,但大部分转到多任务框架的人都没做,等交付时才被甲方拿真实场景照片打脸。

6. 最后一公里:模型导出、批量推理与吞吐量验证的落地习惯

训练和推理验证都通过以后,剩下的就是把模型从开发环境搬进生产流程。我先讲常见的导出路径,再讲一个我自己的验证习惯。

导出 ONNX 是跨平台部署最常见的做法,命令很简单:

yolo export model=runs/segment/multi_task_defect/weights/best.pt \ format=onnx \ opset=12 \ imgsz=640 \ simplify=True

导出后的 onnx 可以在 CPU 上跑,可以用 TensorRT 加速。这一过程中最容易出现的坑是动态尺寸支持,默认导出的模型固定输入 640×640,一旦业务图像不是这个比例,需要先做 letterbox 再喂给模型。我通常直接按固定尺寸导出,避免动态 shape 带来的额外优化负担和推理抖动。

落地环节的另一个关键指标是吞吐量,简称“每秒能处理多少张图”。不要用官方 benchmark 的数字来验收,也不要在模型导出前测具体路数,因为 ONNX 转 TensorRT 引擎后的 fps 和浮点模型完全不是一个量级。我一般会在自己机器上跑一轮固定分辨率、固定 batch 的耗时测试:批次大小从 1 递增到业务实际需要的并发数,记录每批延迟,找到延迟增量开始变得不线性的拐点。这个拐点基本就是这台设备的合理吞吐上限。如果你要做多路实时视频流,每一路都要留出至少 30% 的算力余量,否则编码解码的信号过来时,推理延迟会叠加成肉眼可见的卡顿。

我自己在几个项目里已经不太关心模型结构本身怎么魔改,而把更多精力放在产出前的两件事上:一是换数据集重训前一定清缓存,二是每次训练结束立刻把 best.pt 在真实部署机器上跑一遍典型图,记录推理耗时和计数结果。前者防止你训练了半天其实在读旧标签,后者防止你在开发机上满怀信心,到了生产机一测才发现导出后的精度被损坏。

衷心希望这篇多任务框架的落地笔记能帮你省下几个通宵调试的夜晚。如果一个方案让你陷入玄学调参,先回到数据和缓存去排查,大概率真正的答案就藏在那两个最不起眼的地方。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 4:01:51

WebView内嵌H5密码安全:JS注入+原生随机键盘实战方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 4:01:27

多模态决策模型:从感知理解到可执行动作的工业级闭环

1. 这不是又一个“多模态大模型”&#xff0c;而是决策链路里缺了十年的那块拼图最近刷到Perplexity开源pplx-decider-27b、同步上线Decisions API的消息&#xff0c;朋友圈里好几条转发都写着“Perplexity终于下场做多模态了”。我盯着标题看了三分钟——不对&#xff0c;这根…

作者头像 李华
网站建设 2026/10/5 4:01:02

STM32 SPI接口读写SD卡与FATFS文件系统移植实战

1. 项目整体设计与方案选型做这个项目的起因很直接&#xff1a;要给一块老掉牙的STM32F103开发板加一个数据记录功能&#xff0c;现场没有屏幕也没有上位机&#xff0c;最稳妥的办法就是把传感器数据写到SD卡里&#xff0c;回头把卡拔出来插电脑上看。最开始我图省事想过用SDIO…

作者头像 李华
网站建设 2026/10/5 4:01:02

H∞鲁棒控制MATLAB仿真全流程:从不确定性建模到闭环验证

写这篇文章的念头&#xff0c;源于我最近帮一位做机电系统的朋友排查控制问题。他调了一个月的PID参数&#xff0c;在标称工况下响应漂亮得无懈可击&#xff0c;结果换了一批负载、环境温度一变化&#xff0c;系统直接振荡发散。这其实是鲁棒性问题里最典型的一个场景——你设计…

作者头像 李华
网站建设 2026/10/5 4:01:00

HBM带宽如何决定智能体并发规模

1. 这不是芯片参数表&#xff0c;而是一份智能体规模的“水电容量”预估报告你可能已经看过不少关于HBM&#xff08;高带宽内存&#xff09;的技术解析——堆叠层数、TSV孔密度、微凸点间距、带宽计算公式……但这次&#xff0c;我们得换个视角&#xff1a;把HBM看作一种算力基…

作者头像 李华
网站建设 2026/10/5 3:59:52

JitWord实测:国产系统上协同AI文档的部署与应用

最近一个月&#xff0c;我在测试环境里反复折腾了几款面向国产操作系统的协同文档产品。说句实话&#xff0c;以前这套组合拳打下来体验很折磨&#xff1a;麒麟系统上能装WPS&#xff0c;但你想让团队多人同时编辑一份文档、想让AI帮你起草方案初稿、想在内网环境里把文档权限精…

作者头像 李华