
简介面向目标检测初学者和YOLOv8使用者的网络结构图解教程从整体到细节逐步拆解网络设计核心解决“看懂结构但画不出图、对不上代码”的痛点。文档先对Backbone、Neck、Head三部分作整体说明再分模块讲解卷积层、C2f、SPPF、上采样、Concat及检测头的功能与连接关系同时对照yolov8.yaml和PyTorch源码帮助读者将配置文件、代码实现与网络图一一对应。绘制方面文档给出使用Visio、Graphviz、Netron等工具绘制结构图的完整流程覆盖Backbone、Neck、Head的分步画法、标注规范与美化技巧。包内仅含1个doc文档压缩包约53KB内容集中便于随查随用。目前已有2307人学习非常适用于目标检测入门、网络结构分析、论文插图绘制等场景。文档还对比YOLOv5说明C2f的改进并讨论注意力机制、轻量化骨干等优化方向帮助读者在理解结构基础上根据具体任务做调整与实验。1. 看了一天源码还不如先画一张YOLOv8结构图很多同学第一次接触 YOLOv8第一反应是打开官方仓库把模型代码从头到尾读一遍结果读了一个下午能说清楚“输入进了模型之后到底经过了哪些层”的人少之又少。我见过太多人卡在同一个地方训练能跑、demo 也能出框但一旦要改网络结构——比如把某个 C2f 删掉、把 SPPF 换掉、给 Neck 加一层——就完全不知道从哪里下手。这时候与其继续盯着代码不如先把网络结构图画出来。结构图不只是一张给汇报用的插图它是你理解模型、定位模块、动手改造的地图。这篇文章我会把我自己画 YOLOv8 结构图的方法、参数和踩过的坑完整讲一遍新手能照着画熟手也能拿去对照检查自己的理解有没有偏差。2. 拆开YOLOv8的骨架Backbone、Neck、Head到底怎么连接2.1 先读懂结构文件一张表看懂模块职责YOLOv8 的网络结构在官方仓库里是用一个结构描述文件扩展名为 yaml 的配置文件定义的而不是在 Python 代码里硬编码每一层。这个文件里写了三件事Backbone 有哪些模块、Neck 有哪些模块、Head 输出几个尺度的检测结果。结构图要画得准第一步就是把这份文件里的模块名和实际作用对应起来。下面这张表是我每次画图前必看的对照表模块名以常见版本的结构文件为准。结构部分模块名按出现顺序主要作用BackboneCBS、C2f、SPPF提取特征逐层降低分辨率最终输出多层特征图NeckCBS、Upsample、Concat、C2f、CBS下采样融合不同尺度的特征形成 PAN 结构HeadDetect三个分支在每个尺度上预测框位置和类别这里有个容易被忽略的点结构文件里的模块名是“简写”真正的实现是 Python 类。比如 CBS 是 Conv BatchNorm SiLU 的封装C2f 是一个包含多个 Bottleneck 的 CSP 结构模块SPPF 是空间金字塔池化的快速版本。画结构图时如果你只写“C2f”不展开内部细节这张图是能用的如果你想给别人讲清楚每个模块为什么这么设计就需要把 C2f、SPPF 单独拆开画。我一般会先画一张“粗粒度”的总体连接图再针对关键模块画“细粒度”的内部展开图两张图配合使用。2.2 张量尺寸流转从640x640到三路输出结构图的核心不是方框而是方框之间的连线以及线上流动的张量形状。YOLOv8 默认输入是 640x640x3 的 RGB 图像经过 Backbone 后会在三个不同尺度上输出特征图分别记为 P3、P4、P5。这三个名字在结构图里必须标清楚否则后续解读 Neck 和 Head 会全乱。输出层级特征图尺寸通道数对应输入图像上的下采样倍数P380x802568 倍下采样P440x4051216 倍下采样P520x20102432 倍下采样这几个数字不是靠记忆背下来的而是跟着卷积的 stride 推出来的。Backbone 前面几个 CBS 模块里凡是 stride2 的卷积都会让特征图宽高减半。输入 640过第一个 stride2 变成 320再过第二个 stride2 变成 160继续下去就会得到 80、40、20 这一串尺寸。画结构图时我习惯在每个模块旁边标注“输出尺寸 通道数”而不是只写模块名。这样一张图看完整个模型的计算路径也就清楚了。2.3 手工画结构图的五个步骤如果你不想一开始就写代码画图可以先在纸上或者白板上手工画步骤固定下来之后会很快。第一步画输入框标注 640x640x3。第二步沿着 Backbone 顺序画出所有 CBS 和 C2f每画一个在旁边写下输出尺寸和通道数。第三步标出 P3、P4、P5 三个分支点这三个点分别引出三条线进入 Neck。第四步在 Neck 里按“先上采样融合再下采样融合”的顺序连线注意 Concat 模块要有两条输入线。第五步把 Neck 的三个输出分别连到 Detect 的三个分支标注每个分支的输出通道数。画完这五步一张能用的结构图就出来了。手工画一遍的最大价值是强迫你自己把每一层的输入输出走一遍很多“以为懂了其实没懂”的细节会在这个过程中暴露出来。3. 结构图的核心部件C2f、SPPF和解耦头的内部细节3.1 C2f结构图里出现最多的模块C2f 是 YOLOv8 的 Backbone 和 Neck 里出现频率最高的模块结构图如果只画一个方块写“C2f”信息量是不够的。C2f 全称是 CSP Bottleneck with 2 convolutions它的设计思路是输入先经过一个 1x1 卷积把通道分成两路一路直接走、另一路经过若干个 Bottleneck 子模块最后把多路结果在通道维度上拼接起来。这里的“拼接”是 Concat而不是相加画图时一定要区分。C2f 有两个关键参数一个是输出通道数另一个是 Bottleneck 的重复次数。在结构文件里C2f 的写法类似C2f(128, 3)前半是通道数后半是重复次数。有一个常见误区是以为重复次数越大输出通道数越大其实不会因为每个 Bottleneck 内部的卷积会自己管理通道数最后 Concat 完之后还会有一个 1x1 卷积把通道数还原到设定值。画图时我建议把 C2f 画成一个“大框套小框”的形式大框里画两到三个 Bottleneck 方块并标注重复次数 n这样别人一眼就能看出这是个可堆叠的结构。3.2 SPPF一个“快版”SPP画图时别画错SPPF 在结构图里通常出现在 Backbone 的最后一层负责在固定分辨率下聚合多尺度的感受野信息。它的全称是 Spatial Pyramid Pooling - Fast和早期 YOLO 系列里的 SPP 作用类似但是实现方式做了简化。SPP 是并行做几个不同尺寸的池化再拼接比如 5x5、9x9、13x13SPPF 则改成串行先做一个 5x5 的最大池化再把结果继续做 5x5 池化再做一次 5x5 池化最后把原始输入和三次池化结果全部拼接起来。画 SPPF 时最常犯的错误是把它画成三个并行的池化分支。实际上它是串行的第二次池化作用的对象是第一次池化的输出第三次池化作用的对象是第二次池化的输出这种串行结构让计算量明显下降但效果上和并行 SPP 基本一致。SPPF 内部通道也很有特点输入的 1024 通道先被一个 1x1 卷积压缩成 512池化过程中始终保持 512最后 Concat 得到 2048再经过一个 1x1 卷积还原到 1024。我把这个通道变化画成一条“先收窄再还原”的路径结构图的信息量就上来了。3.3 解耦检测头输出通道数是怎么算出来的YOLOv8 的 Head 是解耦头也就是说分类和回归各走各的分支。在结构图上Detect 不是一个简单的方框而是三个分支分别对应 P3、P4、P5。每个分支内部又分成分类支路和回归支路二者共享一部分卷积之后各自输出。回归支路的输出通道数是固定的由reg_max决定。以常见配置为例reg_max16每个框需要预测 4 个位置参数所以回归支路的输出是 4 乘 16 等于 64。分类支路的输出通道数是数据集类别数比如检测 80 个类别时输出就是 80。所以一个检测头的总输出是 64 加 80 等于 144。这个数字经常出现在结构文件的 Head 部分很多人不知道它是怎么来的画图时直接把 144 写成卷积输出不对它其实是两条支路拼出来的。画图时把分类支路和回归支路分开画再标注拼接操作这张图就严谨了。3.4 结构图对应源码从哪里找到每个部件结构图画完之后建议把它和源码对应一遍确认每个方块在代码里都能找到实现。CBS 对应的是卷积、批归一化、SiLU 激活的组合C2f 对应的是那个包含多个 Bottleneck 的类SPPF 对应的是串行最大池化的实现Detect 对应的是检测头类。我习惯在结构图的每个方块旁边写上源码里的类名这样后续想改结构时能直接定位到要改的代码位置。这里有一个很实用的小技巧官方源码里通常会有一份结构文件里面每一行的格式是“模块名: 参数”。你可以在读结构文件时把每一行的模块名标上序号然后在结构图里也标同样的序号两边一一对应。这样排查问题时你可以顺着序号快速跳转到对应代码不用每次都从头搜索。我自己的经验是做过一次完整的“结构文件到结构图再到源码”的映射之后再去看模型训练日志里的层名基本就能直接读懂。4. 用Python把结构图画成矢量图可直接跑的绘图脚本4.1 绘图脚本matplotlib版手工画图适合理思路但要做汇报材料或者写技术文档时还是需要一张干净的矢量图。我一般用 matplotlib 直接画不需要额外安装 graphviz 之类的工具。下面这个脚本是我常用的简化版它能把 YOLOv8 的 Backbone、Neck、Head 按区域画出来并自动编号。代码可以直接保存运行注意提前安装好依赖。import matplotlib.pyplot as plt from matplotlib.patches import FancyBboxPatch, FancyArrowPatch def draw_module(ax, x, y, w, h, text, fc): 画一个带圆角的模块方块。fc 控制填充颜色。 box FancyBboxPatch((x, y), w, h, boxstyleround,pad0.02, linewidth1.2, edgecolor#2d2d2d, facecolorfc) ax.add_patch(box) ax.text(x w / 2, y h / 2, text, hacenter, vacenter, fontsize8) def draw_arrow(ax, x1, y1, x2, y2): 画一条带箭头的连线表示张量流向。 arrow FancyArrowPatch((x1, y1), (x2, y2), arrowstyle-|, mutation_scale14, linewidth1.1, color#2d2d2d) ax.add_patch(arrow) # 画布初始化 fig, ax plt.subplots(figsize(16, 10)) ax.set_xlim(0, 24) ax.set_ylim(0, 14) ax.axis(off) # Backbone 上层从左到右 backbone_top [ Input 640x640x3, CBS k3 s2 c64, C2f c64 n3, CBS k3 s2 c128, C2f c128 n6, CBS k3 s2 c256 ] # Backbone 下层从右到左连接 backbone_bottom [ C2f c256 n6, CBS k3 s2 c512, C2f c512 n3, SPPF c1024 ] # 绘制 Backbone 上层 x_pos 1.0 for name in backbone_top: draw_module(ax, x_pos, 10.5, 2.0, 0.9, name, #dbe9f6) x_pos 2.4 # 绘制 Backbone 下层 x_pos 1.0 for name in backbone_bottom: draw_module(ax, x_pos, 8.2, 2.0, 0.9, name, #cfe5cf) x_pos 2.4 # Neck 模块示意 neck_names [ CBS 1x1 c512, Upsample x2, Concat, C2f c512 n3, CBS 1x1 c256, Upsample x2, Concat, C2f c256 n3, CBS 3x3 s2 c256, Concat, C2f c512 n3, CBS 3x3 s2 c512, Concat, C2f c1024 n3 ] x_pos 1.0 for name in neck_names: draw_module(ax, x_pos, 5.2, 1.7, 0.8, name, #f5e2c8) x_pos 1.9 # Head 三个分支 detect_names [Detect P3, Detect P4, Detect P5] x_pos 1.0 for name in detect_names: draw_module(ax, x_pos, 2.2, 2.2, 0.8, name, #f2cccc) x_pos 2.6 # 主干连线Backbone 上层末尾 - Backbone 下层开头 draw_arrow(ax, 13.0, 10.5, 13.0, 9.1) # 示意连线Backbone 上层第二个 C2f 输出P3连到 Neck draw_arrow(ax, 2.2, 10.5, 2.2, 6.0) # 示意连线Backbone 下层第二个 C2f 输出P4连到 Neck draw_arrow(ax, 8.2, 8.2, 8.2, 6.0) # 示意连线Backbone 下层 SPPF 输出P5连到 Neck draw_arrow(ax, 13.0, 8.2, 13.0, 6.0) plt.savefig(yolov8_structure.png, dpi150, bbox_inchestight) plt.show()这个脚本的运行效果是一张按三行区域排布的结构图上行是 Backbone 前半段中行是 Backbone 后半段与 Neck下行是检测头。三个区域的颜色不同方便区分职责。注释里写清楚了每个函数的作用后面要扩展也非常容易比如给每个模块加上输出尺寸标注。4.2 参数说明与样式调整脚本里的几个关键参数会影响成图效果。figsize(16, 10)控制整张图的宽高比例如果你的模块名比较长建议把宽度调大比如 18防止文字互相挤压。fontsize8是模块内文字的默认字号当模块数量多、方块变窄时可以降到 7如果模块少可以调到 10图更清晰。boxstyleround,pad0.02让方块带圆角这是个人审美问题不需要在意。dpi150决定导出 PNG 的清晰度如果后续要放大打印建议提到 300。三个区域的填充颜色通过facecolor控制我习惯用蓝、绿、橙分别表示 Backbone、Neck、Head这样读者一眼能看出模块属于哪个部分。draw_arrow函数里的mutation_scale控制箭头大小。连线坐标是手动指定的这也是这个脚本最需要耐心的地方一旦模块坐标变了箭头坐标必须跟着改。我的做法是先运行脚本看输出的图里哪些箭头位置不对再逐个微调坐标反复两三轮就稳了。4.3 画完怎么验证结构图没画错结构图画完后验证比画图更重要。我一般用三个方法。第一个方法是从头顺着连线走一遍确认每个模块至少有一条输入和一条输出线。第二个方法是拿结构文件里的通道数逐层核对从 640x640x3 开始每过一个模块就把输出通道数更新一次最后看 P3、P4、P5 的通道数是不是 256、512、1024。第三个方法是打印模型的每一层输出形状和结构图上标的形状对比。最后这个方法最严格能同时验证你对结构文件的理解和对张量流转的理解。只要这三个检查都通过这张图就可以放心拿去用了。5. 画YOLOv8结构图的常见坑五个从翻车里总结的注意点5.1 把yaml直接当成连接图漏掉Concat的来路现象照着结构文件逐行画图画到 Concat 时只画了一条输入线因为结构文件里 Concat 那行看起来只有一个来源。 原因结构文件里 Concat 的输入往往来自前面某个模块的输出但文件名里不会直接列出这个“来自哪里”而是靠代码里的路由逻辑决定的。没理解路由就会漏画一条线。 解决画 Concat 前先在上一步的特征图列表里找到两个输入分别是谁。我习惯在结构文件旁边标注“本层输入来自哪一层”再动手画。这条坑几乎每个新手都会踩好在这个检查很简单。5.2 把C2f的通道数当成一路直通不知道内部是拼接现象画出来的 C2f 是一个单进单出的方块输入 128 通道输出 128 通道看起来像是一个普通卷积。 原因C2f 内部有分流、Bottleneck 堆叠、Concat 三个步骤只看输入输出形状确实和普通卷积一样但如果把它当成普通卷积你就无法理解为什么 C2f 能提升梯度流动。 解决至少在结构图的图例里注明“C2f 内部包含分流和拼接”或者单独画一张 C2f 内部展开图。展开图不需要画所有 Bottleneck只画两个方块并标注“重复 n 次”就够了。5.3 主干画对了颈部方向却画反了现象Backbone 画得完全正确Neck 里却把“先上采样再融合”画成了“先下采样再融合”整张图看着别扭但说不出哪里错。 原因PAN 结构有两条通路一条从深层往浅层做上采样融合另一条再从浅层往深层做下采样融合。只记住“特征融合”却忘记方向就会画反。 解决在 Neck 区域画两个明确的大箭头一个朝上上采样路径一个朝下下采样路径中间用 Concat 作为分界点。这样方向问题在视觉上就避开了。5.4 P3/P4/P5的对应关系搞混导致输出尺寸对不上现象结构图画完检测头三个分支标注的输入尺寸分别是 80、40、20但和 Backbone 的实际输出对应不上。 原因P3 是较小的下采样倍数、对应较大的特征图 80x80P5 是最大的下采样倍数、对应最小的特征图 20x20。数字越大特征图越小这个反直觉关系特别容易记反。 解决画图时用“下采样倍数”而不是“层号”来命名比如标注“P3 / 8x down / 80x80”三个信息同时出现任何两个对上都算验证通过。5.5 拿着旧结构的理解画新结构图上出现“CBL”这种残留现象结构图上出现以前旧版 YOLO 的结构名比如 CBLConv BatchNorm LeakyReLU但 YOLOv8 用的是 CBSConv BatchNorm SiLU。 原因很多同学是从旧版迁移过来的画图时肌肉记忆把激活函数写成了旧名字。 解决画图前先确认每个简写对应的激活函数CBS 里的“S”代表 SiLU不要沿用旧习惯。这种小错误不影响整体结构理解但在给别人讲的时候会明显露怯。6. 拿着结构图去改模型一个改C2f参数的完整例子6.1 改n3为n2输出通道为什么不变结构图最直接的用途就是指导改模型。比如我想让模型更轻量打算把 Backbone 里某个 C2f 的重复次数从 3 改成 2。在结构文件里这个改动只是把 C2f 的参数n3改成n2。但是改完之前你必须先理解一件事这个改动不会改变输出通道数。因为 C2f 的输出通道由最后的 1x1 卷积决定而那个卷积的输入是多个分支 Concat 的结果分支数量不会因为 n 变了就变。换句话说你把 n 从 3 改成 2只是让内部 Bottleneck 少了两个整体输入输出形状不变。这个结论在结构图上是一目了然的。如果你没有结构图很容易改完参数之后对着训练日志里的 shape 发愣明明改了结构为什么输出没变现在你知道了这不算翻车而是 C2f 的设计本来如此。改完之后需要关注的是模型参数量和计算量变化而不是输出形状变化。6.2 改完先核对结构图再训练改动任何结构参数之后不要急着开训练先做一次形状核对。我用的是一个小技巧加载模型后把每一层的输出形状打印出来拿它和结构图上标注的形状逐一对比。如果 C2f 的重复次数变化没有引起任何形状变化说明结构图标注是对的如果某层的形状变了结构图上也要同步更新否则下次看图的人会以为模型有 bug。这套“先核对再训练”的习惯能节省大量排查时间。6.3 值得投入这个方向吗画结构图这件事看起来花时间实际上是把未来所有调试时间压缩在前期的一次性投入里。我自己的习惯是改网络结构之前先花半小时把结构图和形状表更新好改完之后再花十分钟核对这比之后出了问题对着报错日志猜原因要快得多。如果你打算长期做模型结构方面的工作我建议把结构图当作代码的一部分来维护每次改动都同步更新。这个方向绝对值得投入它不解决某一个具体 bug但它能让你在所有结构相关的工作里都不再瞎猜。希望这篇教程能帮到你画图的过程本身就是最好的学习过程。本文还有配套的精品资源点击获取