1. YOLOv8 三种任务共用一个骨干的真实逻辑
第一次把 YOLOv8 的检测、分割、姿态三个权重文件同时跑在一张图上的时候,我盯着输出结果看了很久。同一张图,detect 模型给出框,seg 模型给出框加像素掩码,pose 模型给出框加 17 个骨架点,三种输出结构完全不同,但它们共享的却是同一套骨干网络和颈部结构。这件事本身就值得拆开讲清楚——为什么一个检测框架能同时扛下三种任务,又为什么它在每种任务上的表现都没有明显拖后腿。
先说清楚这三个任务到底在干什么。目标检测解决的是"图里有什么、在哪里",输出的是类别加矩形框;实例分割在检测的基础上再往前一步,要求给每个目标画出像素级的轮廓,同一个类别的两只猫要能分得开;关节点估计则是把人或动物的关键部位定位出来,比如人的鼻子、肩膀、手腕、脚踝,输出的是带置信度的点坐标。这三件事的难度是递进的:检测只要知道范围,分割要知道边界,关节点要知道结构。
传统做法是三个任务三套网络,各训各的。YOLOv8 走的路线是统一骨干 + 任务专属头。骨干负责提取通用特征,颈部负责多尺度融合,最后接一个检测头;要做分割就在检测头旁边挂一个掩码分支,要做姿态就在检测头旁边挂一个关键点分支。这种设计不是 YOLOv8 发明的,但它在工程上把这条路线打磨得足够顺滑,使得你换任务时只需要换权重、换数据配置,代码层面几乎不用动。
我见过不少刚入门的朋友,一上来就纠结"为什么分割模型也能出检测框",其实原因很简单:分割分支是搭在检测分支之上的,没有检测框就没有实例归属,所以分割模型必然自带检测能力。理解这一点,后面很多配置项和输出字段就顺理成章了。
提示:如果你想快速验证三种任务的差异,拿同一张带人的街景图,分别用 yolov8n.pt、yolov8n-seg.pt、yolov8n-pose.pt 推理一遍,对比输出张量的形状,比看十篇原理文章都直观。
三种任务共享骨干带来的直接好处是部署成本低。在边缘设备上,你不需要为每种任务准备一套完全独立的推理管线,骨干部分的算子可以复用,量化策略也可以统一制定。当然代价也存在:分割和姿态头会增加计算量,尤其是分割的原型掩码分支,在高分辨率输入下显存占用并不小,这一点后面会细说。
从热词里能看到一个很典型的诉求——"yolov8 网络结构图"。很多人想要一张图把所有模块标清楚,但只看图容易陷入"每个模块都认识、连起来就懵"的状态。所以接下来我按数据流动的顺序来讲,先讲骨干和颈部这两个三种任务共用的部分,再分别讲三个头各自的原理。
2. 骨干与颈部:C2f、SPPF 和多尺度融合到底在做什么
2.1 C2f 模块相比 C3 改了什么
YOLOv8 的骨干里最显眼的变化是把 YOLOv5 的 C3 模块换成了C2f。C3 的结构是"一路卷积 + 若干 Bottleneck 堆叠 + 拼接",C2f 则把 Bottleneck 的输出全部保留并拼接到最终输出里,而不是只拼最后一路。
这个改动的动机来自 ELAN 的设计思路:让梯度在反向传播时有更多条路径可以走,缓解深层网络的梯度消失。你可以把它类比成"多个支流汇入主干",每条支流都带着自己提取到的特征,主干拿到的是更丰富的组合。代价是通道数变多,但 YOLOv8 通过控制 Bottleneck 的数量和通道扩张比例,把参数量压在了可接受范围内。
实际做轻量化改进的时候,C2f 是第一个被动手的地方。常见的做法是把标准卷积换成深度可分离卷积,或者把 Bottleneck 里的卷积替换成 Ghost 模块。我自己试过在 C2f 里插入 Ghost 卷积,YOLOv8n 的参数量能再降一截,但在小目标上掉点比较明显,所以要看你的数据集里小目标占比有多高。
2.2 SPPF 与 PAN-FPN 的多尺度融合
骨干末端接的是SPPF,作用是增大感受野。它把特征图依次做三次最大池化,池化核大小相同但堆叠起来等效于更大范围的池化,然后拼接。这样做的计算量比直接用大核池化小得多,效果接近。感受野变大的直接好处是模型能"看到"更大范围的上下文,对判断一个大物体的类别很有帮助。
颈部用的是PAN-FPN结构,自顶向下传语义、自底向上传位置,两条路径反复融合。为什么需要这个?因为浅层特征分辨率高、位置信息准,但语义弱;深层特征语义强,但位置信息在多次下采样后已经损失了。检测需要同时知道"这是什么"和"在哪",所以必须把两条信息合起来。
YOLOv8 在 PAN-FPN 里也做了简化,去掉了 YOLOv5 里的一些上采样后的卷积组合,把融合路径做得更短。这个取舍换来的推理速度提升比较可观,尤其是在高分辨率输入下。
2.3 Anchor-Free 和解耦头的设计动机
YOLOv8 是anchor-free的,不再预设一堆锚框。YOLOv5 时代你需要根据数据集跑聚类得到 anchor 尺寸,数据集一换就得重新聚,很麻烦。anchor-free 直接预测中心点到四条边的距离,省掉了这一步。对新手来说,最直观的感受是"训练自己的数据集时不用再纠结 anchor 匹配不上了"。
检测头是解耦的,分类分支和回归分支各走各的卷积。分类关心的是"像不像某个类",回归关心的是"边界在哪",两者关注的特征其实不完全一样。共享一套卷积会让两者互相牵制,拆开之后各自能学到更针对性的特征。这个思路最早在 YOLOX 上被验证有效,YOLOv8 沿用了下来。
注意:解耦头会带来额外的参数量和计算量,在极轻量场景下可以考虑重新耦合回去,但精度通常会掉一点,需要自己权衡。
3. 目标检测头的原理解析与实操要点
3.1 正负样本怎么分配:Task-Aligned Assigner
训练检测模型的核心问题之一是:预测出来的几千个位置里,哪些算正样本、哪些算负样本?分错了,模型就学歪了。
YOLOv8 用的是Task-Aligned Assigner,思路是同时考虑分类得分和回归质量,给每个真实框挑出综合得分最高的若干个预测位置作为正样本。这个"综合"很关键——如果只看 IoU,可能会选到分类很差的预测;只看分类,可能会选到框位置很偏的预测。两者加权对齐之后,选出来的正样本在两方面都过得去。
这个机制带来的实际好处是:不需要像早期 YOLO 那样精心调 anchor 匹配阈值,模型对超参不那么敏感了。我在一些小数据集上试过,即使不改任何默认分配参数,收敛也比 YOLOv5 稳。
3.2 损失函数的三块拼图
YOLOv8 的检测损失由三部分组成:
| 损失项 | 作用对象 | 主要作用 |
|---|---|---|
| BCE Loss | 分类分支 | 判断每个位置属于哪个类别 |
| CIoU Loss | 边框回归 | 让预测框和真实框重叠得更好 |
| DFL Loss | 边框分布 | 把边框回归建模成离散概率分布 |
CIoU 在 IoU 的基础上额外考虑了中心点距离和长宽比,比单纯的 IoU 收敛更快。DFL 是 YOLOv8 比较有特色的地方:它不再直接回归一个连续的距离值,而是让网络在每个方向上输出一组离散概率,最后取期望作为预测值。这样做的好处是回归目标被约束在一个有界区间内,训练更稳定,尤其是对边界模糊的目标。
实际调参时,这三个损失的权重是可以单独调的。如果你的场景里分类混淆严重(比如外观相近的多个类别),可以适当提高分类损失权重;如果框位置总是偏,就关注回归部分。但默认权重在多数场景下都是能用的,不建议一上来就大改。
3.3 训练参数里那几个真正影响结果的
配置文件里参数一大堆,但真正影响收敛和最终精度的其实就那么几个。
imgsz,输入分辨率。默认 640,小目标多的话可以提到 960 甚至 1280,但显存占用会显著上升,而且推理速度会掉。我用 1660Ti 这种 6G 显存卡的时候,640 配 batch 8 是比较稳的组合。
batch,批大小。太小会导致 BatchNorm 统计不准,太大显存不够。有个实用技巧是用batch=-1让框架自动按显存选一个合适的值。
freeze,冻结层数。迁移学习时常用,冻结骨干前几层能加快收敛、防止小数据集过拟合。通常冻结前 10 层左右,具体看你的数据集规模和目标域差异。
lr0和lrf,初始学习率和最终学习率比例。YOLOv8 默认用余弦退火,从 lr0 衰减到 lr0*lrf。小数据集建议把 lr0 调到 0.001 左右,大数据集可以保持 0.01。
patience,早停轮数。默认 50,意思是在 50 轮内验证指标没提升就停。这个参数在数据集小的时候要调小,否则容易白跑很多轮。
实操心得:训练自己的数据集前,先拿官方权重在少量图上过一遍,确认标注格式、类别名、路径配置都对,再开正式训练。我见过太多人跑了半天才发现类别名写成了中文或者标签文件没对上,白白浪费时间。
4. 实例分割分支:从检测框到像素掩码
4.1 原型掩码加系数的组合思路
YOLOv8 的分割分支没有直接为每个实例生成一张掩码图,那样计算量太大了。它用的是原型掩码 + 系数的思路:网络先输出一组共享的原型掩码,形状大概是 32 通道、160×160 分辨率,然后每个检测实例输出一组系数,用这组系数对原型掩码做加权组合,就得到该实例的掩码。
这个思路和 Mask R-CNN 的 mask head 不一样,好处是原型只算一次,所有实例共享,实例数量增加时额外计算量很小。坏处是原型数量有限,如果一张图里目标种类特别多、形状差异特别大,原型可能不够用,掩码精度会受影响。
4.2 掩码是怎么算出来的
流程拆开是这样几步:检测头照常输出框和类别;掩码分支输出原型张量和每个框对应的系数向量;两者做矩阵乘法得到每个实例的原始掩码;最后按检测框的位置把掩码裁剪并缩放到原图尺寸。
这里面有个容易踩的坑:掩码是在特征图分辨率上生成的,上采样回原图时会损失细节。如果你的目标边缘要求特别精细,比如医学图像里的器官轮廓,默认配置可能不够。这时候可以考虑提高输入分辨率,或者在后处理阶段加边缘细化。
另外掩码的阈值也需要调。默认二值化阈值是 0.5,边缘区域会偏保守,实际可视化时可以试 0.3 到 0.6 之间,看哪个更贴合你的目标。
4.3 分割数据标注的成本和替代方案
实例分割的标注成本比检测高一个量级。检测只要画框,分割要沿着轮廓描点,一张复杂图可能要点几十个点。我做过一个项目,几百张图标注花了整整一周。
如果预算有限,有几个思路可以考虑。一是用检测标注加弱监督的方式,只用框信息训练,推理时靠模型自己学到的形状先验去分割,效果会打折但成本低很多。二是先用检测模型辅助标注,把预测框转成初始掩码,人工只做修正,能省一半以上时间。三是找现成的公开数据集做预训练,再在自己的少量标注上微调。
标注工具方面,labelme 和 roboflow 都能导出 YOLO 分割格式,格式是每行一个多边形点序列,归一化到 0 到 1。要注意多边形点的顺序和闭合性,点太密会影响训练效率,一般保留 20 到 50 个点就够描述轮廓了。
5. 关节点估计分支:把关键点当成回归问题
5.1 热力图还是直接回归
姿态估计有两条主流路线。一条是热力图方式,为每个关键点生成一张概率图,峰值位置就是关键点位置,精度高但计算量大,因为要逐像素处理。另一条是直接回归,网络直接输出关键点的坐标值,速度快,但精度对坐标回归的稳定性要求高。
YOLOv8 选的是直接回归路线,和它的实时定位一致。每个关键点输出三个值:x、y 和可见性置信度。COCO 数据集是 17 个关键点,所以每个人实例输出 17×3=51 个值。
这个选择的直接后果是:速度快,但遮挡情况下的表现会弱一些。热力图方式因为有空间上下文,对遮挡更鲁棒。如果你做的是体育动作分析,遮挡频繁,可能要额外做时序平滑或者其他后处理。
5.2 关键点的损失设计
关键点分支的损失主要有两块:位置损失和可见性损失。位置损失衡量预测点和真实点的距离,可见性损失是一个二分类,判断这个点在当前实例中是否可见(被遮挡或者超出画面的点标为不可见)。
训练时要特别注意不可见点的处理。如果把不可见点也当成正常点去回归位置,模型会学到错误的坐标。正确做法是让这些点不参与位置损失,只参与可见性损失。YOLOv8 的默认实现已经处理了这一点,但你自己写数据加载的时候要注意。
注意:关键点数据集的标注质量非常关键。同一个人的同一个关节,不同标注员点的位置可能差好几个像素,数据一致性别差的话,模型很难学到稳定的位置。
5.3 数据格式和常见坑
YOLOv8 的关节点数据格式是每个实例一行,包含类别、框中心坐标和宽高、以及每个关键点的 x、y、可见性。可见性取值是 0、1、2,分别代表未标注、标注但不可见、标注且可见。
最常见的问题是框和关键点不匹配。比如从 COCO 转格式时,框的坐标和关键点坐标用了不同的归一化基准,导致训练时框和点完全错位。我的做法是转完格式后,随机抽几张图把框和点画在原图上目视检查,这一步不能省。
另一个坑是左右关键点标反。鼻子、眼睛这些无所谓,但左右肩、左右肘标反会让模型学混。可以在可视化时用不同颜色区分左右,一眼就能看出来。
6. 训练、部署与常见问题排查实录
6.1 环境配置和显存适配
环境这块,PyTorch 版本和 CUDA 版本的匹配是新手最容易卡住的地方。经验做法是先去 PyTorch 官网查版本对应表,选一个稳定组合。比如较新的 PyTorch 配 CUDA 11.8 或 12.1,都是经过验证的。显卡驱动版本要满足 CUDA 的最低要求,这个用nvidia-smi一看就知道。
显存不够是另一个高频问题。以 6G 显存的 1660Ti 为例,跑 yolov8n 的检测任务,640 分辨率、batch 8 是比较合适的;跑分割任务就要降到 batch 4 左右;跑姿态任务介于两者之间。如果实在不够,可以开启 AMP 混合精度训练,能省不少显存,速度也快。
Windows 下用 PyCharm 部署的话,重点是把工作目录设对,因为 ultralytics 默认会在当前目录下找数据集配置。建议在项目根目录建一个 datasets 文件夹,配置文件里的路径用相对路径,避免换机器后路径失效。
6.2 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 训练 loss 不下降 | 学习率过大或数据标签错 | 先降 lr,再抽查标签可视化 |
| 验证集指标远低于训练集 | 过拟合 | 加数据增强、冻结骨干、减小模型 |
| 小目标漏检严重 | 下采样丢失细节 | 提高输入分辨率、改颈部融合 |
| 分割掩码边缘毛糙 | 原型分辨率不足 | 提高 imgsz、调掩码阈值 |
| 关节点左右错乱 | 标注左右反了 | 可视化检查左右颜色标记 |
| 推理速度远低于预期 | 未用半精度或 TensorRT | 导出 ONNX 后转 TensorRT |
| 显存溢出 | batch 或分辨率过大 | 降 batch、开 AMP、降 imgsz |
6.3 部署侧的几个关键点
训练完只是第一步,真正落地要考虑推理效率。ONNX 导出是最通用的中间格式,导出时记得指定 opset 版本,太老的版本可能不支持某些算子。导出后可以用 onnxruntime 先验证一下输出是否和 PyTorch 一致,不一致通常是某层算子被替换了。
如果目标是边缘设备,TensorRT 是提速最明显的路线。转 TensorRT 时要注意动态尺寸和静态尺寸的选择,固定尺寸能获得更好的优化效果,但灵活性差。INT8 量化能进一步提速,但需要准备校准数据集,量化不当会掉点,建议先用 FP16 跑通再考虑 INT8。
嵌入式平台部署的时候,算子支持是最大的坑。某些算子在这些平台上没有高效实现,会被回退到 CPU 执行,速度断崖式下跌。解决办法通常是换用平台官方提供的模型转换工具,把不支持的算子替换掉,或者在训练阶段就避免使用这些算子。
我在实际项目里踩过最深的一个坑是:模型在 PC 上跑得好好的,换到边缘设备后精度突然掉了十几个点。查了很久才发现是输入图像的预处理不一致——PC 上用的是 BGR,设备上用了 RGB,颜色通道反了,模型自然认不准。所以部署前一定要用同一张图在两端跑一遍,逐层对比中间输出,把差异定位到具体环节。
另一个值得养成的习惯是把损失曲线画出来看。YOLOv8 训练完会保存 results.csv,用 pandas 读出来画 loss、mAP、precision、recall 的曲线,能直观看出有没有过拟合、有没有震荡。震荡通常是学习率太大或者 batch 太小,过拟合则是数据量不够或者增强太弱。这些信号比最终一个 mAP 数字有用得多。
关于模型改进,我的建议是先跑通基线再动手。很多人一上来就改 C2f、加注意力、换损失,结果基线都没跑明白,改完不知道是哪里起了作用。正确顺序是:用默认配置跑出一个基线指标,记录清楚硬件、参数、数据版本;然后每次只改一个地方,单独评估;最后把有效的改动组合起来看是否有叠加增益。这套流程听起来笨,但能帮你省下大量重复试错的时间。
最后说一个关于数据集的实在话:模型结构的改进带来的提升,往往不如把标注质量提上去。我遇到过的精度瓶颈,八成以上最后是靠清洗数据、补充难例、统一标注标准解决的,真正靠改网络结构解决的不到两成。与其花时间在结构上反复折腾,不如先把数据集里的错标、漏标、类别不平衡处理干净。