RK3588采用多线程运行yoloV5(三)
--------------yolov5的后处理阶段
在第二节中,我们已经知道了关于使用rknn进行推理的步骤,在最后我们通过下面这段代码得到了经过推理后的结果:
rknn_outputs_get(ctx, outputs_num, outputs, NULL);
这是RK内置的API,目的就是将推理的结果保存在outputs中(注:这里outputs使我们自己定义的一个存储结构体的数组),具体定义过程如下:
//rknn_output结构体的定义
typedef struct _rknn_output {
uint8_t want_float; /* want transfer output data to float */
uint8_t is_prealloc; /* whether buf is pre-allocated.
if TRUE, the following variables need to be set.
if FALSE, the following variables do not need to be set. */
uint32_t index; /* the output index. */
void* buf; /* the output buf for index.
when is_prealloc = FALSE and rknn_outputs_release called,
this buf pointer will be free and don't use it anymore. */
uint32_t size; /* the size of output buf. */
} rknn_output;
//我们自己定义一个outputs数组来存储输出的变量
rknn_output outputs[io_num.n_output];
在这个结构体中,比较重要的有两个变量,分别是:
uint8_t want_float;
void* buf;
其中第一个是一个标志位,意思是是否需要将输出数据转换为浮点数(float),如果在计算机中运行yolo模型,其实是可以直接用浮点数进行计算的,但是在这里我们使用RK3588进行计算时,就需要进行量化操作,量化后的类型是int8_t类型,相较于float计算量会减少4倍,所以在推理中我们都是用量化后的进行计算。大家对量化有兴趣的同学可以自己去看,这块我也理解的不深。
那么第二个就是我们今天后处理的核心部分,buf中存储的就是我们输出的结果,那么我们如何来讲这个buf中的数据处理成我们想要的东西呢?在这之前,需要给大家讲一下这个buf是如何存储数据的?
对于一张推理的图片,yolov5会将其分成固定大小的方格,我以20×20的方格进行说明:
由于使用coco数据集,所以有80个种类,在yolov5的输出中会额外加五个参数,分别是x、y、w、h、c(每个格子的中心横坐标、纵坐标、宽、高、置信度(就是这个格子里有物体的概率,通常如果低于某个值,就说明yolo认为没有这80类的东西)),那么这个时候我们来考虑buf的存储,它会先将所有格子的x按顺序存进去、再将所有格子的y存进去,然后w、h、c也是同样,如果以20×20为例,则x、y、w、h、c将会分别占用400的存储单元,而后会按照80个类别存储,即格子0有类别0的概率,格子1有类别0的概率、格子2有类别0的概率…格子399有类别0的概率,格子0有类别1的概率,格子1有类别1的概率,格子2有类别1的概率…格子399有类别1的概率,然后其他类别依次这样存储,可以看下面数组的示意图:
由于每个框里检测到的物体并不都是正方形,所有yolov5有三个锚框,这个概念大家可能需要自己去了解一下,就是上面的这个排列是第一个锚框的数据,在这些后面还有第二个锚框的相同存储方式。在这里不过多的讲了。
下面我们来解析buf:
1、首先进过推理,已经将每个框中的置信度存储在上面数组中的c中了,然后我们此时来检查遍历每个框的置信度,如果置信度大于我们设定的阈值(如0.5),就把这个框进行相关的处理,并且把框的坐标存储在某个数据结构中,这个置信度只是说明yolo认为有80类里面的东西,我们需要再去找到最大的类别和该类别对应的概率,分别存储进去
2、进行非极大值抑制:由于有可能存在重复画框的问题,那么一个物体上就会有很多框,我们只需要一个框,那么我们就留下置信度最大的那个框即可
具体的代码就不再这里进行展示、主要还是思路的理解。
后面就是根据框的坐标在图片上画出方框即可
总结:整个后处理部分理解起来还是比较麻烦的,主要原因就是这个一维数组存储的数据太多,需要多去自己学习才可以!!!