YOLOv3 原理详解:从 Darknet-53 到三尺度检测头

写在前面

YOLOv3 发布于 2018 年,论年纪早就算"老前辈"了。但直到今天,它依然是目标检测入门最好的教材:结构不复杂、代码量不大、论文写得像博客一样口语化,而且几乎所有后续的 YOLO 变体(v4/v5/v7/v8...)都能在它身上找到影子------多尺度检测、Anchor 机制、特征拼接、损失构成,这些概念都是从这一版开始定型并被反复沿用的。

所以这篇文章不讲怎么训练、不讲怎么调参,就老老实实回答一个问题:

YOLOv3 是怎么把一张图片,变成"一堆带类别的方框"的?

先把官方的经典测试图摆出来,感受一下它要解决的问题------输入一张普通照片,输出每个物体的位置和类别:

一、先给 YOLOv3 一个准确的定位

很多文章把 YOLOv3 讲得很玄,好像它发明了什么划时代的新结构。其实论文作者自己说得很直白:这版就是**"a bunch of little design changes"(一堆小的设计改动)**,它并没有引入全新的检测范式。

它属于 one-stage(单阶段)检测器------不生成候选区域,一次前向传播直接回归出框的位置和类别。对比两阶段检测器(如 Faster R-CNN)的"先出候选框、再分类回归",YOLOv3 把这两件事塞进了同一个网络。

同时代的竞争对手是 RetinaNet(提出 Focal Loss 的那篇),两者的路线差异很典型:

YOLOv3 RetinaNet
类别预测 每个类别独立的 sigmoid softmax / sigmoid + Focal Loss
难例挖掘 不做 Focal Loss 自动降权易样本
精度表现 AP50 高、AP75 偏低 高 IoU 下更稳
速度 很快 相对慢

论文里甚至专门写了一节"Things We Tried That Didn't Work(试过但没用的东西) ",承认了几件失败尝试:给 anchor 也预测 x/y 偏移、用线性激活预测 x/y 而不是 sigmoid、用 Focal Loss、用双 IoU 阈值做软训练。这种坦诚很值得一读------它告诉你哪些"看起来很美"的改动实际上没用。


二、整体结构:一张图看懂

整条链路可以拆成四步:

  1. 输入图像:416×416×3(也可以是 320、608 等);
  2. Darknet-53 主干网络:把图像层层下采样,抽出一串特征图;
  3. 多尺度融合:把不同分辨率的特征图用「上采样 + 拼接」拼成三个尺度;
  4. 三个检测头:每个尺度各自用卷积直接预测出方框坐标、置信度和类别。

最终输出的是三个张量:

复制代码
13 × 13 × 255    ← 大目标
26 × 26 × 255    ← 中目标
52 × 52 × 255    ← 小目标

255 这个数字是怎么来的? 这是理解 YOLOv3 的第一个关键点:

复制代码
255 = 3 × 85 = 3 × (4 + 1 + 80)
       ↑   ↑        ↑   ↑   ↑
       |   |        |   |   └─ 80 个类别各自的得分(COCO 数据集)
       |   |        |   └───── 1 个 objectness(这个格子里有没有物体)
       |   |        └───────── 4 个坐标 tx, ty, tw, th
       |   └────────────────── 每个格子预测 3 个框
       └────────────────────── 3 个先验框(anchor)

也就是说:特征图上的每一个格子,都会预测 3 个边界框;每个框除了位置,还要给出"有没有物体"的置信度和 80 个类别的得分。13×13 的格子数最少,52×52 的格子数最多,这就是三个尺度输出形状不同的原因。


三、主干网络 Darknet-53

3.1 结构本身:5 级下采样 + 残差堆叠

Darknet-53 的名字来自它53 个卷积层。它最鲜明的两个特点:

  • 没有池化层 。所有下采样都由 stride = 2 的 3×3 卷积完成,特征图尺寸依次是 416 → 208 → 104 → 52 → 26 → 13,一共下采样 5 次,正好 25=322^5 = 3225=32 倍。这也是为什么输入边长必须是 32 的倍数------否则 13×13 那个尺度得不到整数尺寸。
  • 残差连接。每个基本单元是「Conv 1×1(通道砍半)→ Conv 3×3 → 与输入相加」,借鉴了 ResNet 的跳连思路,让深层网络能训得下去。

3.2 为什么不直接用 ResNet?

因为要在精度和速度之间取平衡。论文给了一张对比表:

主干网络 Top-1 精度 Top-5 精度 Bn Ops FPS
Darknet-19 74.1% 91.8% 7.29 171
ResNet-101 77.1% 93.7% 19.7 53
ResNet-152 77.6% 93.8% 29.4 37
Darknet-53 77.2% 93.8% 18.7 78

看最后两行:Darknet-53 的精度和 ResNet-152 几乎持平,但速度快了一倍多,算力(Bn Ops)也更少。论文的说法很实在:"It's more powerful than Darknet-19 but still more efficient than ResNet-101 or ResNet-152."

3.3 顺带说一声 DBL

后面的图里会反复出现 DBL,它是 Darknet 里的基本卷积单元:

复制代码
DBL = Conv(卷积层) + BN(批归一化) + Leaky ReLU(斜率 0.1)

YOLOv3 的主干和检测头,都是靠 DBL 一层层堆出来的。


四、三尺度预测:YOLOv3 最核心的改动

这是 YOLOv3 相对前代最大的一次结构升级。YOLOv2 只有一个尺度的输出,YOLOv3 变成了三个。

4.1 上采样 + 拼接:把"深层语义"喂给"浅层细节"

思路源自 FPN(特征金字塔网络):

  1. 主干最深处拿到 13×13 的特征,语义最强,但分辨率最低------它负责大物体;
  2. 把 13×13 的特征用 1×1 卷积压到 256 通道,再上采样 ×2 变成 26×26;
  3. 与主干里同样 26×26 的那张特征图(512 通道)拼接(Concat),得到 26×26×768;
  4. 26×26 这一支再走一遍上面的流程,得到 52×52×384 的特征,负责小物体。

通道数要能对上,这里列清楚:

尺度 主干特征图 上采样分支 拼接后 最终输出
13×13 1024 通道 --- --- 13×13×255
26×26 512 通道 256 通道(13² 上来) 768 26×26×255
52×52 256 通道 128 通道(26² 上来) 384 52×52×255

之所以能把深层特征"喂"给浅层,是因为拼接而不是相加:检测头需要的既有"这是什么"(语义),也有"在哪里"(位置精度)。相加会互相污染,拼接则把两种信息都留给后面的卷积自己组合。

4.2 三个尺度分别管什么?

特征图 单个格子对应的感受野 适合的目标
13 × 13 最大(粗略但看得远) 大目标:公交车、卡车、人物特写
26 × 26 中等 中等目标
52 × 52 最小(精细) 小目标:远处的行人、飞鸟

13×13 的格子大,一个大物体正好落在一个格子里;52×52 的格子小,小物体才有机会被"单独分到一个格子"。如果没有多尺度,小目标检测会明显更差------这正是后面第十节要说到的短板。


五、先验框 Anchor:9 个框是从数据里"聚"出来的

每个格子预测 3 个框,但网络不是从零开始"瞎猜"框的大小------它先有 9 个**先验框(anchor)**作为参考,网络只负责在它们基础上做微调。

这 9 个框不是手工设计的,而是在 COCO 数据集上对真实框的宽高做 k-means 聚类得到的,然后按尺度大小分成 3 组,每组配给一个尺度:

尺度 特征图 分配到的 3 个先验框 (w×h)
52 × 52 小目标 (10,13)、 (16,30)、 (33,23)
26 × 26 中目标 (30,61)、 (62,45)、 (59,119)
13 × 13 大目标 (116,90)、 (156,198)、 (373,326)

注意规律:特征图越大(格子越密),配的先验框越小。因为 52×52 是给小目标用的,如果给它配一个 373×326 的大框,这个框会横跨几十个格子,完全没有意义。


六、边界框解码:σ 和 e^t 分别在做什么

图上每个框的坐标不是直接回归出来的绝对像素值,而是四个相对量 tx,ty,tw,tht_x, t_y, t_w, t_htx,ty,tw,th,需要按下式解码成真实框:

bx=σ(tx)+cx b_x = \sigma(t_x) + c_x bx=σ(tx)+cx

by=σ(ty)+cy b_y = \sigma(t_y) + c_y by=σ(ty)+cy

bw=pw⋅etw b_w = p_w \cdot e^{t_w} bw=pw⋅etw

bh=ph⋅eth b_h = p_h \cdot e^{t_h} bh=ph⋅eth

其中 cx,cyc_x, c_ycx,cy 是当前格子的左上角坐标(相对特征图),pw,php_w, p_hpw,ph 是先验框的宽高,σ\sigmaσ 是 sigmoid。

为什么中心点要用 σ?

预测物体中心时,网络会在特征图上"滑动"。如果让 txt_xtx 无限取值,可能出现中心点漂到十个格子外的情况,训练时会很不稳定。加上 sigmoid 之后,σ(tx)∈(0,1)\sigma(t_x) \in (0, 1)σ(tx)∈(0,1),意味着预测中心永远被限制在负责它的那个格子内部------谁负责这个物体,就由谁的格子来预测中心,职责清晰,梯度也稳。

为什么宽高要用 e^t?

宽高的缩放比例可以是 1/10,也可以是 10 倍,是一个乘性关系 。用 ete^{t}et 表示倍率,正好把"乘性调整"变成"加性偏移",tw=0t_w = 0tw=0 表示保持先验框原样,tw>0t_w > 0tw>0 表示放大、tw<0t_w < 0tw<0 表示缩小。再加上 ln 的对称性,训练时正负方向的梯度尺度一致。而且解码出来的框可以远远大于所在格子,这正是大目标所必需的。

论文还提到一个细节:他们试过让网络直接预测未裁剪的 x/y 偏移(不用 sigmoid),结果 mAP 掉了 0.1 左右。这就是第六节说的"试过但没用"的改动之一。


七、类别预测:为什么把 softmax 换成一群 sigmoid

这是一个非常容易被忽略、但设计意图很明确的改动。

YOLOv2 用 softmax 输出类别概率------它默认"一个框只属于一个类别",各类概率互斥。YOLOv3 改成了对每个类别各做一次独立的 sigmoid(logistic 分类)。

原因在论文里写得很清楚:数据集中存在类别重叠的情况。比如 Open Images 里既有 "Person" 也有 "Woman",被标注成同一个人的框,用 softmax 就会被强行逼着二选一(压掉另一个类别的分数),而用独立的 sigmoid,就可以同时给出 "Person = 0.95" 和 "Woman = 0.92"。

换句话说:

softmax 假设"标签互斥",独立的 logistic 假设"标签可以叠加"。YOLOv3 选择了后者,因为它更符合真实标注数据。

顺带一提,"这个格子里到底有没有物体"的 objectness 置信度,用的也是 sigmoid。


八、损失函数:三部分怎么分工,正负样本怎么分

YOLOv3 的总损失是三块相加:

损失项 形式 作用范围
位置损失 LboxL_{box}Lbox 平方和误差(MSE) 只对正样本计算 (t_x, t_y, t_w, t_h)
置信度损失 LobjL_{obj}Lobj 二元交叉熵(BCE) 正样本和负样本都算
分类损失 LclsL_{cls}Lcls 二元交叉熵(BCE) 只对正样本计算,80 类各自独立

比公式更重要的是正负样本的分配规则,这也是初学者最容易卡住的地方:

  1. 一个真实框(ground truth)只分配给"和它 IoU 最大"的那一个先验框,被选中的框称为正样本,负责预测这个物体的位置、置信度和类别;
  2. 剩下的框里,如果 IoU > 0.5 但不是最大的 ,直接忽略------既不算正样本去逼它拟合,也不算负样本去惩罚它。这就是论文里说的 "we ignore the prediction",阈值取 0.5;
  3. IoU ≤ 0.5 的算负样本,只参与置信度损失,让网络学会在该输出"这里没东西"的时候输出 0。

第 2 条是精髓:一个 20×20 的物体,可能和旁边的框有 0.7 的 IoU,但它并不是最佳匹配。如果强行把它当负样本,就等于让网络"不要预测这个真实存在的物体",模型会越训越糊涂。用"忽略"来绕开这种歧义,比硬区分正负要聪明得多。


九、训练与推理中的几个工程细节

1)输入尺寸是"可变的"

由于网络是全卷积的、且只做 32 倍下采样,它天然可以接受任意 32 倍数的输入。论文给出了三档常用配置,正好对应三种精度/速度取舍:

输入尺寸 mAP (0.5:0.95) AP50 推理时间(Titan X)
320 × 320 28.2 51.5 22 ms
416 × 416 31.0 55.3 29 ms
608 × 608 33.0 57.9 51 ms

2)多尺度训练

训练时随机切换输入尺寸(如 320/352/384/.../608),让同一个网络适应多种分辨率。代价是 BatchNorm 之类的统计量会波动,但换来的是推理时"改尺寸不用重训"的灵活性。

3)推理阶段要 NMS

三个尺度、9 个先验框、每个格子 3 个框,同一个物体必然被重复预测多次。所以最后要用非极大值抑制(NMS):先按置信度阈值过滤,再把同一类别里 IoU 过高的框合并掉,只留分数最高的那个。


十、YOLOv3 的得与失

论文里最诚实的一段,是它自己承认的短板。看这张精度-速度图:

  • 速度上是压倒性的:608×608 输入,精度 33.0 mAP,只要 51 ms;
  • AP50 非常能打 :57.9 AP50 @ 51ms,而 RetinaNet-101-800 拿到 57.5 AP50 却要 198 ms,YOLOv3 快了约 3.8 倍;
  • 但 AP75 / 高 IoU 下偏弱:IoU 要求越严格,YOLOv3 的优势越小;
  • 小目标偏弱:这是它公开承认的问题(论文提到对小物体的表现较差)。

为什么会这样?因为 YOLOv3 走的是"简单粗暴"的路线:没有 Focal Loss 去专门处理难例,也没有 hard negative mining,全靠大量先验框 + 多尺度 + BCE 的均衡。在"框大致对得上"的场景里它极快极划算;在"必须框得很准"的场景里,RetinaNet 那类方法更有优势。

相关推荐
zx_741484812 小时前
YOLOv3 核心原理详解:Darknet-53 架构、多尺度预测与 FPN 特征融合
算法·yolo·目标检测·yolov3
广州灵眸科技有限公司5 小时前
瑞芯微(EASY EAI)RV1126B DeepSeek R1部署教程
linux·数据库·yolo
词却6 小时前
计算机视觉:YOLO 目标检测
yolo·目标检测·计算机视觉
凌晨1687 小时前
YOLO系列学习笔记:从YOLOv1到YOLOv3的演进之路
笔记·yolo·目标跟踪
FL16238631291 天前
塔吊下方站人检测数据集VOC+YOLO格式1049张1类别
人工智能·深度学习·yolo
晨间晓2 天前
单目视频测速不准?我给 YOLO + ByteTrack 加了透视标定和鸟瞰图测速
yolo·音视频
中国.NET研究协会2 天前
SimdYolo:纯 C# YOLO 推理实现,CPU SIMD / Vulkan 架构与 PyTorch、ORT 实测
yolo·.net core
hhzz2 天前
【YOLO 入门到精通 11】进阶任务实战:分割、姿态、OBB 与深度估计
人工智能·python·yolo·计算机视觉·目标跟踪
晨间晓2 天前
两个目标会不会发生冲突?我用 YOLO + ByteTrack 做了 TTC 碰撞风险分析
yolo