犀牛鸟 2026 研究笔记 estelledc.github.io

第 3 章 · 目标检测基础——从像素到边界框

开场:快递分拣站的工作流

假设你在一个快递分拣站工作。传送带上一个接一个地过来包裹,你的任务是:看一眼每个包裹上的标签,判断它要去哪个城市(分类),然后把它放到对应的分拣筐里(定位)。

目标检测模型做的事情本质上一样:接收一张图片,输出”图里有什么、在哪里”。不同的是,快递分拣站一次处理一个包裹,而检测模型需要一次性处理图中所有目标——可能是几个,也可能是几十个。

这一章,我们从最基础的概念开始,搞清楚检测模型的输入是什么、输出是什么、中间经历了什么变换、怎么评价好坏。这些基础知识是后面所有章节的地基。


一、检测流水线:从图像到预测

任何一个现代目标检测模型,不管是 YOLO、RT-DETR 还是 Faster R-CNN,都可以拆成三大组件。用做菜来类比:

 原料处理         烹饪加工         装盘上菜
    │               │               │
    ▼               ▼               ▼
 Backbone          Neck            Head
(骨干网络)     (颈部网络)     (检测头)
    │               │               │
 提取特征        融合多尺度       输出预测

Backbone(骨干网络)——原料处理

Backbone 的作用是把原始图像(像素矩阵)转换成有意义的特征表示。就像厨师先把原材料清洗、切配——原始像素里的信息太多太杂,需要提炼出有用的”特征”。

以 YOLOv8 为例,Backbone 是一个 CSPDarknet 结构,由多层卷积和残差块组成。图像经过 Backbone 后,会输出多个尺度的特征图:

输入图像:640 × 640 × 3(RGB 三通道)
    │
    ├── 经过多层卷积和下采样 ──→ P3: 80 × 80 × 256   (stride=8,分辨率高,适合小目标)
    │
    ├── 继续下采样 ──────────→ P4: 40 × 40 × 512   (stride=16,中等目标)
    │
    └── 继续下采样 ──────────→ P5: 20 × 20 × 1024  (stride=32,分辨率低,适合大目标)

这里的 stride 是什么意思?stride=8 表示特征图上一个像素对应原图 8×8 的区域。P3 特征图分辨率最高(80×80),保留了更多空间细节,适合检测小目标;P5 分辨率最低(20×20),但每个像素”看到”的范围最大(32×32 的区域),适合检测大目标。

日常类比:这就像用不同倍数的显微镜观察同一个样本——低倍(P5)看全局结构,高倍(P3)看细节。检测不同大小的目标,需要不同倍数的”镜头”。

Neck(颈部网络)——烹饪加工

Backbone 输出的多尺度特征各有各的”视角”:P3 有空间细节但缺少语义信息(知道纹理但不知道是什么),P5 有语义信息但缺少空间细节(知道是汽车但不知道精确位置)。Neck 的作用是把这些不同尺度的特征融合起来,让每个尺度的特征都兼具空间细节和语义信息。

最常用的 Neck 结构是 FPN + PAN(也叫 PANet):

Backbone 输出            FPN (Top-Down)           PAN (Bottom-Up)
                         自顶向下传递语义         自底向上传递细节

P5 (20×20) ─────→ 上采样 + 融合 ────→ 下采样 + 融合 ────→ 输出 P5'
                        │                        ↑
P4 (40×40) ─────→ 融合 ←─┘ 上采样 + 融合 → 融合 ←─┘ ──→ 输出 P4'
                                │                ↑
P3 (80×80) ──────────→ 融合 ←──┘ ────→ 融合 ←──┘ ──→ 输出 P3'

日常类比:FPN 就像公司里”从总裁到员工”的信息传递——高层战略(语义信息)向下传达到基层(高分辨率特征)。PAN 则是”从员工到总裁”的反向传递——一线数据(空间细节)向上汇报。两轮传递之后,每一层都既知道”大局”也知道”细节”。

Head(检测头)——装盘上菜

Head 是模型的最后一步,负责在每个位置输出检测结果。现代 YOLO 使用解耦头(Decoupled Head):分类和回归分别用独立的卷积分支处理——因为”判断是什么”和”判断在哪”是两种不同的任务,共享参数反而互相干扰。

                         ┌─ Conv → Conv → 分类分支 → Sigmoid → [c1, c2, ..., c80]
特征图 (每个位置) ──────┤
                         └─ Conv → Conv → 回归分支 → DFL解码 → [x, y, w, h]
                                                   └→ 目标性分数 (objectness)

对于 COCO 数据集(80 个类别),每个位置会输出:80 个分类分数 + 4 个边界框坐标 + 1 个目标置信度 = 85 个数值。

80×80 的特征图有 6400 个位置,40×40 有 1600 个,20×20 有 400 个——三个尺度加起来,一个 YOLOv8 模型在一次前向传播中会产生 8400 个预测框。当然,绝大部分预测框的置信度都很低(对应背景区域),只有少数是有意义的检测结果。


二、边界框表示:怎么描述一个框?

“在图上画一个矩形框”看似简单,但怎么用数字表示这个框,是检测模型设计的基础选择。

表示方式一:(x1, y1, x2, y2)——两个角点

(x1, y1) ──────────────────┐
│                           │
│         目标区域           │
│                           │
└──────────────────── (x2, y2)

(x1, y1) 是左上角坐标,(x2, y2) 是右下角坐标。这种表示直观,计算 IoU 时方便。COCO 评估工具内部就用这种格式。

表示方式二:(cx, cy, w, h)——中心点 + 宽高

              w
    ┌─────────────────┐
    │                 │
  h │    (cx, cy)     │
    │        ●        │
    │                 │
    └─────────────────┘

(cx, cy) 是框的中心坐标,w 和 h 是宽和高。YOLO 系列模型的预测输出通常用这种格式,因为中心点和宽高比角点更适合做回归(中心点自然和特征图位置对应,宽高自然是正数)。

表示方式三:(x, y, w, h) + 偏移量——COCO 标注格式

COCO 数据集的标注用左上角 (x, y) + 宽高 (w, h)。

不同格式之间的转换

def xyxy_to_cxcywh(x1, y1, x2, y2):
    """角点格式 → 中心点格式"""
    cx = (x1 + x2) / 2        # 中心 x = 两个 x 的平均
    cy = (y1 + y2) / 2        # 中心 y = 两个 y 的平均
    w = x2 - x1               # 宽 = 右边 - 左边
    h = y2 - y1               # 高 = 下边 - 上边
    return cx, cy, w, h

def cxcywh_to_xyxy(cx, cy, w, h):
    """中心点格式 → 角点格式"""
    x1 = cx - w / 2           # 左上 x = 中心 x - 半宽
    y1 = cy - h / 2           # 左上 y = 中心 y - 半高
    x2 = cx + w / 2           # 右下 x = 中心 x + 半宽
    y2 = cy + h / 2           # 右下 y = 中心 y + 半高
    return x1, y1, x2, y2

这些转换虽然简单,但在代码里出现的频率极高——几乎每个检测项目的 utils/ 目录下都有这样的转换函数。


三、Anchor Box:检测的”参考框”

什么是 Anchor?

Anchor box(锚框)是一种预定义的参考框。检测模型不是”从零”预测一个边界框的绝对坐标,而是预测相对于 anchor 的偏移量。

日常类比:你在白板上画一个矩形。如果让你”从头画”,你需要精确标出四个角的位置——很难画准。但如果白板上已经有一个浅灰色的虚线框(anchor),你只需要说”比这个框往右移 2cm、高度减少 1cm”——相对调整就容易多了。

Anchor-based vs Anchor-free

早期的 YOLO(v1-v5)和 Faster R-CNN 都是 anchor-based 的:在特征图的每个位置预设多个不同宽高比的 anchor,模型预测偏移量。但 anchor 的大小和比例需要手动设计或用聚类算法从数据中确定,增加了超参数。

从 YOLOv8 开始,YOLO 转向了 anchor-free:

Anchor-based(YOLOv5 及之前):
  特征图每个位置有 3 个预设 anchor → 模型预测 3 组 (dx, dy, dw, dh) 偏移
  预设 anchor 大小需要匹配数据集特点(COCO vs 自定义数据集)

Anchor-free(YOLOv8+):
  特征图每个位置直接预测 4 个距离值(到上下左右四条边的距离)
  不需要预设 anchor,更简洁

anchor-free 的核心思想是:与其预测”这个框相对于预设框偏了多少”,不如直接预测”目标的四条边距离当前位置有多远”——更直接,也减少了超参数。

YOLO-Master 用的是哪种? anchor-free。因为 YOLO-Master 基于 Ultralytics (YOLOv8) 开发,继承了其 anchor-free 设计。


四、核心评价指标:怎么判断检测模型好不好?

训练出一个检测模型后,怎么客观评价它的好坏?目标检测领域有一套标准的评价体系。

IoU(Intersection over Union,交并比)

IoU 衡量两个框的重叠程度,是所有检测指标的基础。

IoU = 两个框的交集面积 / 两个框的并集面积

    ┌────────────┐
    │  框 A      │
    │      ┌─────┼─────┐
    │      │ 交集│     │
    └──────┼─────┘     │
           │    框 B    │
           └───────────┘

交集 = 重叠区域的面积
并集 = 框 A 面积 + 框 B 面积 - 交集面积(减一次是因为交集被算了两次)

IoU 的范围是 0 到 1:0 表示完全不重叠,1 表示完全重合。

def compute_iou(box1, box2):
    """计算两个框的 IoU,输入格式 (x1, y1, x2, y2)"""
    # 计算交集区域的坐标
    inter_x1 = max(box1[0], box2[0])       # 交集左边取两框左边的较大值
    inter_y1 = max(box1[1], box2[1])       # 交集上边取两框上边的较大值
    inter_x2 = min(box1[2], box2[2])       # 交集右边取两框右边的较小值
    inter_y2 = min(box1[3], box2[3])       # 交集下边取两框下边的较小值

    # 交集面积(如果不重叠,面积为 0)
    inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1)

    # 各自面积
    area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
    area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])

    # IoU = 交集 / 并集
    iou = inter_area / (area1 + area2 - inter_area + 1e-6)  # 加 1e-6 防除零
    return iou

关键阈值:在 COCO 评估中,IoU ≥ 0.5 被认为是一次”成功的检测”。IoU ≥ 0.75 是更严格的标准。

Precision(精确率)和 Recall(召回率)

这两个指标回答不同的问题:

Precision = TP / (TP + FP) = 正确检测 / 所有检测结果
Recall    = TP / (TP + FN) = 正确检测 / 实际目标总数

其中:
  TP (True Positive)  = 正确检测(模型说有,确实有,且 IoU ≥ 阈值)
  FP (False Positive) = 误检(模型说有,但实际没有)
  FN (False Negative) = 漏检(模型没检测到,但实际有)

日常类比:你是安检员,在一批行李中找违禁品。Precision 是”你报警的行李中真正有违禁品的比例”(报警准不准?),Recall 是”所有有违禁品的行李中你发现了多少”(漏掉了多少?)。

理想情况当然是两个都高,但现实中它们往往此消彼长:如果你把检测阈值设得很高(只在很确定时才报告),Precision 高但 Recall 低(会漏掉一些不太确定的目标);反过来阈值设低,Recall 高但 Precision 低(会有更多误报)。

AP(Average Precision,平均精度)

AP 是精确率-召回率曲线(PR 曲线)下的面积。它综合了不同阈值下的 Precision 和 Recall,给出一个单一数值来评价模型在某一类别上的整体表现。

计算过程:

1. 按置信度从高到低排序所有检测结果
2. 依次将每个检测标记为 TP 或 FP(基于 IoU 阈值)
3. 在每一步计算当前的 Precision 和 Recall
4. 画出 PR 曲线
5. 计算曲线下面积 = AP

mAP(mean Average Precision,平均精度均值)

mAP 是所有类别的 AP 的平均值——检测领域最通用的综合指标。

mAP = (AP_person + AP_car + AP_dog + ... + AP_clock) / 80    (COCO 80 类)

在 COCO 评估中,标准的 mAP(也写作 AP)是在 10 个 IoU 阈值(0.5, 0.55, 0.60, …, 0.95)上求平均的,写作 AP@[0.5:0.95]。这比只看 AP@0.5 更严格——不仅要检测到目标,还要框画得很准。

写法 含义 严格程度
AP@0.5(也叫 AP50) IoU ≥ 0.5 就算对 宽松
AP@0.75(也叫 AP75) IoU ≥ 0.75 才算对 严格
AP@[0.5:0.95](COCO 标准 mAP) 10 个阈值取平均 最全面

当我们说”YOLOv8-S 在 COCO 上 mAP 44.9”时,指的就是 AP@[0.5:0.95] = 44.9%。

FPS(Frames Per Second,帧率)

速度指标。每秒能处理多少帧图像。在实时检测中通常要求 ≥ 30 FPS。

注意 FPS 数值的比较需要在相同硬件和相同输入分辨率下才有意义。论文中常见的测试平台是 NVIDIA T4 GPU + 640×640 输入。

FLOPs(Floating Point Operations,浮点运算数)

模型计算量的理论指标,不依赖于具体硬件。通常以 GFLOPs(10 亿次浮点运算)为单位。FLOPs 越低,模型在任何硬件上都倾向于跑得更快(但实际速度还受内存带宽、算子优化等因素影响)。

五个项目的关键数值

模型 mAP (COCO) Params (M) FLOPs (G) T4 FPS
YOLO-Master-S ~46.5 ~9.7 ~29.4 ~250
YOLOv8-S 44.9 11.2 28.6 ~400
YOLOv10-S 46.3 7.2 21.6 ~450
RT-DETR-R18 46.5 20 60 ~217
RT-DETR-L 53.0 32 110 ~114

YOLO-Master-S 用更少的参数达到了高于 YOLOv8-S 的精度,这就是 MoE “自适应计算”的效果——不是模型更大,而是算力分配更聪明。


五、损失函数:怎么告诉模型它错了?

模型训练的本质是:做一次预测,和真实答案对比,计算”有多差”(损失),然后调整参数让损失变小。检测模型需要同时优化两个目标:分类要准、框要画准。

分类损失(Classification Loss)

衡量模型预测的类别概率和真实类别之间的差距。

最基础的是交叉熵损失(Cross Entropy Loss),但检测中有个严重问题:正负样本极度不平衡。一张图里可能只有几个目标,但模型在 8400 个位置都做了预测——绝大部分位置是背景(负样本),只有极少数是目标(正样本)。如果简单用交叉熵,模型会学到”什么都不检测”这个捷径——因为预测全是背景时损失就已经很低了。

Focal Loss 解决了这个问题(RetinaNet 提出):

# 标准交叉熵:CE = -log(p)
# Focal Loss:FL = -alpha * (1-p)^gamma * log(p)
#
# 关键:(1-p)^gamma 这个因子
# 当 p 很大时(模型很确定的简单样本),(1-p)^gamma 接近 0 → 损失被降权
# 当 p 很小时(模型不确定的困难样本),(1-p)^gamma 接近 1 → 损失保持不变
#
# 效果:让模型把注意力集中在难分的样本上,而不是被大量简单的背景淹没

日常类比:Focal Loss 就像一个聪明的老师——学生已经会做的简单题(正确率高的背景样本)不再花时间讲,把精力集中在学生还不会的难题(困难样本)上。

回归损失(Regression Loss)

衡量预测框和真实框之间的差距。从简单到复杂有好几种选择:

L1 Loss:直接算坐标差的绝对值。简单但对大框小框一视同仁——一个 1 像素的误差对 10×10 的小框影响很大,对 500×500 的大框几乎没影响。

IoU Loss:直接用 1 - IoU 作为损失。更合理,因为 IoU 本身就是框质量的标准指标。但当两个框完全不重叠时(IoU = 0),梯度为零,模型无法学习。

GIoU Loss(Generalized IoU):在 IoU 的基础上加入”最小闭合框”的概念,解决了不重叠时梯度为零的问题。

CIoU Loss(Complete IoU):在 GIoU 基础上再加入中心点距离和宽高比的约束,收敛更快。这是 YOLOv8 和 YOLO-Master 默认使用的回归损失。

# CIoU 的直觉:不仅看框重叠了多少,还看——
# 1. 两个框的中心点差多远?(中心距离惩罚)
# 2. 两个框的宽高比差多大?(形状一致性)
# 3. 最小闭合框有多大?(位置相对关系)
#
# 多维度约束使得优化更稳定,收敛更快

DFL(Distribution Focal Loss)——边界框的概率分布表示

这是 YOLOv8 / YOLO-Master 的一个关键创新。传统方法让模型直接回归一个连续值(”这条边距中心 3.7 像素”),DFL 改为让模型输出一个离散概率分布(”距离为 0 像素的概率 5%,1 像素的概率 8%,…,15 像素的概率 2%”),然后对分布取期望得到最终值。

class DFL(nn.Module):
    """Distribution Focal Loss 模块"""
    def __init__(self, c1=16):           # c1=16 表示 16 个离散 bin
        super().__init__()
        # 一个 1×1 卷积,把 16 个 logits 加权求和为 1 个值
        self.conv = nn.Conv2d(c1, 1, 1, bias=False)
        # 权重固定为 [0, 1, 2, ..., 15],不参与训练
        self.conv.weight.data[:] = torch.arange(c1).float().view(1, c1, 1, 1)
        self.conv.requires_grad_(False)  # 关键:权重冻结

    def forward(self, x):
        # x 形状: (B, 16, H, W) → softmax 归一化 → 加权求和 → (B, 1, H, W)
        return self.conv(x.softmax(1))   # softmax 变成概率分布,conv 做期望计算

为什么这样做更好? 想象你要估计明天的气温。直接说”25.3 度”是一个点估计,如果错了你也不知道错在哪。但如果你说”24-25 度概率 30%,25-26 度概率 45%,26-27 度概率 20%”——这是一个分布估计,它携带了不确定性信息。DFL 用同样的思路建模边界框的位置,让模型可以表达”这条边大概在 3-4 像素之间,但 3.5 的概率最大”。这比直接回归一个点值更稳定、更容易训练。

类比边界:DFL 的 16 个 bin 不是”16 像素”,而是 16 个离散区间。每个区间的实际物理长度取决于特征图的 stride——P3 (stride=8) 上一个 bin 对应原图 8 像素,P5 (stride=32) 上一个 bin 对应 32 像素。


六、Backbone 的内部:特征提取怎么做?

Backbone 不是一个神秘的黑盒子。让我们打开看看里面的关键组件。

卷积层(Conv)

卷积层是 CNN 的基本构建块。一个 3×3 的卷积核在输入特征图上滑动,每个位置做一次”元素乘加”运算,输出一个数值。

输入特征图 (5×5)          卷积核 (3×3)           输出特征图 (3×3)
┌─┬─┬─┬─┬─┐              ┌─┬─┬─┐               ┌─┬─┬─┐
│1│0│1│0│1│              │1│0│1│               │4│3│4│
├─┼─┼─┼─┼─┤              ├─┼─┼─┤               ├─┼─┼─┤
│0│1│0│1│0│     ×        │0│1│0│       →       │2│4│3│
├─┼─┼─┼─┼─┤              ├─┼─┼─┤               ├─┼─┼─┤
│1│0│1│0│1│              │1│0│1│               │2│3│4│
├─┼─┼─┼─┼─┤              └─┴─┴─┘               └─┴─┴─┘
│0│1│0│1│0│
├─┼─┼─┼─┼─┤
│1│0│1│0│1│
└─┴─┴─┴─┴─┘

实际的卷积层通常带有批归一化(BatchNorm)和激活函数(SiLU / ReLU):

class Conv(nn.Module):
    """标准卷积块:Conv + BN + SiLU"""
    def __init__(self, c_in, c_out, k=1, s=1, p=None):
        super().__init__()
        self.conv = nn.Conv2d(c_in, c_out, k, s, autopad(k, p))  # 卷积
        self.bn = nn.BatchNorm2d(c_out)                           # 批归一化
        self.act = nn.SiLU()                                      # 激活函数

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))  # Conv → BN → SiLU

C2f 模块(Cross Stage Partial with 2 convolutions)

C2f 是 YOLOv8 / YOLO-Master 的 Backbone 中最常用的构建块。它的核心思路是:把输入通道分成两半,一半直接传过去(跳跃连接),另一半经过多个 Bottleneck 层逐步变换。最后把所有中间结果 concat 起来。

输入
  │
  ├── Conv 1×1(降维)
  │     │
  │     ├── 直接传递(一半通道)── chunk ──→ ┐
  │     │                                    │
  │     └── Bottleneck 1 ─→ Bottleneck 2 ─→ ┤  concat
  │                  │                       │
  │                  └───────────────────→ ┘
  │
  └── Conv 1×1(融合所有通道)
  │
输出

为什么这样设计? 直接传递的那一半通道保留了输入的原始信息(类似残差连接),Bottleneck 层负责提取更深层的特征。concat 把两种信息融合,让输出既有原始细节又有深层语义。这种设计在参数量和计算量之间取得了很好的平衡。


七、从特征到预测:检测头的工作细节

理解了 Backbone 提取特征、Neck 融合特征之后,让我们详细看看 Head 怎么把特征变成最终的检测结果。

每个格子负责检测什么?

以 P3 特征图(80×80)为例。每个格子对应原图一个 8×8 的区域。如果一个目标的中心落在某个格子对应的区域内,那个格子就”负责”检测这个目标。

原图 640×640                    P3 特征图 80×80
┌──────────────────┐           ┌──────────────┐
│                  │           │   ·  ·  ·    │  每个 · 是一个格子
│     ┌──┐         │           │   ·  ★  ·    │  ★ = 目标中心落在这个格子
│     │🐕│         │    ──→    │   ·  ·  ·    │  这个格子负责检测这只狗
│     └──┘         │           │              │
│                  │           │              │
└──────────────────┘           └──────────────┘

预测值的含义

每个格子输出的预测值包含:

分类部分:80 个分数(对应 COCO 的 80 个类别)
  [person=0.95, bicycle=0.01, car=0.02, ..., toothbrush=0.00]

回归部分:4 × 16 = 64 个值(DFL 的 4 条边 × 16 个 bin)
  [left_bin0, left_bin1, ..., left_bin15,    ← 左边界的分布
   top_bin0, top_bin1, ..., top_bin15,       ← 上边界的分布
   right_bin0, right_bin1, ..., right_bin15, ← 右边界的分布
   bottom_bin0, ..., bottom_bin15]           ← 下边界的分布

经过 DFL 解码后,64 个值变成 4 个距离值 (l, t, r, b)——表示目标的四条边分别距离格子中心有多远。再根据格子在原图中的坐标,就可以还原出绝对的边界框坐标。

后处理:从 8400 个框到最终结果

一次前向传播产生 8400 个预测框,但最终输出通常只有几个到几十个。中间需要两步过滤:

第一步:置信度过滤

# 丢弃置信度低于阈值的预测
confident_mask = scores > conf_threshold   # 通常 conf_threshold = 0.25
boxes = boxes[confident_mask]
scores = scores[confident_mask]
# 8400 个框可能只剩下几百个

第二步:NMS(Non-Maximum Suppression,非极大值抑制)

同一个目标可能被多个相邻的格子检测到,产生多个重叠的框。NMS 的作用是去重——只保留每个目标的最佳检测框。

NMS 的算法流程:
1. 按置信度从高到低排序所有候选框
2. 取出置信度最高的框,加入结果列表
3. 计算这个框和所有剩余框的 IoU
4. 删掉 IoU 超过阈值(通常 0.45-0.7)的框(被认为检测的是同一个目标)
5. 重复 2-4,直到没有候选框

结果:每个目标只保留一个最佳框

NMS 的具体工作原理和问题我们在第 6 章会深入讨论。


八、完整的推理流程图

把所有步骤串起来,一次完整的检测推理是这样的:

输入图像 (H×W×3)
    │
    ▼
[预处理] 缩放到 640×640, 归一化到 [0,1], 转为 tensor
    │
    ▼
[Backbone] CSPDarknet / ResNet / HGNet
    │ 输出多尺度特征 P3(80×80), P4(40×40), P5(20×20)
    ▼
[Neck] FPN + PAN 多尺度融合
    │ 输出融合后的特征 P3', P4', P5'
    ▼
[Head] 解耦头 (Decoupled Head)
    │ 每个位置输出: 80类分数 + 4×16(DFL) = 144 个值
    │ 三个尺度共 8400 个预测
    ▼
[DFL 解码] 64 → 4 个距离值 → 还原为 (x1,y1,x2,y2)
    │
    ▼
[置信度过滤] 丢弃低分预测 (conf < 0.25)
    │
    ▼
[NMS] 去除重复框 (IoU > 0.45 的保留分数最高的)
    │
    ▼
最终输出: N 个检测结果
  每个: (x1, y1, x2, y2, class_id, confidence)

九、训练 vs 推理:两个阶段的区别

初学者容易混淆训练和推理的流程,这里做一个清晰的对比。

训练阶段

输入: 带标注的图像(图 + ground truth 框)
目标: 调整模型参数,让预测越来越接近 ground truth

流程:
  图像 → 前向传播 → 8400 个预测
  正样本分配(TAL)→ 决定哪些预测和哪些 GT 配对
  计算损失 = 分类损失 + 回归损失 + (MoE 损失)
  反向传播 → 更新参数

特有步骤:数据增强、正样本分配、损失计算、梯度回传

推理阶段

输入: 没有标注的图像
目标: 输出检测结果

流程:
  图像 → 前向传播 → 8400 个预测
  置信度过滤 + NMS → 最终结果

特有步骤:后处理(NMS)、无梯度计算

关键区别:训练时有正样本分配(第 5 章会详讲),推理时有 NMS(第 6 章会详讲)。YOLO-Master 在训练阶段还有 MoE 专有的损失计算(负载均衡损失 + Z-Loss),这在第 11 章会详讲。


初学者常见误区

误区一:”mAP 越高模型越好”

→ 正确理解:mAP 只是一个维度。一个 mAP 53.0 但只能跑 114 FPS 的模型(RT-DETR-L)和一个 mAP 46.3 但能跑 450 FPS 的模型(YOLOv10-S),哪个”更好”取决于你的应用场景。自动驾驶可能需要高 FPS,医学影像可能需要高 mAP。一定要看 mAP-FPS 的 trade-off 而不是单个数值。

误区二:”IoU 就是准确率”

→ 正确理解:IoU 衡量的是一个预测框和一个真实框的重叠程度,是一个框级别的指标。mAP 才是衡量模型整体检测能力的指标(综合了 Precision、Recall 和 IoU 阈值)。IoU 是计算 mAP 的基础工具,但本身不等于准确率。

误区三:”特征图就是缩小版的原图”

→ 正确理解:特征图和原图在空间上有对应关系(通过 stride),但特征图的每个”像素”不再代表颜色值,而是一个高维向量(比如 256 维),包含了该位置的语义特征。你可以把特征图理解为”每个位置的描述向量”——浅层描述的是边缘和纹理,深层描述的是物体部件和语义类别。


读完本章你能做什么

读完这一章,你应该能够:

如果以上问题你都能回答,恭喜你已经掌握了目标检测的核心基础概念。接下来,我们看看 YOLO 家族是怎么一步步走到今天的。


导读目录:README.md 上一章:第 2 章 · 阅读路线图与前置知识 下一章:第 4 章 · YOLO 家族演进