Skip to content

一、先建立一个最重要的模型 ​

无论 YOLOv5、YOLOv8、YOLO11,本质上你先把它理解成:

                        YOLO模型
                           │
图片 ──→ 预处理 ──→ [ 一堆神经网络 ] ──→ 输出Tensor
                                             │
                                             ↓
                                          后处理
                                             │
                                             ↓
                                    框、类别、置信度

所以你现在只需要研究两头:

             你关心                      你暂时不关心
               ↓                            ↓
        ┌──────────────┐
输入 → │   YOLO内部    │ → 输出 → 后处理
        └──────────────┘
  ↑                            ↑
研究这里                    研究这里

以后别人扔给你一个模型,你先问 6 个问题:

  1. 输入几个 Tensor?
  2. 输入 shape 是什么?
  3. 输入数据怎么预处理?
  4. 输出几个 Tensor?
  5. 每个输出 shape 是什么?
  6. 输出里的每一个数字代表什么?

把这 6 个搞明白,后处理基本就出来了。

什么是Tensor

二、先从输入开始:[1, 3, 640, 640] 是什么? ​

你以后经常看到:

input:
shape = [1, 3, 640, 640]
dtype = float32

它通常表示:

[N, C, H, W]

N = Batch
C = Channel
H = Height
W = Width

也就是:

[1, 3, 640, 640]
 │  │   │    │
 │  │   │    └─ 宽 640
 │  │   └────── 高 640
 │  └────────── 3通道 RGB
 └───────────── 一次输入1张

所以你以后看到:

1 × 3 × 736 × 1280

马上应该能反应过来:

batch=1,RGB 三通道,输入尺寸 1280×736。


三、但“输入 shape”还远远不够 ​

这点非常重要。

假设模型输入是:

[1, 3, 640, 640]

你不能直接:

model(image);

因为模型还有一个输入契约。

至少要知道:

原始图片
1920×1080 BGR uint8
        │
        ↓
resize / letterbox
        │
        ↓
640×640
        │
        ↓
BGR → RGB
        │
        ↓
uint8 → float32
        │
        ↓
/ 255.0
        │
        ↓
HWC → CHW
        │
        ↓
[1,3,640,640]

比如 OpenCV 给你的通常是:

HWC
[640, 640, 3]

BGR

uint8
0~255

YOLO 的 PyTorch/ONNX 输入可能要求:

CHW
[3,640,640]

RGB

float32
0~1

这就是为什么:

shape 对了,不代表模型输入就对了。

你以后检查一个 YOLO 输入,至少检查:

① Width / Height
② NCHW 还是 NHWC
③ RGB 还是 BGR
④ uint8 / float16 / float32 / int8
⑤ 是否 /255
⑥ resize 还是 letterbox

尤其你以后搞 RKNN:

ONNX:
NCHW float32

          ↓ RKNN转换

RKNN:
可能使用 NHWC uint8

所以永远以实际模型 Tensor 属性为准,不要背死。


四、接下来才是最关键的:YOLO 输出怎么看? ​

先从最容易理解的 YOLOv5 HBB 开始。

假设:

输入:
[1,3,640,640]

输出:
[1,25200,85]

第一次看到:

25200 × 85

很多人都会懵。

其实拆开非常简单。


五、先看最后面的 85 ​

假设这个 YOLO 是 COCO 80 类:

85 = 4 + 1 + 80

也就是:

┌────────────┬──────────────┬─────────────┐
│ 4个框参数   │ 1个obj       │ 80个类别     │
├────────────┼──────────────┼─────────────┤
│ x y w h    │ objectness   │ cls0...cls79 │
└────────────┴──────────────┴─────────────┘

所以其中某一个目标可能是:

[
    320,
    240,
    100,
    60,

    0.9,

    0.01,
    0.02,
    0.80,
    0.03,
    ...
]

我们拆一下。

框:

cx = 320
cy = 240
w  = 100
h  = 60

表示:

              w=100
        ┌───────────────┐
        │               │
        │       ●       │
        │    (320,240)  │ h=60
        │               │
        └───────────────┘

注意它通常是:

cx cy w h

不是:

x1 y1 x2 y2

六、obj 是什么? ​

例如:

objectness = 0.9

粗略理解:

YOLO 认为“这里确实有个东西”的可信程度。

然后:

class0 = 0.01
class1 = 0.02
class2 = 0.80
...

假设:

class2 = car

那么:

类别 = car

YOLOv5 常见最终 confidence:

confidence
=
objectness × class_probability

=
0.9 × 0.8

=
0.72

所以最终变成:

car

confidence = 0.72

cx = 320
cy = 240
w = 100
h = 60

这时候其实神经网络的任务已经基本结束了。

剩下的,就是你的:

后处理。 ​


七、后处理第一步:置信度过滤 ​

比如你设置:

conf_threshold = 0.25;

那么:

25200 个候选框

        ↓ confidence > 0.25

可能剩 100 个

大量垃圾框直接删掉。


八、第二步:把 xywh 转成 xyxy ​

模型给你:

cx = 320
cy = 240
w  = 100
h  = 60

你画框通常需要:

x1 y1 ┌─────────────┐
      │             │
      │             │
      └─────────────┘ x2 y2

所以:

x1 = cx - w/2
y1 = cy - h/2

x2 = cx + w/2
y2 = cy + h/2

代进去:

x1 = 320 - 50 = 270
y1 = 240 - 30 = 210

x2 = 320 + 50 = 370
y2 = 240 + 30 = 270

于是:

[270,210,370,270]

九、第三步:NMS ​

这是你后处理里一定会遇到的。

比如 YOLO 检测同一辆车:

┌───────────┐
│  car 0.91 │
│    ┌────────────┐
│    │ car 0.85   │
│    │            │
└────│────────────┘
     └────────────┘

它可能吐出来很多高度重叠的框:

框A car 0.91
框B car 0.85
框C car 0.72

实际上都是同一辆车。

NMS 就负责:

保留 A

删除 B
删除 C

所以:

模型输出
   ↓
confidence filter
   ↓
NMS
   ↓
真正的检测结果

十、最后还有一个你以后一定会踩坑的:坐标映射 ​

你的摄像头可能是:

1920 × 1080

但模型是:

640 × 640

模型输出:

x = 320
y = 200

这是:

640×640 模型坐标系里的位置。

而不是原始:

1920×1080

所以最后还要:

模型坐标
    ↓
去掉 letterbox padding
    ↓
除以 resize ratio
    ↓
原图坐标

这一步叫:

scale coordinates

或者:

逆 letterbox

所以完整后处理其实已经出来了:

             YOLO输出
                │
                ↓
         解析每个 candidate
                │
                ↓
           计算 confidence
                │
                ↓
           confidence过滤
                │
                ↓
          xywh → xyxy
                │
                ↓
               NMS
                │
                ↓
        映射回原始图像尺寸
                │
                ↓
       class / score / bbox

这就是你当前阶段所谓的:

YOLO 后处理。


十一、那 25200 又是什么? ​

这个你也应该理解,但不需要研究神经网络内部。

YOLOv5 640×640 一般有三个检测尺度:

80×80
40×40
20×20

因为 YOLOv5 每个位置有 3 个 Anchor:

80 × 80 × 3 = 19200

40 × 40 × 3 = 4800

20 × 20 × 3 = 1200

加起来:

19200 + 4800 + 1200

= 25200

所以:

[1,25200,85]

你现在完全可以理解成:

这一张图片

模型认为一共有

25200 个“候选位置”

每个候选位置提供

85 个数字

然后你的后处理把 25200 个候选结果筛到:

5 个
10 个
20 个

真正的目标。


十二、YOLOv8 / YOLO11 又会不一样 ​

这就是为什么我不希望你单纯背:

YOLO输出 = [1,25200,85]

那样以后换 YOLO11 你又懵了。

比如一个典型 YOLOv8 / YOLO11 Detect ONNX 模型,你可能看到:

[1,84,8400]

为什么是 84?

COCO 80 类:

84 = 4 + 80

你发现:

YOLOv5

4 + 1 + 80
    ↑
   obj

YOLOv8/11

4 + 80

典型 Ultralytics YOLOv8/11 检测输出:

没有独立 obj

所以:

confidence = max(class_score)

而不是 YOLOv5 的:

obj × class_score

十三、为什么是 8400? ​

因为 YOLOv8/11 是 anchor-free:

80 × 80 = 6400

40 × 40 = 1600

20 × 20 = 400

于是:

6400 + 1600 + 400

= 8400

所以你以后看到:

[1,84,8400]

脑子里应该马上尝试:

84 = 4 + nc?

8400 = 80×80 + 40×40 + 20×20?

如果:

nc = 3

那么你自己的模型可能看到:

[1,7,8400]

7 = 4 + 3

一下就读懂了。


十四、这里教你一个非常重要的“拆 shape”思维 ​

以后看到任何 YOLO 输出:

[1, X, Y]

不要慌。

开始猜:

有没有:

4 + nc

5 + nc

4 + 1 + nc

4 + nc + angle

5 + nc + angle

然后另外一个数字:

是不是:

8400

25200

80×80

40×40

20×20

你就会逐渐发现:

YOLO 输出看起来很玄学,其实很多时候就是小学拆数字。


十五、再说你真正会碰到的 OBB ​

你现在做的很多是:

YOLO OBB

HBB 普通框:

cx cy w h

只能这样:

┌─────────────┐
│             │
│    汽车      │
│             │
└─────────────┘

OBB:

cx cy w h θ

多一个:

θ = angle

于是可以:

        ╱────────╲
       ╱          ╲
      ╲            ╱
       ╲──────────╱

也就是说,本质上的变化只是:

HBB

[x,y,w,h]

变成:

OBB

[x,y,w,h,angle]

当然,不同 OBB 项目可能输出:

cx cy w h angle

也可能直接:

x1 y1
x2 y2
x3 y3
x4 y4

所以:

OBB 绝对不能看到 shape 就凭经验硬猜,一定要确认模型实现。

尤其你的 YOLOv5 OBB 并不是 Ultralytics 当前官方 OBB 那一套,输出格式很可能和 YOLO11-OBB 不完全一样。


十六、OBB 后处理有什么不同? ​

大流程其实没有变:

模型输出
   ↓
解析 bbox
   ↓
解析 angle
   ↓
解析 class
   ↓
confidence过滤
   ↓
Rotated NMS
   ↓
坐标恢复
   ↓
OBB

区别主要在两个地方:

普通 YOLO:

xywh → xyxy

普通 IoU

普通 NMS

OBB:

xywhr

旋转框 IoU / Polygon IoU

Rotated NMS

所以你不需要把它当成完全不同的一套东西。


十七、还有一个大坑:模型输出可能分两种 ​

这个和你最近考虑的:

“是否去检测头”

直接相关。

第一种:已经 Decode 的输出 ​

例如:

[1,25200,85]

里面已经基本是:

x
y
w
h
obj
classes...

那么你的工作非常舒服:

过滤
↓
NMS
↓
坐标转换

第二种:Raw Head 输出 ​

你可能看到三个输出:

output0
[1,3,80,80,85]

output1
[1,3,40,40,85]

output2
[1,3,20,20,85]

这时候里面的:

tx
ty
tw
th

还不是最终的 x y w h。

你还必须自己 Decode。

例如 YOLOv5 大致有:

x = (sigmoid(tx) * 2 - 0.5 + grid_x) * stride

y = (sigmoid(ty) * 2 - 0.5 + grid_y) * stride

w = (sigmoid(tw) * 2)^2 * anchor_w

h = (sigmoid(th) * 2)^2 * anchor_h

这时候:

Raw Tensor
    ↓
Sigmoid
    ↓
Grid
    ↓
Stride
    ↓
Anchor
    ↓
真正的 xywh
    ↓
confidence
    ↓
NMS

这才是真正意义上比较完整的:

YOLO 后处理。


十八、YOLOv8 / YOLO11 Raw Head 又不一样 ​

这也是为什么你的“通用 YOLO 转换工具”不能认为:

v5后处理代码

复制一下

↓

v8

↓

v11

不行。

YOLOv8/11 bbox regression 一般涉及:

Anchor Point

Stride

DFL

而不是 YOLOv5 那套:

Anchor Width
Anchor Height

大概可以先理解成:

YOLOv5

raw
 ↓
grid + stride + anchor
 ↓
bbox

而:

YOLOv8/11

raw
 ↓
DFL decode
 ↓
anchor point + stride
 ↓
bbox

DFL 你现在完全不用学。

等你已经能独立看懂普通输出以后,我们再学。


十九、所以我建议你的学习顺序非常明确 ​

不要现在一股脑:

YOLOv5
YOLOv8
YOLO11
RKNN
INT8
OBB
DFL
NMS
Detect Head

全部一起学。

很容易再次乱掉。

你按这个顺序:

第一阶段
YOLOv5 HBB
已Decode输出

[1,25200,5+nc]

目标:
看懂 xywh + obj + class
        ↓

第二阶段
亲手写最简单的 Python 后处理

conf filter
xywh→xyxy
NMS
scale box
        ↓

第三阶段
YOLOv5 Raw Head

80×80
40×40
20×20

学习:
grid
stride
anchor
sigmoid
decode
        ↓

第四阶段
YOLOv8 / YOLO11

理解:
anchor-free
8400
无单独objectness
DFL
        ↓

第五阶段
OBB

理解:
xywhr
angle
rotated NMS
        ↓

第六阶段
RKNN

把前面的东西移植成:

RKNN Tensor
    ↓
C/C++ postprocess

这个路径会顺很多。


二十、而且你现在不用“学习所有 YOLO” ​

你真正应该形成的是这样一种习惯:

比如别人丢给你:

model.onnx

你第一件事情不是:

“这是 YOLO11,我得去网上搜 YOLO11 后处理代码。”

而应该是:

model.onnx
   ↓
输入是什么?
   ↓
输出有几个?
   ↓
shape是什么?
   ↓
每个维度是什么?
   ↓
输出已经Decode了吗?
   ↓
有没有obj?
   ↓
bbox是什么表示形式?
   ↓
score有没有做sigmoid?
   ↓
需不需要DFL?
   ↓
需不需要NMS?

到这个程度,你就真的开始有模型部署能力了。


二十一、你现在可以马上做一个非常简单的练习 ​

比如以后看到:

input:
[1,3,640,640]

output:
[1,25200,8]

你应该尝试分析:

假设:

nc = 3

那么:

8

= 4 + 1 + 3

所以极有可能:

0 cx
1 cy
2 w
3 h
4 objectness
5 class0
6 class1
7 class2

再比如:

output:
[1,7,8400]

模型也是 3 类。

那么:

7 = 4 + 3

结合:

8400
=
80×80 + 40×40 + 20×20

你就应该高度怀疑:

YOLOv8 / YOLO11 风格

4 bbox + 3 classes

无独立objectness

你已经开始会“看模型”了。


二十二、以后你可以把 YOLO 部署看成这三层 ​

我建议你脑子里长期保留这个图:

┌─────────────────────────────┐
│       第一层:模型输入        │
│                             │
│ shape                       │
│ dtype                       │
│ RGB/BGR                     │
│ NCHW/NHWC                   │
│ resize/letterbox            │
│ normalize                   │
└──────────────┬──────────────┘
               ↓
┌─────────────────────────────┐
│       第二层:模型输出        │
│                             │
│ output数量                   │
│ shape                       │
│ bbox                        │
│ objectness                  │
│ class                       │
│ angle                       │
│ raw / decoded               │
└──────────────┬──────────────┘
               ↓
┌─────────────────────────────┐
│       第三层:后处理          │
│                             │
│ decode                      │
│ confidence                  │
│ threshold                   │
│ NMS / Rotated NMS           │
│ coordinate scale            │
│ final result                │
└─────────────────────────────┘

你现在只学这三层。

至于:

Backbone
Neck
C3
C2f
SPPF
Bottleneck
Conv
BatchNorm

现在可以全部放一边。


我建议我们下一步直接实战 ​

而且先不要拿 YOLO11 OBB 开刀,那个变量太多。

我们就拿一个最普通的:

YOLOv5
640×640
HBB

我下一步可以带你真正从:

一张 1920×1080 图片
        ↓
letterbox
        ↓
[1,3,640,640]
        ↓
YOLO
        ↓
[1,25200,85]
        ↓
取出其中一个 candidate
        ↓
算 confidence
        ↓
xywh → xyxy
        ↓
NMS
        ↓
恢复成 1920×1080 坐标

手算一遍。

这遍做完以后,你对“YOLO 输入输出 + 后处理”就不会再只是一个模糊概念了。

最近更新