YOLOv1: You Only Look Once

论文: You Only Look Once: Unified, Real-Time Object Detection (CVPR 2016)
作者: Joseph Redmon, Santosh Divvala, Ross Girshick, Ali Farhadi


一、核心思想

1.1 什么样的任务是”回归问题”?

一句话:输出连续数值的是回归,输出离散标签的是分类。

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────────────────────────┐
│ 分类问题 (Classification) │
│ 输入一张图 → 输出: "猫" (0或1,离散标签) │
│ 例: 这张图里有猫吗? → 有/没有 │
│ │
│ 回归问题 (Regression) │
│ 输入一张图 → 输出: (x=120, y=85, w=200, h=150) │
│ 例: 猫在哪个位置?框多大? → 四个连续数值 │
└─────────────────────────────────────────────────────────────┘

数学本质上

  • 分类:模型学的是决策边界,把输入空间切分成不同类别区域
  • 回归:模型学的是连续映射函数 $f: \text{image} \to \mathbb{R}^n$,直接从像素映射到实数

1.2 为什么目标检测天然不像是回归问题?

传统上,目标检测不用回归解决,是因为有个致命障碍

1
2
3
4
5
6
回归要求:  输入 → 固定维度的输出
(一张图 → 你知道要输出多少个数值)

目标检测: 输入 → 图片里有几个物体?你事先不知道!
可能是 0 个、1 个、3 个、10 个...
输出维度不固定 → 传统回归框架装不下

这就是为什么 R-CNN 系列走「分类路线」:

  1. 先想办法找出候选区(可能有物体的地方)
  2. 对每个候选区分别做分类(这个区域是猫还是背景?)
  3. 对每个候选区微调位置(回归修正框的坐标)

本质上是把检测拆成了「分类 + 小回归」,绕开了输出维度不固定的难题。

1.3 YOLO 怎么把检测变成回归的?—— 固定网格的妙想 🔑

YOLO 的核心洞察:把图像切成固定网格,每个网格固定输出一组数值

1
2
一张图切成 S×S 网格 → 每个网格固定预测 B 个框 + C 个类别
→ 输出维度永远是 S×S×(5B+C) → 固定了!回归可以做了!
传统思路 YOLO 的思路
“图里有几个物体?先找出来,再一个个分类” “不管几个物体,我把图切成 49 格,每格固定输出一组预测”
输出维度不固定 → 不能直接回归 输出维度固定 → 可以端到端回归
需要 region proposal 步骤 不需要,整张图一次性出结果
多个模型/步骤串行 单个 CNN 端到端

💡 关键的思维转换:不是”找出物体再分类”,而是”每个位置都预测,相信总有一个位置能对上”。

1.4 YOLO 回归的具体内容

YOLO 从一个 CNN 里直接回归出两类连续值:

回归内容 具体数值 类型
BBox 坐标 $(x, y, w, h)$ 连续实数(位置和大小)
置信度 $\text{confidence} \in [0,1]$ 连续实数(有物体的确信程度)
类别概率 $P(\text{class}_i) \in [0,1]$ 连续实数(各类别的概率值)

所有这些值都是直接从像素回归出来的实数,中间没有”这个区域是不是物体”的二分类判断步骤。

对比:Faster R-CNN 的 RPN 阶段先做二分类(有物体/无物体),再做回归(修框)。YOLO 把这全部压缩进一次回归。

1.5 深入理解:凭什么说它是”回归”?—— 人定规则 ≠ 不是回归 🔥

很多人读到这会困惑:“网格分配、IoU 选框、confidence 公式……不都是人设计的规则吗?模型只是照规则输出,这也能叫回归?”

这是一个关键误区:把”人怎么设计 label”和”模型在解什么数学问题”混为一谈了。

1
2
3
4
5
6
┌────────────────────────────────────────────────────────────┐
│ 人设计 label 规则 (教什么) ≠ 模型解的数学问题 (怎么学) │
│ │
│ 前者是所有监督学习都做的事 │
│ 后者才是判断"回归还是分类"的依据 │
└────────────────────────────────────────────────────────────┘

用房价预测来类比——最纯粹的回归问题:

1
2
3
4
5
6
7
8
9
房价预测:
人设计的规则: "面积=100㎡, 3室, 学区的房子 → label 价格 = 500万"
↑ 凭什么? 人定的! 你也可以定 600 万。
模型做的事: 面积, 房间数, 学区 → [学一个函数] → 价格(连续实数)
↑ 这才是回归

你不会质疑"房价预测不是回归,因为价格 label 是人定的"。
因为你知道: 回归指的是模型在学一个「输入→实数」的连续映射,
label 怎么来的不重要。

YOLO 完全一样:

1
2
3
4
5
6
7
YOLO:
人设计的规则: "狗的中心在格(3,2) → label = [x=0.5, y=0.3, w=0.2, h=0.4, conf=0.8, 狗=1]"
↑ 凭什么狗归格(3,2)管? 人定的! (中心点规则)
模型做的事: 像素 → [CNN学一个函数] → 7×7×30 个连续实数
↑ 这才是回归

一模一样!

判断”是不是回归”只看三样东西,跟 label 规则无关:

1
2
3
4
5
6
7
┌──────────────────────────────────────────────────────────────┐
│ 判断维度 分类问题 YOLO(回归) │
│──────────────────────────────────────────────────────────────│
│ ① 模型输出什么? 离散标签("猫"/"狗") 连续实数(0.52,0.31…) │
│ ② 损失函数用什么? 交叉熵(Cross-Entropy) 均方误差(L2/MSE) │
│ ③ 模型学什么? 决策边界(分开各类) 连续映射 f:像素→ℝⁿ │
└──────────────────────────────────────────────────────────────┘

YOLO 三条全中回归:输出实数、L2 损失、学连续映射。

那 R-CNN 不也输出坐标吗?凭什么它不是”纯回归”?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
R-CNN 的做法:
Step 1: "这个区域有物体吗?" → 离散决策 (是/否)
↑ 这是分类! 学的是二分类决策边界
Step 2: 对有物体的区域, 微调框坐标 → 这步是回归
↑ 但只是 pipeline 中的一小步

R-CNN = 分类(找区域) + 回归(修框) = 混合体

YOLO 的做法:
像素 → CNN → 一次性吐出 7×7×30 个连续实数
全程没有任何"是/否"的离散判断节点
连类别概率都是用 L2 回归出来的, 不是 softmax 交叉熵

YOLO = 纯回归, 从头到尾

一句话总结这个误区:

“人设计了网格分配的规则”说明的是训练数据的 label 是怎么构造的——这和房价预测中”人给房子标价格”是同一件事。但 YOLO 之所以是回归,是因为模型在做的事情是从像素直接映射到连续实数,全程没有离散分类决策——这和房价预测中”从特征映射到价格”是同一类数学问题。

1.6 一句话总结

1
2
3
4
5
6
7
8
Two-Stage (R-CNN系列):
输入 → 候选区提取(RPN) → 对每个候选区分类+回归 → 输出 "看两眼,慢"

One-Stage (YOLO):
输入 → [单个CNN] → 输出 "只看一眼,快"

YOLO 把检测变成回归的关键:
固定网格划分 → 固定输出维度 → 端到端回归所有坐标和类别 → 极快!

YOLO vs Faster R-CNN 关键差异:

  • YOLO 没有显式求解 region proposal,整个网络统一训练
  • YOLO 同时预测位置(回归)和类别(分类),而非分两步
  • Faster R-CNN 需反复训练 RPN 和 Fast RCNN,YOLO 端到端一步搞定

二、网络架构

2.1 整体结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输入: 448×448×3

┌────────────────────────────────────┐
│ 24个卷积层 (特征提取) │
│ 前20层: 1×1 降维 → 3×3 提取特征 │ ← 借鉴GoogLeNet, 但用1×1+3×3
│ 穿插 4个 Max Pooling (2×2, stride=2)│ 替代Inception Module
└────────────────┬───────────────────┘

┌────────────────────────────────────┐
│ 2个全连接层 (预测输出) │
│ FC1: 4096维 (实际实现中首层局部连接) │
│ FC2: 7×7×30 = 1470维 │
└────────────────┬───────────────────┘

输出: 7×7×30 张量

2.2 设计灵感与关键组件

组件 配置 作用
卷积层 24 层 1×1降维 + 3×3特征提取,替代 Inception Module
激活函数 Leaky ReLU (0.1) $f(x)=\max(0.1x, x)$,防止神经元在负区间彻底”死亡”
池化层 4 层 Max Pooling (2×2, s=2) 逐步下采样:448→224→112→56→28→14→7
Dropout 第1个FC层后,rate=0.5 防止过拟合
数据增强 随机缩放、平移、曝光+饱和度调整 提升泛化能力

2.3 下采样过程

1
2
3
448×448 → Pool1 → 224×224 → Pool2 → 112×112
→ Pool3 → 56×56 → Pool4 → 28×28 → Pool5 → 14×14
→ 最终特征图: 7×7×1024 → FC → 7×7×30

⚠️ 实际实现中,第一个全连接层被替换为局部连接层,与论文有所不同。

⚠️ 重要限制: 由于使用了全连接层,YOLOv1 只支持与训练图像相同分辨率的输入图片。


三、图像预处理

3.1 输入处理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原始图像 (任意尺寸)

1. Resize: 缩放到 448×448
└── 保持宽高比,填充黑边或拉伸

2. Normalize: 像素值归一化到 [0, 1]
└── pixel = pixel / 255.0

3. 数据增强 (仅训练时):
├── 随机缩放: 随机调整图像尺寸 ±20%
├── 随机平移: 随机移动图像位置
├── 曝光调整: 随机改变亮度和对比度
└── 饱和度调整: HSV 空间随机扰动 1.5×

输入张量: [batch, 3, 448, 448]

四、预测阶段(前向推断)详解

4.1 完整推理流程

1
2
3
4
5
Step 1: Resize image → 448×448
Step 2: Run ConvNet → 输出 7×7×30 张量
Step 3: 解码 BBox 坐标 + 计算 class-specific confidence
Step 4: 阈值过滤 → 低分框置零
Step 5: NMS 去重 → 得到最终检测结果

4.2 网格划分策略

将 448×448 图像划分为 7×7 = 49 个网格

核心规则: 如果某个 object 的中心点落在某个网格内,则该网格负责预测该 object。

1
2
3
4
5
6
7
┌───────────────┐
│ │ │ │ │ │
│──┼──┼──┼──┼──│
│ │ │● │ │ │ ← ● = 物体中心点
│──┼──┼──┼──┼──│ 则该网格负责预测此物体
│ │ │ │ │ │
└───────────────┘

⭐ 通俗理解:这句话到底在说什么?

很多人在这一步卡住——“凭什么中心点在哪格就归哪格管?网络怎么知道的?”

答案:网络不知道。是你训练时教的。

1
2
不要把这句话理解成"网络的行为",
要理解成"你给训练数据贴标签的规则"。

为什么是”中心点”?——解决”一个物体跨多个格子,谁说了算”的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
猫的身体可能横跨 3 个格子:
┌──┬──┬──┬──┐
│ │ │ │ │
├──┼──┼──┼──┤
│ │◀══🐱══▶│ │ 猫占了 3 个格
├──┼──┼──┼──┤
│ │ │ │ │

如果三个格都说"猫在我这!" → 三个框 → 乱套!

约定: 只看猫的鼻子(中心点)落在哪
→ 只有中间那个格说"猫在我这"
→ 其他两格说"我这没东西"
→ 一个猫只有一个格管, 清清爽爽

打个比方——分田到户:

1
2
3
4
5
6
7
一个村(图片) → 分成 49 块地(网格) → 每块地一个农民(cell)

规则: 谁家地里有作物的"根"(中心点),这作物就归谁管。
根在你地里 → 你负责收割 → 你要报告"什么作物、多大"

农民只管自己一亩三分地。
根不在你地里的作物 → 报告"我这没有"就行。

训练时的实际过程:

1
2
3
4
5
6
7
8
9
10
你拿来一张训练图:
Step 1: 画上 7×7 格子
Step 2: 找到猫的中心在格(3,2)
Step 3: 在格(3,2)的答题卡上写: "正确答案: 有猫, 框在这个位置"
Step 4: 其他 48 个格的答题卡上写: "正确答案: 没东西"
Step 5: 让网络对答案, 错了就改参数

训练 10000 张图之后:
→ 网络学会了: "看到类似猫在类似位置, 格(3,2)就该输出那些值"
→ 这不是网络悟出来的, 是你用 10000 道题硬教出来的

不要理解错:

❌ 不要理解成 ✅ 应该理解成
网络主动判断”这个物体归格(3,2)管” 训练时人告诉网络: 格(3,2), 这道题你的正确答案是这个
网格是模型自己学会的”结构” 网格是人给训练数据贴标签的”分配方案”
“凭什么?” —— 网络有魔法 “凭什么?” —— 没凭什么, 人定的规矩, 为了方便教学

4.3 每个网格的预测内容

每个网格预测 B=2 个 Bounding Box + C=20 个类别概率(PASCAL VOC)。

BBox 预测 (每个 BBox 5 个值):

含义 归一化方式
x, y BBox 中心点坐标 相对于当前网格左上角,归一化到 [0, 1]
w, h BBox 宽和高 相对于整张图片的宽高,归一化到 [0, 1]
confidence 置信度 见下方公式

4.4 坐标解码公式

编码(网络输出):

  • $x, y$: 相对于所在网格左上角偏移,归一化到 [0,1]
  • $w, h$: 相对于整张图宽高,归一化到 [0,1]

解码(从网格坐标还原实际坐标):

设网格位置为 $(c_x, c_y)$,网格宽度为 $g_w$,网格高度为 $g_h$:

$$\text{实际中心X} = (c_x + x) \times g_w$$
$$\text{实际中心Y} = (c_y + y) \times g_h$$
$$\text{实际宽度W} = w \times \text{image_width}$$
$$\text{实际高度H} = h \times \text{image_height}$$

例如:图片 448×448,网格(2,3)位于第2列第3行,网格宽=448/7=64。若预测 x=0.5, 则实际中心X = (3+0.5)×64 = 224。

4.5 置信度 Confidence 详解

置信度由两个因素组成:

$$\text{confidence} = \text{Pr(Object)} \times \text{IoU}_{pred}^{truth}$$

因子 含义 取值
Pr(Object) 该 BBox 包含目标的可能性 有目标 → 1,无目标 → 0
IoU(truth, pred) 预测框与真实框的交并比 [0, 1]

训练时:confidence 的 label 按 $\text{Pr(Object)} \times \text{IoU}$ 计算——有物体时 Pr=1,label=IoU;无物体时 Pr=0,label=0。且 IoU 是动态变化的(每轮预测框都在变)。

推理时:网络直接输出一个 confidence 值。不需要也不可能分别算出 Pr(Object) 和 IoU(因为没有 GT 框!)。网络在训练中学会了直接输出这个乘积值——它隐含地包含了”有没有物体”和”框得准不准”两重信息。

💡 类比:训练时你教网络”conf = 有物体×IoU”这个公式,测试时网络直接吐出答案,你不需要拆开看中间步骤。

4.6 Class-Specific Confidence Score

这是最终用于 NMS 筛选的分数。

每个网格只预测一组类别概率(不管预测几个 BBox),即 $P(Class_i|Object)$。

$$\text{Score} = P(Class_i|Object) \times \text{confidence}$$
$$= P(Class_i|Object) \times \text{Pr(Object)} \times \text{IoU}_{pred}^{truth}$$

1
2
3
4
5
6
具体计算:
Box1 的 Score[car] = P(car|Object) × Box1.confidence
Box1 的 Score[dog] = P(dog|Object) × Box1.confidence

Box2 的 Score[car] = P(car|Object) × Box2.confidence ← 同一网格共享类别概率
Box2 的 Score[dog] = P(dog|Object) × Box2.confidence

每个网格产生: 2×20 = 40 个分数(2个BBox × 20类)
整张图产生: 49×2×20 = 1960 个分数


五、IoU 计算方法

5.1 IoU 定义

IoU (Intersection over Union) = 交并比,衡量两个矩形框的重叠程度。

$$\text{IoU} = \frac{\text{Area of Intersection}}{\text{Area of Union}} = \frac{A \cap B}{A \cup B}$$

5.2 几何图解

1
2
3
4
5
6
7
8
9
       ┌──────────┐
│ BBox A │
│ ┌───────│───┐
│ │ 交集 │ │
└──│───────┘ │
│ BBox B │
└───────────┘

IoU = 交集面积 / (A面积 + B面积 - 交集面积)

5.3 计算步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输入: BoxA(x1,y1,x2,y2), BoxB(x1,y1,x2,y2)

1. 计算交集:
inter_x1 = max(A.x1, B.x1)
inter_y1 = max(A.y1, B.y1)
inter_x2 = min(A.x2, B.x2)
inter_y2 = min(A.y2, B.y2)
inter_w = max(0, inter_x2 - inter_x1)
inter_h = max(0, inter_y2 - inter_y1)
inter_area = inter_w × inter_h

2. 计算并集:
A_area = (A.x2 - A.x1) × (A.y2 - A.y1)
B_area = (B.x2 - B.x1) × (B.y2 - B.y1)
union_area = A_area + B_area - inter_area

3. IoU = inter_area / union_area

5.4 IoU 在 YOLO 中的两个用途

用途 何时 说明
训练时选 BBox 反向传播 找出与 GT 框 IoU 最大的预测框,该框负责位置损失
NMS 去重 推理后处理 对同一类别的重叠框,IoU 高则移除得分低的

六、NMS(非极大值抑制)处理流程

NMS 的目标:从大量重叠的预测框中,每个物体只保留一个最好的框

6.1 逐类别 NMS 六步法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
输入: 所有 BBox (每框含:类别、得分、坐标)
NMS 阈值: IoU_thresh1 (通常是 0.5)

═══════════════════════════════════════
对每一类 c 分别执行:
═══════════════════════════════════════

Step 1: 得分过滤
for each BBox:
if score < score_thresh1:
score = 0 ← 直接移除低分框

Step 2: 按得分排序
剩余框按 score 降序排列
[Box_A:0.9, Box_B:0.85, Box_C:0.7, Box_D:0.6, ...]

Step 3: 选最高分 + 抑制重叠
取最高分 Box_A (score=0.9) 作为保留框
for 其余每个框 Box_i:
if IoU(Box_A, Box_i) > IoU_thresh:
将 Box_i 的 score 置 0 (抑制)

Step 4: 寻找下一个最高分
从未被抑制的框中选最高分 → Box_C (假设 Box_B 在 Step3 被抑制)

Step 5: 重复 Step 3-4
直到所有框都被处理(要么保留,要么抑制)

Step 6: 处理下一个类别
回到 Step 1,处理下一个类别

6.2 NMS 效果示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
处理前 (3个重叠框都预测 "dog"):
┌─────────────────┐
│ ┌──────┐ │
│ │ Box1 │──┐ │ Box1: score=0.9, IoU(Box1,Box2)=0.85
│ └──┬───┘ │ │ Box2: score=0.8, IoU(Box2,Box3)=0.75
│ │Box2 │ │ Box3: score=0.6
│ └──┬───┘ │
│ │Box3 │
│ └─────────│
└─────────────────┘

处理后 (仅保留 Box1):
┌─────────────────┐
│ ┌──────┐ │
│ │ Box1 │ │ ← 唯一保留
│ └──────┘ │
└─────────────────┘

七、训练阶段(反向传播)详解

7.1 训练参数设定

参数 含义
S 7 网格 7×7
B 2 每网格预测 2 个 BBox
C 20 PASCAL VOC 20 类
输入尺寸 448×448 固定分辨率
输出张量 7×7×30 $S \times S \times (5B + C)$

7.2 输出张量结构

1
2
3
4
5
6
7
7×7×30 张量的 30 个通道拆解:

通道 0-4: Box1 的 (x, y, w, h, confidence)
通道 5-9: Box2 的 (x, y, w, h, confidence)
通道 10-29: 20 个类别概率 P(Class₁), P(Class₂), ..., P(Class_20)

每个网格独立拥有这 30 个值!

7.3 检测框分配机制 —— 最难理解的核心 🔥🔥🔥

这是 YOLOv1 最绕、也最关键的概念。一句话总结:

物体中心落在哪个网格 → 该网格”负责”这个物体 → 该网格的两个 BBox 中 IoU 最大的那个”代表”这个网格去预测。

⭐ 前置理解:从”训练”和”测试”两个阶段分别看

很多人第一次读 YOLO 都会问:“凭什么物体的中心落在这个 cell,它就负责预测?”

答案是:这不是网络自己决定的,而是训练阶段你通过标签”教”它的。

1
2
3
4
5
6
7
8
9
训练阶段(你教网络):
你给图片打 label 时:
狗的中心在格(3,2) → 你把格(3,2)的 target 设为"你要预测狗"
车在格(5,4) → 你把格(5,4)的 target 设为"你要预测车"
网络通过反向传播学会: "哦,原来我应该预测中心在我这格里的物体"

测试阶段(网络照做):
网络已经学会了规则 → 自然会对中心在格(3,2)的物体做出响应
不需要"凭什么"——因为训练时就是这么教的

💡 这和教小孩认东西一样:你指着苹果说”这是苹果”(训练),小孩以后看到苹果就知道说”苹果”(测试)。”中心落在哪个 cell”只是你给训练样本贴标签的规则,不是网络自己悟出来的魔法。

第一步:网格分配 —— 谁”拥有”这个物体?

1
2
3
4
5
6
7
8
9
10
11
12
规则: 只看物体的几何中心点落在哪个网格里。

┌─────────────────────┐
│ │ │ │ │
│ │ │ │ │
│─────┼─────┼─────┼───│
│ │ │ ● │ │ ← 物体中心在网格 (3,2)
│ │ │ ╔═╗ │ │
│─────┼─────┼─╬═╬─┼───│
│ │ │ ║ ║ │ │ 网格(3,2) 声明:
│ │ │ ╚═╝ │ │ "这个物体归我管!"
└─────────────────────┘

关键推论:

  • ✅ 一个网格可以”拥有”多个物体吗? → 不可以(v1 的限制:每格只预测一组类别)
  • ✅ 一个物体可以被多个网格”拥有”吗? → 不可以(中心点只落在一个格子里)
  • ✅ 物体横跨多个网格怎么办? → 只看中心点,中心在哪格就归哪格

第二步:BBox 分配 —— 谁来”干活”?

1
2
3
4
5
6
7
8
9
10
11
网格(3,2) 有两个 BBox:
Box1: 预测了一个宽扁的框 ────┐
Box2: 预测了一个窄高的框 ────┤ ← 两个框都声称能预测

但只有一个可以"正式上岗"! │

谁和 GT 框的 IoU 更大,谁就获得这个权利 ←┘

例如:
Box1 与 GT 的 IoU = 0.3 ❌ 落选
Box2 与 GT 的 IoU = 0.7 ✅ 当选! (称为 responsible box)

这时

  • Box2 的 (x,y,w,h) 参与位置损失,努力学习去拟合 GT 框
  • Box1 的 (x,y,w,h) 不参与位置损失,但 confidence 被监督为 0(”你预测得不好”)
  • Box2 的 confidence 被监督为 当前 IoU=0.7(”你的 confidence 应该反映你的 IoU”)

第三步:完整分配链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
图片 (448×448)

▼ 划分为 7×7 网格

├── 网格(2,1): 无物体中心 → 什么都不负责 (只监督 confidence→0)
├── 网格(3,2): 有物体中心 → 负责该物体!
│ │
│ ├── Box1: IoU=0.3 → 不负责位置,confidence→0
│ └── Box2: IoU=0.7 → ★ Responsible Box!
│ ├── 参与位置损失 (学习拟合 GT)
│ ├── 参与置信度损失 (学习输出高 confidence)
│ └── 该网格的类别概率也参与损失

├── 网格(4,2): 无物体中心 → 什么都不负责
└── ... (其余 46 个网格)

具体例子:一张图的完整分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
假设一张训练图片中有 3 个物体: 狗(中心在格(3,2))、车(中心在格(5,4))、人(中心在格(1,6))

═══════════════════════════════════════════════════
49个网格中:
3个网格"有责" (包含物体中心) → 计算位置+置信度+类别损失
46个网格"无责" (无物体中心) → 仅计算置信度损失(→0)
═══════════════════════════════════════════════════

网格(3,2) - 负责"狗":
Box1 与 GT 的 IoU = 0.4 → 不参与位置损失
Box2 与 GT 的 IoU = 0.8 → ★ 参与位置损失
类别: [狗=1, 车=0, 人=0, ...]

网格(5,4) - 负责"车":
Box1 与 GT 的 IoU = 0.6 → ★ 参与位置损失
Box2 与 GT 的 IoU = 0.2 → 不参与位置损失
类别: [狗=0, 车=1, 人=0, ...]

网格(1,6) - 负责"人":
Box1 与 GT 的 IoU = 0.5 → ★ 参与位置损失
Box2 与 GT 的 IoU = 0.45 → 不参与位置损失
类别: [狗=0, 车=0, 人=1, ...]

其余46个网格:
Box1: confidence 监督为 0
Box2: confidence 监督为 0
类别: 不参与损失

为什么要这样设计?

问题 如果不这样设计会怎样? YOLO 的设计
两个 BBox 都参与位置损失? 两个框争抢同一个物体,互相干扰 只让 IoU 最大的那个学习,另一个”退让”
所有网格都预测类别? 背景网格被迫输出假的物体类别 无物体的网格只监督 confidence→0
类别直接共享? 一个网格两个框预测不同类,NMS 后可能留两个同类框 共享类别 + 选最佳框 = 每格最多输出 1 个物体

两个灵魂追问 🔥

理解了上面的分配机制后,自然会产生两个问题。

追问一:如果两个 BBox 各有独立的类别预测,同个 cell 能预测两个物体吗?

1
2
3
4
5
6
7
8
9
10
11
假设每个 BBox 独立预测类别(不共享):

网格(3,2) 有物体 A 和 B:
Box1 类别预测: [狗=0.9, 猫=0.1] ← 像在预测狗
Box2 类别预测: [狗=0.1, 猫=0.9] ← 像在预测猫

问题: 训练时怎么给 Box1 和 Box2 分工?
Box1 应该说"我负责狗"还是"我负责猫"?不知道!
没有机制来分配"谁负责哪个物体"。

→ 答案: ❌ 不行!因为缺乏分配机制。

这也是为什么 YOLOv2 要引入 Anchor:Anchor 的形状天然决定了”谁更适合预测哪个物体”——细长的 Anchor 适合人,宽扁的 Anchor 适合车。有了这个分配依据,每个 Anchor 才能独立预测不同的类别。

追问二:既然一个 cell 只能预测一个物体,为什么要设计 2 个 BBox?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
答案: "找两个人并行干活,在线选做得更好的那个。"

训练时:
Box1 预测了一个框 → 跟 GT 算 IoU = 0.3
Box2 预测了另一个框 → 跟 GT 算 IoU = 0.8
→ 选 Box2! (IoU 更大)
→ Box1 被抑制

好处: 两个 predictor 各自从不同"角度"去猜测物体位置
→ 增加命中率(总有一个猜得更准)
→ 网络在线选出最好的那个来优化

类比: 两个学生做同一道题,老师看谁做得好就表扬谁,
另一个人虽然这次没被表扬,但下次可能做得更好。

这个设计的核心缺点

1
2
3
4
5
6
7
8
9
10
问题场景: 两个物体的中心落在同一个网格

┌───────────┐
│ 🐶 🐱 │ ← 狗和猫的中心都在同一个网格!
│ │
└───────────┘

YOLOv1 的处理: 该网格只能预测一个类别 → 必然有一个被忽略
这就是"每格最多检测一个物体"限制的根源!
v2 引入 Anchor Box 后解决 → 每个 Anchor 可以独立预测不同类别

7.4 训练时的 Ground Truth 构造

基于以上分配机制,构造 7×7×30 的 target 张量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
对于坐标为 (i, j) 的网格:
├── 如果该网格包含物体中心:
│ ├── class[gt_label] = 1 (one-hot)
│ ├── 选与 GT IoU 最大的 BBox (设为 Box_k):
│ │ ├── x = gt_center_x - i (相对于网格左上角)
│ │ ├── y = gt_center_y - j
│ │ ├── w = gt_w / image_width (相对于整张图)
│ │ ├── h = gt_h / image_height
│ │ └── confidence = 1 (存在物体, Pr(Object)=1)
│ └── 另一个 BBox:
│ └── confidence = 0 (不参与位置, 只监督 confidence→0)

└── 如果该网格不包含物体中心:
├── class = all 0 (不参与类别损失)
├── Box1: confidence = 0
└── Box2: confidence = 0

7.5 训练时的 Confidence 计算细节

这是一个关键细节:训练时 confidence 的 ground truth 是动态变化的

情况 Confidence GT 值 说明
有物体的网格 + IoU 最大的 BBox $\hat{C}i = \text{IoU}{pred}^{truth}$ 训练时不断用最新预测框与 GT 计算 IoU!
有物体的网格 + 另一个 BBox $\hat{C}_i = 0$ 不参与位置损失,confidence也不学习
无物体的网格 + 两个 BBox $\hat{C}_i = 0$ 两个框的 confidence 都应趋于 0

⚠️ 注意:有物体的网格中,$\hat{C}_i$ = 当时的 IoU 值。每轮训练预测的 BBox 位置都在变,所以 IoU 也在变,Confidence 的 ground truth 是动态的

7.6 哪些 BBox 参与哪种 Loss?

这是 YOLOv1 最容易搞混的地方,总结如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
对于一个有物体的网格 (i, j):
┌────────────────────────────────────────────────────────┐
│ │
│ Box_gt = argmax_{j} IoU(Box_j, GroundTruth) │
│ ↑ 选与真实框 IoU 最大的那个预测框 │
│ │
│ Box_gt 参与: 位置损失 ✅ 置信度损失 ✅ │
│ Box_other 参与: 位置损失 ❌ 置信度损失 ✅ (confidence→0)│
│ │
│ 类别损失: 网格参与 ✅ (使用 GT 类别标签) │
└────────────────────────────────────────────────────────┘

对于一个无物体的网格 (i, j):
┌────────────────────────────────────────────────────────┐
│ 两个 Box 都参与: 位置损失 ❌ 置信度损失 ✅ (confidence→0)│
│ 类别损失: ❌ 不参与 │
└────────────────────────────────────────────────────────┘

7.7 损失函数完整解析

$$\mathcal{L} = \underbrace{\lambda_{coord}\sum_{i=0}^{S^2}\sum_{j=0}^{B}\mathbb{1}_{ij}^{obj}\left[(x_i-\hat{x}_i)^2+(y_i-\hat{y}i)^2\right]}{\text{① 有obj网格中最佳BBox的中心坐标损失}}$$

$$+ \underbrace{\lambda_{coord}\sum_{i=0}^{S^2}\sum_{j=0}^{B}\mathbb{1}_{ij}^{obj}\left[(\sqrt{w_i}-\sqrt{\hat{w}_i})^2+(\sqrt{h_i}-\sqrt{\hat{h}i})^2\right]}{\text{② 有obj网格中最佳BBox的宽高损失,对w,h取平方根}}$$

$$+ \underbrace{\sum_{i=0}^{S^2}\sum_{j=0}^{B}\mathbb{1}_{ij}^{obj}(C_i-\hat{C}i)^2}{\text{③ 有obj网格中所有BBox的置信度损失,}\hat{C}i=\text{IoU}{pred}^{truth}}$$

$$+ \underbrace{\lambda_{noobj}\sum_{i=0}^{S^2}\sum_{j=0}^{B}\mathbb{1}_{ij}^{noobj}(C_i-\hat{C}i)^2}{\text{④ 无obj网格中所有BBox的置信度损失,}\hat{C}_i=0}$$

$$+ \underbrace{\sum_{i=0}^{S^2}\mathbb{1}{i}^{obj}\sum{c \in classes}(p_i(c)-\hat{p}i(c))^2}{\text{⑤ 有obj网格的类别概率损失}}$$

⚠️ 一个容易被忽略的细节:注意第⑤项——类别损失用的也是均方误差(L2 Loss),而不是分类任务中常见的交叉熵。

这意味着 YOLOv1 在数学上彻底把目标检测变成了回归问题——位置是回归、置信度是回归、连类别预测都是回归(输出连续概率值,用 L2 监督)。

这在当时是不寻常的(分类用交叉熵是常识),也是 YOLOv1 损失函数比较”粗糙”的原因之一。后续版本(v3)就把分类改回了交叉熵。

7.8 损失函数权重设计原理

参数 原因
λ_coord = 5 定位权重 定位比分类更重要,增加梯度信号
λ_noobj = 0.5 无目标惩罚权重 49个网格中大多数没有物体,不惩罚太狠避免训练发散
w,h 取平方根 非线性映射 小框 1像素偏差比大框 1像素偏差影响更大,平方根缩小差异

为什么 w,h 要用平方根?

1
2
3
4
5
6
大框:  w=100, δ=5  →  误差贡献 ∝ 5² = 25
小框: w=10, δ=5 → 误差贡献 ∝ 5² = 25 ← 相同惩罚!不合理!

用平方根后:
大框: √100=10.0, 预测 √105≈10.25, (0.25)²=0.0625
小框: √10≈3.16, 预测 √15≈3.87, (0.71)²=0.504 ← 惩罚约8倍!

平方根让小框的定位偏差被更大惩罚,符合人类直觉:在小物体上偏一点比在大物体上偏一点更不能接受。

7.9 训练流程伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 简化版 YOLOv1 训练循环

for epoch in range(156):
for images, targets in dataloader: # images: [B,3,448,448]

# 1. 前向传播
predictions = model(images) # [B,7,7,30]

# 2. 解析预测结果
for each grid cell (i,j) in 7×7:
box1 = predictions[i,j,0:5] # x,y,w,h,conf
box2 = predictions[i,j,5:10] # x,y,w,h,conf
class_probs = predictions[i,j,10:30] # softmax over 20 classes

# 3. 确定该网格是否有物体
if object_center_in_cell(i,j):
# 找与 GT IoU 最大的 BBox
iou1 = compute_iou(decode(box1), GT_box)
iou2 = compute_iou(decode(box2), GT_box)

# 最佳 BBox 参与位置损失
best_box = argmax(iou1, iou2)
loc_loss += coord_loss(best_box, GT_box)

# 所有 BBox 参与置信度损失
for box in [box1, box2]:
conf_loss += (box.conf - IoU(box,GT))²

# 类别损失
cls_loss += (class_probs - GT_onehot)²
else:
# 无物体: 只计算置信度损失 (target=0)
conf_loss += λ_noobj × (box1.conf - 0
conf_loss += λ_noobj × (box2.conf - 0

# 4. 总损失
total_loss = λ_coord×loc_loss + conf_loss + cls_loss

# 5. 反向传播 + 优化
total_loss.backward()
optimizer.step()

八、优缺点深度分析

优点 ✅

  1. 极快: 45 FPS (标准) / 150 FPS (极速版),真正的实时检测
  2. 背景误检率低: 全局推理看整张图,不像 R-CNN 只看候选区域 → 背景误检率仅 4.75% (Fast R-CNN 13.6%)
  3. 迁移能力强: 泛化到艺术品检测等非自然图像领域效果远超 DPM 和 R-CNN
  4. Pipeline 简单: 统一回归,端到端训练

局限 ❌

  1. 输入尺寸固定: FC 层要求固定分辨率(448×448),不能处理任意尺寸
  2. 每格最多检测 1 个物体: 每个网格只预测一组类别,密集小物体(鸟群、人群)效果差
  3. 小物体定位差: 平方根缓解但不根治,小物体定位精度不如大物体
  4. 泛化弱: 对不常见角度/长宽比的目标表现不佳
  5. 损失函数粗糙: 分类也用 SSE,不如交叉熵合理(v3 改进)

九、性能表现

指标 YOLOv1 Fast R-CNN Faster R-CNN
速度 45 FPS / 150 FPS ~0.5 FPS 7 FPS
mAP (VOC 2007) 63.4% 70.0% 73.2%
mAP (VOC 2012) 57.9% 70.4%
背景误检率 4.75% 13.6%

十、训练配置 (yolo-master From Scratch)

📦 参考: [[30.resources/yolo-master/docs/Pytorch_YoLo_From_Scratch/v1/YOLOv1.ipynb|YOLOv1 Notebook]]

配置项 说明
数据集 PASCAL VOC 2007+2012 train/val 2007 + train/val 2012
测试集 VOC 2007 test
Batch Size 64 ~14GB 显存
Epochs 156
单 Epoch 耗时 ~140s
总训练时间 ~6h
优化器 SGD + Momentum 动量 0.9
学习率调度 分段衰减 前75 epoch: 1e-2, 后30: 1e-3, 最后: 1e-4
权重衰减 5e-4 L2 正则化
mAP 最终 79.4% (VOC) 对齐官方 darknet 实现

Notebook 结构:

1
2
3
4
5
数据准备: VOC下载 → 格式转换 → DataLoader
网络定义: 各模块代码 → 网络拼接
损失函数: 坐标损失 + 置信度损失 + 分类损失
训练循环: 前向 → 损失计算 → 反向传播
评估: mAP计算 → NMS后处理 → 可视化

十一、相关链接

  • 📝 [[YOLO 系列总览]] — 版本对比总览
  • 📝 [[YOLO 核心概念]] — Grid/Anchor/IoU/NMS 详解
  • 📝 [[YOLOv2 详解]] — 锚框机制 + BN 改进
  • 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLO/YOLO学习笔记1——YOLOv1详解]] — 知乎原文 (rtfff)
  • 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLO/YOLO系列算法精讲:从yolov1至yolov8的进阶之路(2万字超全整理)-CSDN博客]] — CSDN 原文 (AI菌)
  • 📦 [[30.resources/yolo-master/docs/Pytorch_YoLo_From_Scratch/v1/README|v1 From Scratch 教程]]