1)如果使用原版yolov5s.pt这种模型框架最终跑出来,可以在1126上运行640,不用零拷贝。x640会花150ms~200ms。产品着陆肯定是不可接受的,所以模型需要轻量优化。
2)rknn-toolkit疑似不支持pow层,没有仔细看,一个一个调查,从结果来看有pow层的输出为零。
3)rknpu官方例程切断了最终锚定,并使用软件编写锚定。可能是因为上面2),锚定被切断了。但是为了减少它cpu占用和运行时间不稳定的可能性,我需要在模型中定的可能性。
4)由于需要量化,锚定部分的输出数量级为100,但信心范围为0~1,精度差太大。如果不处理最终输出信心,则为0或1。因此,有必要将锚定集成在那里。
对于坑1,要解决这个问题,要么更换其他较轻的模型,如NanoDet,实测416*416NanoDet能在1126上跑到30~40ms;要么简化yolov5s项目等模型github.com/EASY-EAI/yolov5.更换卷积核,删除切片等操作,简单使用上述项目进行训练,然后导出。整个过程的最终结果是在1126上跑416*416也能跑到35ms左右。但是,EASY-EAI锚定项目也被切断了。我不能接受那些不想写锚定代码的人,所以我想export修改代码。
先使用EASY-EAI的yolov5训练特化的模型,然后他的导出给了三个选项(可同时使用)
--rknn_mode(主要目的是改变很多东西,提高速度。
--ignore_output_permute(在切割锚定的基础上切割最后一层,以适应rknpu例程,但我不需要)
--add_image_preprocess_layer(多加一个输入Transpose层,据说可以提升set实测输入模式的速度似乎确实提高了一点,不知道是不是误差,但他很奇怪,下面详细说明)
因为我需要锚,但是不知道哪行代码控制锚定,从detect.py分析pt文件因该自带锚定,但EASY如果在导出文件中没有找到切割或添加的部分,则只能使用rknn_mode添加内容yolov5本体的export里面。具体操作:
1)把EASY项目的export.py里if opt.rknn_mode != True:的else:内容直接复制到本体export.py的run函数中。运行看看有什么不好,系统找不到什么。
2)然后补充相应的引用,如:import models;补充相应的文件,如:common_rk_plug_in.py放到models内;补充相应的函数,如:common.py里添上SPP;反正运行时缺少什么补什么。
然后说下add_image_preprocess_layer,EASY-EAI说能提升但是,零拷贝通常用于速度,即map系的操作吧?如果打开这个选项,他的输入将从1开始x3x416x416(NCHW)变成1x416x416x3(NHWC),然后在input后面加一层[0, 3, 1, 2]的Transpose。后处理没有变化,但是rknn-toolkit转模时报错,rknn-toolkit可能只接受NCHW即使格式输入模型,config开force_builtin_perm=True也没用。我不明白,也不知道是否有遗漏。
无论如何,如果您想要此操作,您需要更改以下代码onnx:(有脱裤子放屁的感觉)
import onnx import numpy as np onnx_model = onnx.load("best.onnx") d = onnx_model.graph.input[0].type.tensor_type.shape.dim d[0].dim_value = 1 d[1].dim_value = 3 d[2].dim_value = 416 d[3].dim_value = 416 graph = onnx_model.graph node = graph.node id = 0 for i in range(len(node)): if node[i].op_type == 'Transpose': if node[i].input[0]=='images': id = i old_scale_node = node[id] new_perm = onnx.helper.make_attribute("perm", [0,1,2,3]) del node[id].attribute[0] node[id].attribute.extend([new_perm]) onnx.checker.check_model(onnx_model) onnx_model = onnx.shape_inference.infer_shapes(onnx_model) onnx.checker.check_model(onnx_model) onnx.save(onnx_model, 'best .onnx')
上面提到,转rknn后,过pow从netron你可以知道这一点pow参数为2,我们可以pow层改成mul层,让输入层乘以自己,主代码示例如下:
for i in range(len(node)): if node[i].op_type == 'Pow': if node[i].input[1]=='336': id = i old_scale_node = node[id] graph.node.remove(old_scale_node) new_scale_node = onnx.helper.make_node( op_type='Mul', inputs=335,335 outputs=['337'], ) graph.node.insert(id, new_scale_node) onnx.checker.check_model(onnx_model)
上面还提到锚定需要集成,否则信心精度会丢失。操作是直接锚定的最后一个mul层除416外,主要代码示例如下:(338.npy它是锚定层的参数之一,直接从netron下载即可。338只是名字/代码,不同规格的模型标号可能不同,取决于自己的情况)
for i in range(len(node)): if node[i].op_type == 'Mul': if node[i].input[1]=='327': id = i old_scale_node = node[id] graph.node.remove(old_scale_node) mul_tensor = onnx.helper.make_tensor('327_',onnx.TensorProto.FLOAT,[1][8/416] graph.initializer.append(mul_tensor) new_scale_node = onnx.helper.make_node( op_type='Mul', inputs=['326','327_'], outputs=['328'], ) graph.node.insert(id, new_scale_node) onnx.checker.check_model(onnx_model) for i in range(len(node)): if node[i].op_type == 'Mul': if node[i].input[1]=='338': id = i old_scale_node = node[id] graph.node.remove(old_scale_node) data=np.load('338.npy') data=data/416 mul_tensor = onnx.helper.make_tensor('338_',onnx.TensorProto.FLOAT,data.shape,data) graph.initializer.append(mul_tensor) new_scale_node = onnx.helper.make_node( op_type='Mul', inputs=['337','338_'], outputs=['339'], ) graph.node.insert(id, new_scale_node) onnx.checker.check_model(onnx_model)
上述两段代码所需的头尾:
import onnx import numpy as np import torch onnx_model = onnx.load("best.onnx") graph = onnx_model.graph node = graph.node id = 0 ''' 重复上述两段代码 ''' onnx_model = onnx.shape_inference.infer_shapes(onnx_model) onnx.checker.check_model(onnx_model) onnx.save(onnx_model, 'out.onnx')