【yolov5转换为rknn模型过程中的问题】

标题问题1、

在这里插入图片描述

出现该问题说明你并未连接rk1808或者未给出rk1808的ID;如果你是在rknn模拟rk1808环境下进行测试,则需要将代码中的相应语句进行修改:

ret = rknn.init_runtime() #模拟rk1808环境下进行测试
ret = rknn.init_runtime('rk1808',device_id='1808') #连接rk1808进行测试

问题2、rknn中不支持pytorch+GPU,因此需要转化成CPU模型
在这里插入图片描述
问题3、
在这里插入图片描述
出现以上问题,需检查yolov5的版本,yolov5 5.*版本的输出有三个,而yolov5 6.*版本的输出有4个。
问题4、
在这里插入图片描述

出现该问题的原因是numpy版本太高,改成1.16.3版本或者降低到适合的版本。

pip install --upgrade numpy==1.16.3

问题5、
在这里插入图片描述

出现该问题的原因是onnxruntime与CUDA版本不匹配,进入给出的网址选择合适的onnxruntime版本。

参考:rknn模型

自定义数据训练的rknn模型部署 踩坑记录

自己训练了一个只有2种类别的yolov8模型之后,部署到瑞芯微RKNN。

踩坑一:类别的变化

之前用COCO数据集训练的.pt模型转rknn后,output0的shape为8400 x 176.
而把自定义数据集训练的模型转rknn后,output0的shape为8400 x 98.
为什么会不一样?一度以为模型或代码有问题,拿图片测了下发现模型没问题,能检测出来。

分析一下8400x176,其中8400是proposal的个数,176是box相关的64+80个类别+32的mask coefficient.
现在类别变成了2,自然要变成64+2+32=98.

所以,如果后处理中用到类别数=80,画图中的类别标签用了80个的,这里需要修改一下。

踩坑二:模型转化(.pt 转 .rknn)

用转化后的rknn模型检测,只能检测出一种类别,另一种检测不出来。

于是从后处理出发找原因,
现输入图片中有两种物体,每种一个,理论上应该检测出2个物体。
先看NMS之前检测了多少个物体。

std::vector<int> picked;
nms_sorted_bboxes(proposals, picked); //picked里面保存的是proposals的下标

int count = picked.size();

这里NMS之前 picked.size() = 1, 说明NMS之前就只检测出一个,已经不对了。

回到NMS之前的处理,
这里选择较大概率的类别作为label,保存label, score和box坐标。

int label = -1;
float score = -FLT_MAX;
//找到最大score和对应的label
for (int k = 0; k < num_class; k++) {
    float confidence = deqnt_affine_to_f32(score_ptr[k], zp,
                                           scale);  //反量化,int转float
    if (confidence > score) {
        label = k;
        score = confidence;
    }
}

float box_prob = sigmoid(score);
if (box_prob >= thres) {
    //忽略一段处理过程
    obj.rect.x = x0;
    obj.rect.y = y0;
    obj.rect.width = x1 - x0;
    obj.rect.height = y1 - y0;
    obj.label = label;
    obj.prob = box_prob;
}

于是追踪label = 0和label = 1的检测概率。
用同一图片测试,很神奇的是.pt模型(转rknn之前)测试中,label=0和label=1的目标 检测概率都>0.9,
而转rknn之后label=1的目标 检测概率只有0.5,label=0的目标 检测概率小得出奇。
观察到score作softmax运算前,label=0的目标 反量化的检测概率小于-7.

认为这是反量化出现了问题,不过反量化用的是从rknn模型中读出的量化参数scales和zp。

for (int i = 0; i < io_num.n_output; ++i) {
    output_attrs[i].index = i;
    if(rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)) < 0) {
        LOGE("rknn_query output_attrs[%d] fail!\n", i);
        return;
    }
    // set out_scales/out_zps for post_process
    out_scales.push_back(output_attrs[i].scale);
    out_zps.push_back(output_attrs[i].zp);
}

如果量化参数有问题,那就是转rknn模型的过程有问题。

找到转rknn模型的代码。

   OUT_DIR = "rknn_models"
    RKNN_MODEL_PATH = '{}/{}.rknn'.format(OUT_DIR,exp)
    if NEED_BUILD_MODEL:
        DATASET = './dataset.txt'
        rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rkXXXX")
        # Load model
        print('--> Loading model')
        ret = rknn.load_onnx(MODEL_PATH)
        if ret != 0:
            print('load model failed!')
            exit(ret)
        print('done')

        # Build model
        print('--> Building model')
        ret = rknn.build(do_quantization=True, dataset=DATASET)
        if ret != 0:
            print('build model failed.')
            exit(ret)
        print('done')

        # Export rknn model
        if not os.path.exists(OUT_DIR):
            os.mkdir(OUT_DIR)
        print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH))
        ret = rknn.export_rknn(RKNN_MODEL_PATH)
        if ret != 0:
            print('Export rknn model failed.')
            exit(ret)
        print('done')
    else:
        ret = rknn.load_rknn(RKNN_MODEL_PATH)

    rknn.release()

注意到rknn模型的转换用到了dataset.txt,它指定的是原版COCO训练下的yolov8的图片,而现在的自定义数据集中没有这样的图片。

换成现在自定义数据集中的图片,再次转rknn模型。

然后观察scales和zp这两个量化值,发现跟之前不一样了。
再次检测,成功检测出2个目标,且检测概率>0.9,和.pt模型差不多。

总结:
转rknn模型时用的图片可能需要用现有数据集中的图片,不要用不相关的图片。

【RK3588 第四篇】YOLO V5中pytorch2onnx,pytorch和onnx模型输出不一致,精度降低

在yolo v5训练的模型,转onnx,再转rknn后,测试发现:

rknn模型,量化与非量化,相较于pytorch模型,测试精度都有降低

onnx模型,相较于pytorch模型,测试精度也有降低,且与rknn模型的精度更接近

于是,根据这种测试情况,rknn模型的上游,就是onnx。onnx这里发现不对劲,肯定是这步就出现了问题。于是就查pytorch转onnx阶段,就存在转化的精度降低了。

一、pytorch转onnx:torch.onnx.export

yolo v5 export.py: def export_onnx()中,添加下面代码,检查转储的onnx模型,与pytorch模型的输出结果是否一致。代码如下:

torch.onnx.export(
    model.cpu() if dynamic else model,  # --dynamic only compatible with cpu
    im.cpu() if dynamic else im,
    f,
    verbose=False,
    opset_version=opset,
    export_params=True, # 将训练好的权重保存到模型文件中
    do_constant_folding=True,  # 执行常数折叠进行优化
    input_names=['images'],
    output_names=output_names,
    dynamic_axes={
        "image": {0: "batch_size"},  # variable length axes
        "output": {0: "batch_size"},
    }
)

# Checks
model_onnx = onnx.load(f)  # load onnx model
onnx.checker.check_model(model_onnx)  # check onnx model
    
import onnxruntime
import numpy as np
print('onnxruntime run start', f)
sess = onnxruntime.InferenceSession('best.onnx')
print('sess run start')
output = sess.run(['output0'], {'images': im.detach().numpy()})[0]
print('pytorch model inference start')


pytorch_result = model(im)[0].detach().numpy()
print(' allclose start')
print('output:', output)
print('pytorch_result:', pytorch_result)
assert np.allclose(output, pytorch_result), 'the output is different between pytorch and onnx !!!'

在这里插入图片描述

对其中的输出结果进行了打印,将差异性比较明显的地方进行了标记,如下所示:

也可以直接使用我下面这个版本,在转完onnx后

import os
import platform
import sys
import warnings
from pathlib import Path
import torch

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # add ROOT to PATH
if platform.system() != 'Windows':
    ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

from models.experimental import attempt_load
from models.yolo import ClassificationModel, Detect, DetectionModel, SegmentationModel
from utils.dataloaders import LoadImages
from utils.general import (LOGGER, Profile, check_dataset, check_img_size, check_requirements, check_version,
                           check_yaml, colorstr, file_size, get_default_args, print_args, url2file, yaml_save)
from utils.torch_utils import select_device, smart_inference_mode


import numpy as np
def cosine_distance(arr1, arr2):
    # flatten the arrays to shape (16128, 7)
    arr1_flat = arr1.reshape(-1, 7)
    arr2_flat = arr2.reshape(-1, 7)

    # calculate the cosine distance
    cosine_distance = np.dot(arr1_flat.T, arr2_flat) / (np.linalg.norm(arr1_flat) * np.linalg.norm(arr2_flat))

    return cosine_distance.mean()


def check_onnx(model, im):

    import onnxruntime
    import numpy as np
    print('onnxruntime run start')
    sess = onnxruntime.InferenceSession('best.onnx')
    print('sess run start')
    output = sess.run(['output0'], {'images': im.detach().numpy()})[0]
    print('pytorch model inference start')

    with torch.no_grad():
        pytorch_result = model(im)[0].detach().numpy()
    print(' allclose start')
    print('output:', output, output.shape)
    print('pytorch_result:', pytorch_result, pytorch_result.shape)
    cosine_dis = cosine_distance(output, pytorch_result)
    print('cosine_dis:', cosine_dis)

    # 判断小数点后几位(4),是否相等,不相等就报错
    # np.testing.assert_almost_equal(pytorch_result, output, decimal=4)

    # compare ONNX Runtime and PyTorch results
    np.testing.assert_allclose(pytorch_result, output, rtol=1e-03, atol=1e-05)

    # assert np.allclose(output, pytorch_result), 'the output is different between pytorch and onnx !!!'

import cv2
from utils.augmentations import letterbox
def preprocess(img, device):
    img = cv2.resize(img, (512, 512))

    img = img.transpose((2, 0, 1))[::-1]
    img = np.ascontiguousarray(img)
    img = torch.from_numpy(img).to(device)
    img = img.float()
    img /= 255
    if len(img.shape) == 3:
        img = img[None]
    return img
def main(
        weights=ROOT / 'weights/best.pt',  # weights path
        imgsz=(512, 512),  # image (height, width)
        batch_size=1,  # batch size
        device='cpu',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        inplace=False,  # set YOLOv5 Detect() inplace=True
        dynamic=False,  # ONNX/TF/TensorRT: dynamic axes

):
    # Load PyTorch model
    device = select_device(device)
    model = attempt_load(weights, device=device, inplace=True, fuse=True)  # load FP32 model

    # Checks
    imgsz *= 2 if len(imgsz) == 1 else 1  # expand

    # Input
    gs = int(max(model.stride))  # grid size (max stride)
    imgsz = [check_img_size(x, gs) for x in imgsz]  # verify img_size are gs-multiples
    im = torch.zeros(batch_size, 3, *imgsz).to(device)  # image size(1,3,320,192) BCHW iDetection
    # im = cv2.imread(r'F:\tmp\yolov5_multiDR\data\0000005_20200929_M_063Y16640.jpeg')
    # im = preprocess(im, device)

    print(im.shape)
    # Update model
    model.eval()
    for k, m in model.named_modules():
        if isinstance(m, Detect):
            m.inplace = inplace
            m.dynamic = dynamic
            m.export = True

    warnings.filterwarnings(action='ignore', category=torch.jit.TracerWarning)  # suppress TracerWarning
    check_onnx(model, im)

if __name__ == "__main__":
    main()

在这里插入图片描述
在这里插入图片描述

这更像是一个运行环境收集的一个记录文件。在我对全网进行搜索时候,发现了类似的报错提示,但并没有解决办法。不知道是不是因为这个函数还在内测阶段,并没有很好的适配。

二、新的pytorch转onnx:torch.onnx.dynamo_export
在参考pytorch官方,关于torch.onnx.export的模型转换,相关文档中:(OPTIONAL) EXPORTING A MODEL FROM PYTORCH TO ONNX AND RUNNING IT USING ONNX RUNTIME
在这里插入图片描述
上述案例,是pytorch官方给出评测pytorch和onnx转出模型,在相同输入的情况下,输出结果一致性对比的评测代码。对比这里:

testing.assert_allclose(actual, desired, rtol=1e-07, atol=0, equal_nan=True, err_msg='', verbose=True)

其中:

rtol:相对tolerance(容忍度,公差,容许偏差)
atol:绝对tolerance
要求 actual 的 desired 值的差别不超过 atol + rtol * abs(desired),否则弹出错误提示。
在这里插入图片描述
于是,就转到torch.onnx.dynamo_export链接,点击这里直达:[EXPORT A PYTORCH MODEL TO ONNX]
同样的流程,导出模型,然后进行一致性评价,发现官方竟然没有采用允许误差的评测,而是下面这样:

在这里插入图片描述

致,这是一个大好消息。至此,开始验证

2.1、验证结果

与此同时,发现yolo v5更新到了v7.0.0的版本,于是就想着把yolo 进行升级,同时将pytorch版本也更新到最新的2.1.0,这样就可以采用torch.onnx.dynamo_export 进行转onnx模型的操作尝试了。

当一起就绪后,采用下面的代码转出onnx模型的时候,却出现了错误提示。

export_output = torch.onnx.dynamo_export(model.cpu() if dynamic else model,
                                             im.cpu() if dynamic else im)
export_output.save("my_image_classifier.onnx")

2.2、转出失败

在这里插入图片描述
给出失败的的提示:torch.onnx.OnnxExporterError,转出onnx模型失败,产生了一个SARIF的文件。然后介绍了什么是SARIF文件,可以通过VS Code SARIF,也可以 SARIF web查看。最后说吧这个错误,报告给pytorch的GitHub的issue地方。

产生了一个名为:report_dynamo_export.sarif是文件,打开文件,记录的信息如下:

{
 "runs":[
  {
   "tool":{
    "driver":{
     "name":"torch.onnx.dynamo_export",
     "contents":[
      "localizedData",
      "nonLocalizedData"
     ],
     "language":"en-US",
     "rules":[],
     "version":"2.1.0+cu118"
    }
   },
   "language":"en-US",
   "newlineSequences":[
    "\r\n",
    "\n"
   ],
   "results":[]
  }
 ],
 "version":"2.1.0",
 "schemaUri":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/schemas/sarif-schema-2.1.0.json"
}

三、总结

原本想着验证最终转rknn的模型,与原始pytorch模型是否一致的问题,最后发现在转onnx阶段,这种差异性就已经存在了。并且发现rknn的测试结果,与onnx模型的测试结果更加的贴近。无论是量化后的rknn,还是未量化的,均存在这个问题。

同时发现,量化后的rknn模型,在config阶段改变量化的方式,确实会提升模型的性能,且几乎接近于未量化的模型版本。

## 文章做笔记用,侵权联系删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值