嵌入式AI部署---基于RK3588运行yolov5(2)
前言:
在上篇博客中撰写了关于yolov5在RK3588上运行的流程, 在学习的过程中发现后处理很难,我一直不理解输出结果是如何存储的,它比较抽象,对于我来讲很难理解,经过一段时间的学习,有了一个比较好的理解思路。
1. 后处理的传参
在rknn_outputs_get 中获得了NPU推理后的处理结果,这个结果有三个输出结果outputs[i].buf,作为后处理的输入部分,对应模型三个尺度的输出张量。
rknn_outputs_get(ctx, outputs_num, outputs, NULL);
post_process((int8_t *)outputs[0].buf, (int8_t *)outputs[1].buf, (int8_t *)outputs[2].buf,....)
这里的outputs是三个检测头的输出特征图
- YOLOv5采用多尺度检测,三个输出头对应不同下采样步长(这里输出有三个不同类型是因为有不同的检测目标)
| 输出头 | 步长 | 网格大小 | 锚框尺寸 | 检测目标 |
|---|---|---|---|---|
| output0 | 8 | 80×80 | [10,13], [16,30], [33,23] | 小目标 |
| output1 | 16 | 40×40 | [30,61], [62,45], [59,119] | 中目标 |
| output2 | 32 | 20×20 | [116,90], [156,198], [373,326] | 大目标 |
下面我们对于这个输出的结果进行理解,有助于理解后续后处理code的编写
2. 数据格式
- 数据类型:int8_t(量化后的 8 位整数)
- 内存布局:NCHW 或 NHWC(取决于模型导出时的配置,此处代码按 NCHW 处理)
- 每个输出是一个 4 维张量,形状为
[batch(图片数量), channels(通道), grid_h(特征图高度), grid_w(特征图宽度)](batch=1),例如:output0:[1, 255, 80, 80](stride=8)output1:[1, 255, 40, 40](stride=16)output2:[1, 255, 20, 20](stride=32)
其中 255 = 3 × (80 + 5):
- 3:每个网格预测 3 个锚框(anchor0/1/2)
- 80:COCO 数据集的 80 个类别概率
- 5:
x, y, w, h, objectness(框坐标和置信度)
3. 数据内容
每个网格的每个锚框对应一组长度为 85 的数据,排列顺序通常是:
[x, y, w, h, obj_conf(是否存在目标), cls0_prob(第1个类), cls1_prob(第2个类), ..., cls79_prob(第80个类)]
在这里你还是不知道如何存储的话,听我下面的讲解
4. 数据的立体讲解-----最重要理解方式
想象一个 三维立方体,三个维度分别是:
- 通道 C:0 ~ 254(共 255 层)
- 高度 H:0 ~ 79(共 80 行)
- 宽度 W:0 ~ 79(共 80 列)
也就是说,我们对立方体横向砍一刀,得到一个横截面,它的宽和高是H和W,立方体的高是对应着通道C, 我们可以将高度和C一一对应
-
它一共有255层,对应255个通道数
-
**255个通道数如何排列?**如下图所示
[ x, y, w, h, obj_conf, cls0_prob, cls1_prob, ..., cls79_prob ]上面总计85个
这个立方体有85*3 = 255, 为什么有要× 3 ?有3个锚框- 那么锚框0,对应通道0-84
- 锚框1,对应通道85-169
- 锚框2,对应通道170-254
在这里插入图片描述

5. 如何理解内存中的存储?
以output0:[1, 255, 80, 80](stride=8)这个为例子,
特征图的宽和高为80×80, 则x、y、w、h、c 将会分别占用6400的存储单元(x占6400个,y占6400个,w占6400个…)
-
从立方体的从上往下看,先对应的是x,后对应的是y,w等,那么存储的时候,先在内存中存放6400个x, 后存6400个y, 再就是6400个w,依次存完。
-
由于有三个锚框先存完「第 1 个锚框」的全部数据(X/Y/W/H/C/80 个类),再接着存「第 2 个锚框」的全部数据,最后存「第 3 个锚框」的全部数据。
-
单个锚框存储的总单元:
- 基础 5 参数:
X(6400) + Y(6400) + W(6400) + H(6400) + C(6400) = 5×6400 - 80 个类别:
80×6400 - 单个锚框总长度:
(5+80)×6400 = 85×6400 =544000
- 基础 5 参数:
-
3个锚框:(5 + 80 )* 3 * 6400 = 1,632,000
内存地址低 → 高
┌──────────────────────┐
│ 通道 0 (x_a0) │ 6400 个 int8
│ 通道 1 (y_a0) │ 6400 个 int8
│ 通道 2 (w_a0) │ 6400 个 int8
│ 通道 3 (h_a0) │ 6400 个 int8
│ 通道 4 (obj_a0) │ 6400 个 int8
│ 通道 5 (cls0_a0) │ 6400 个 int8
│ ... │
│ 通道 84 (cls79_a0) │ 6400 个 int8
├──────────────────────┤
│ 通道 85 (x_a1) │ 6400 个 int8
│ ... │
│ 通道 169 (cls79_a1) │ 6400 个 int8
├──────────────────────┤
│ 通道 170 (x_a2) │ 6400 个 int8
│ ... │
│ 通道 254 (cls79_a2) │ 6400 个 int8
└──────────────────────┘
总大小 = 255 × 6400 = 1,632,000 个 int8
总结
相对来说比较清晰了吧?后面再告诉如何写后处理部分的code,如何找到对应的数据
更多推荐

所有评论(0)