yolov8在rknn(rv1109/1126)模型转换、量化移植过程

news2024/9/21 8:03:12

续:rv1109/1126 rknn 模型量化过程_CodingInCV的博客-CSDN博客

Yolov8简介

yolov8是比较新的目标检测模型,根据论文和开源项目的报告,相对使用比较广泛的yolov5提升还比较明显。
image.png
yolov8与yolov5相比,结构上的主要区别是将C3结构换成了C2f,检测头换成了anchor free检测头(详细见:YOLOv8 深度详解!一文看懂,快速上手 - 知乎 (zhihu.com))。
image.png
image.png

模型转换和量化

模型导出的修改

当我们直接将模型导出为onnx,然后进行量化,我们会发现整个过程是可以进行了,也就是从算子角度来说,rknn是支持yolov8的。但是如果拿量化好的模型进行仿真推理,会发现无法检测到目标(根据我的尝试,若有不同结论,欢迎分享)。
根据分析pytorch的yolov8代码以及观察yolov8的模型结构,会发现,有一部分后处理操作被导出到了onnx模型中:
image.png
这一部分在训练中是忽略的,只会在推理中存在:
image.png

但若我们导出的onnx中包含了这一部分,会参与量化的过程,可能对结果造成了比较大的影响(也是猜测,博主也尝试了混合量化,仍旧没有解决)。既然这部分并没有包含参数信息,我们可以不导出这一部分,而将这一部分作为后处理的一部分自行实现。修改ultralytics/nn/modules/head.py中的条件:
image.png
这样我们导出的模型就不包含后处理这部分了,模型输出变成了3个:
image.png

分别是三个特征层的输出(每个都是box层和class层拼接后的结果)。通道数144=16*4(框的4个坐标)+80(coco类别数)。

后处理的实现

改变了模型的输出,就需要我们自己实现这部分后处理了,这部分我们可以参考ultralytics/nn/modules/head.py中的实现,主要的过程:
1.将三个特征层输出拼接得到1x144x8400的特征图,8400=80*80+40*40+20*20
2.将特征图切分为1x64x8400(对于框)和1x80x8400(对于类别)的2个特征图
3.对于框的特征图进行softmax操作后,与1x16x1x1的矩阵进行一个卷积,然后转换为原图上的xywh坐标(1x4x8400),对于类别特征图进行sigmoid操作
4.将2个特征图拼接得到1x84x8400的特征图(与未修改导出前的模型输出一致了)
5.阈值过滤以及nms得到最终的输出。
对于1~4,python(numpy)实现如下:

def xywh2xyxy(x: np.ndarray):
    """
    Convert bounding box coordinates from (x, y, width, height) format to (x1, y1, x2, y2) format where (x1, y1) is the
    top-left corner and (x2, y2) is the bottom-right corner.

    Args:
        x (np.ndarray) or (torch.Tensor): The input bounding box coordinates in (x, y, width, height) format.
    Returns:
        y (np.ndarray) or (torch.Tensor): The bounding box coordinates in (x1, y1, x2, y2) format.
    """
    y = np.copy(x)
    y[..., 0] = x[..., 0] - x[..., 2] / 2  # top left x
    y[..., 1] = x[..., 1] - x[..., 3] / 2  # top left y
    y[..., 2] = x[..., 0] + x[..., 2] / 2  # bottom right x
    y[..., 3] = x[..., 1] + x[..., 3] / 2  # bottom right y
    return y

def make_anchors(feats: np.ndarray, strides, grid_cell_offset=0.5):
    """Generate anchors from features."""
    anchor_points, stride_tensor = [], []
    assert feats is not None
    dtype = feats[0].dtype
    for i, stride in enumerate(strides):
        _, _, h, w = feats[i].shape
        sx = np.arange(stop=w, dtype=dtype) + grid_cell_offset  # shift x
        sy = np.arange(stop=h, dtype=dtype) + grid_cell_offset  # shift y
        sx, sy = np.meshgrid(sx, sy)
        anchor_points.append(np.stack((sx, sy), -1).reshape(-1, 2))
        stride_tensor.append(np.full((h * w, 1), stride, dtype=dtype))
    return np.concatenate(anchor_points), np.concatenate(stride_tensor)

def dist2bbox(distance: np.ndarray, anchor_points, xywh=True, dim=-1):
    """Transform distance(ltrb) to box(xywh or xyxy)."""
    lt, rb = np.array_split(distance, 2, dim)
    x1y1 = anchor_points - lt
    x2y2 = anchor_points + rb
    if xywh:
        c_xy = (x1y1 + x2y2) / 2
        wh = x2y2 - x1y1
        return np.concatenate((c_xy, wh), dim)  # xywh bbox
    return np.concatenate((x1y1, x2y2), dim)  # xyxy bbox

def softmax(x, axis=-1):
    # 计算指数
    exp_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
    # 计算分母
    sum_exp_x = np.sum(exp_x, axis=axis, keepdims=True)
    # 计算 softmax
    softmax_x = exp_x / sum_exp_x
    return softmax_x

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def dfl(x: np.ndarray):
    c1 = 16
    b, c, a = x.shape
    conv = np.arange(0, c1, dtype=np.float32)
    conv = conv.reshape(1, 16,1,1)
    
    softmax_x = softmax(x.reshape(b, 4, c1, a).transpose(0,2, 1,3), 1)
    return np.sum(softmax_x * conv, 1,keepdims=True).reshape(b, 4, a)

def yolov8_head(x, anchors, nc):  # prediction head
    strides = [8, 16, 32]  # P3, P4, P5 strides
    shape = x[0].shape
    reg_max = 16
    no = nc + reg_max*4  # number of outputs per anchor
    anchors, strides = (x.transpose(1, 0) for x in make_anchors(x, strides, 0.5))
    x_cat = np.concatenate([xi.reshape(shape[0], no, -1) for xi in x], 2)
    box, cls = np.split(x_cat,(reg_max*4,), 1)
    dbox = dist2bbox(dfl(box), anchors[np.newaxis,:], xywh=True, dim=1) * strides
    y = np.concatenate((dbox, sigmoid(cls)), 1)

    return y

对于5:

def yolov8_postprocess(prediction: np.ndarray,
                        conf_thres=0.25,
        iou_thres=0.45,
        classes=None,
        agnostic=False,
        multi_label=False,
        labels=(),
        max_det=300,
        nc=0,  # number of classes (optional)
        max_time_img=0.05,
        max_nms=30000,
        max_wh=7680,):
    
    assert 0 <= conf_thres <= 1, f'Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0'
    assert 0 <= iou_thres <= 1, f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0'
    if isinstance(prediction, (list, tuple)):  # YOLOv8 model in validation model, output = (inference_out, loss_out)
        prediction = prediction[0]  # select only inference output

    bs = prediction.shape[0]  # batch size
    nc = nc or (prediction.shape[1] - 4)  # number of classes
    nm = prediction.shape[1] - nc - 4
    mi = 4 + nc  # mask start index

    xc = np.amax(prediction[:, 4:mi], 1) > conf_thres # scores per image

    # Settings
    # min_wh = 2  # (pixels) minimum box width and height
    time_limit = 0.5 + max_time_img * bs  # seconds to quit after
    redundant = True  # require redundant detections
    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)
    merge = False  # use merge-NMS

    t = time.time()
    output = [np.zeros((0, 6 + nm))] * bs
    for xi, x in enumerate(prediction):  # image index, image inference
        # Apply constraints
        # x[((x[:, 2:4] < min_wh) | (x[:, 2:4] > max_wh)).any(1), 4] = 0  # width-height
        x = x.transpose(1, 0)[xc[xi]]  # confidence

        # If none remain process next image
        if not x.shape[0]:
            continue

        # Detections matrix nx6 (xyxy, conf, cls)
        box, cls, mask = np.split(x,(4, nc+4,), 1)
        box = xywh2xyxy(box)  # center_x, center_y, width, height) to (x1, y1, x2, y2)
        if multi_label:
            i, j = (cls > conf_thres).nonzero(as_tuple=False).T
            x = np.concatenate((box[i], x[i, 4 + j, None], j[:, None].float(), mask[i]), 1)
        else:  # best class only
            conf = cls.max(1, keepdims=True)
            j = np.argmax(cls, 1, keepdims=True)
            x = np.concatenate((box, conf, j.astype(float), mask), 1)[np.squeeze(conf > conf_thres)]

        # Apply finite constraint
        # if not torch.isfinite(x).all():
        #     x = x[torch.isfinite(x).all(1)]

        # Check shape
        n = x.shape[0]  # number of boxes
        if not n:  # no boxes
            continue
        x = x[x[:, 4].argsort()[::-1][:max_nms]]  # sort by confidence and remove excess boxes

        # Batched NMS
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes,如果是agnostic,那么就是0,否则就是max_wh,为了对每种类别的框进行NMS
        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores
        # i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS
        i = cv2.dnn.NMSBoxes(boxes.tolist(), scores.tolist(), conf_thres,
                                   iou_thres).flatten()
        
        i = i[:max_det]  # limit detections

        output[xi] = x[i]
        
        if (time.time() - t) > time_limit:
            break  # time limit exceeded

    return output

onnx模型推理验证

为了验证我们实现的后处理的正确性,先使用onnx模型推理测试。

import os
import cv2
import onnxruntime
import numpy as np
model = onnxruntime.InferenceSession("weights/yolov8n.onnx",providers=['CUDAExecutionProvider'])
input_name = model.get_inputs()[0].name
image_name="0.jpg"
image = cv2.imread(image_name)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (640,640))
image = image.transpose(2,0,1)
image = image.astype('float32')
image = image/255.0
image = image[np.newaxis,:]

outputs = model.run(None, {input_name: image})
outputs = yolov8_head(outputs, anchors=None, nc=80)
outputs = yolov8_postprocess(outputs, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, labels=(), max_det=300, nc=0, max_time_img=0.05, max_nms=30000, max_wh=7680)
result = outputs[0]
image = cv2.imread(image_path)
for box in result:
	box = box.tolist()
	cls = int(box[5])
	
	box[0:4] = [int(i) for i in box[0:4]]
	x1 = box[0]
	y1 = box[1]
	x2 = box[2]
	y2 = box[3]
	
	score = box[4]
	cv2.rectangle(image, (x1,y1), (x2,y2), (0,0,255), 2)
	cv2.putText(image, "{}_{}".format(cls,str(score)), (x1,y1), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
# cv2.imwrite(os.path.join(save_dir, str(not_zeor_cls[0]), image_name), image)
cv2.imshow("image", image)
cv2.waitKey(0)    

image.png
结果与pytorch执行结果完全一致。

模型量化

步骤参考 rv1109/1126 rknn 模型量化过程_CodingInCV的博客-CSDN博客 采用默认量化方式。

rknn推理

if __name__ == "__main__":
    # Create RKNN object
    rknn = RKNN()

    # Direct load rknn model
    print('--> Loading RKNN model')
    ret = rknn.load_rknn('/mnt/pai-storage-8/jieshen/code/yolov8/weights/yolov8n_nohead.rknn')
    rknn.eval_perf()
    if ret != 0:
        print('Load  failed!')
        exit(ret)
    rknn.init_runtime()

    image = cv2.imread("../coco_resize/0.jpg")
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    outputs = rknn.inference(inputs=[image])
    outputs = yolov8_head(outputs, None, 80)
    # print(outputs.shape)
    outputs = yolov8_postprocess(outputs, conf_thres=0.25, iou_thres=0.45, max_det=300)

    result = outputs[0]
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    for box in result:
        box = box.tolist()
        cls = int(box[5])
        
        box[0:4] = [int(i) for i in box[0:4]]
        x1 = box[0]
        y1 = box[1]
        x2 = box[2]
        y2 = box[3]
        
        score = box[4]
        cv2.rectangle(image, (x1,y1), (x2,y2), (0,0,255), 2)
        cv2.putText(image, "{}_{}".format(cls,str(score)), (x1,y1), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
    # cv2.imwrite(os.path.join(save_dir, str(not_zeor_cls[0]), image_name), image)
    cv2.imshow("image", image)
    cv2.waitKey(0)   

    rknn.release()

image.png
可以看到检测结果和原模型还是差异挺大的(15是猫,66是键盘),不过结果还是正确的。总体的量化精度有待评估。

结语

通过对导出的模型进行一定的修改,1109上可以实现yolov8的运行并得到检测框,不过最终的运行速度和精度还有待验证。后处理的方式目前也是完全按照pytorch中的实现,过多的concat和split,可能对于C++并不太友好,后续尝试用更好的实现方式。
Todo: 量化精度的测试以及C++部署

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/835213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hutool Sm2加解密-自定义公钥私钥

代码 首先说结论&#xff0c;hutool 的SM2对象的公钥私钥是不关联的&#xff0c;你可以存自己的私钥和别人的公钥&#xff0c;这样解密的时候只要协商好就能用了&#xff0c;以下是调换公钥私钥的加解密案例 公钥格式 04公钥x公钥y //使用自定义的公钥私钥生成sm2Testpubli…

android资深工程师如何分析system_server异常

Android系统中的System Server负责管理许多关键功能&#xff0c;例如进程管理、电源管理等。如果System Server出现异常&#xff0c;可能会导致系统重启或部份功能失效。作为Android资深工程师&#xff0c;分析System Server的异常状况时&#xff0c;考虑以下步骤&#xff1a;查…

晶澳转债上市价格预测

晶澳转债 基本信息 转债名称&#xff1a;晶澳转债&#xff0c;评级&#xff1a;AA&#xff0c;发行规模&#xff1a;89.603077亿元。 正股名称&#xff1a;晶澳科技&#xff0c;今日收盘价&#xff1a;31.71元&#xff0c;转股价格&#xff1a;38.78元。 当前转股价值 转债面值…

我做了一个JPA Specification的优化

目录 一、参考二、概述流程效果感受 三、实现原理AST树结构AstNode主要结构 把表达式解析成AstAst 解析成 JPA Specification 三、项目地址四、目前阶段的总结 一、参考 如何搞一个支持自定义函数和变量的四则运算的抽象语法树出来 二、概述 这是一个可以优化生成Specificati…

Android SystemServer中Service的创建和启动方式(基于Android13)

Android SystemServer创建和启动方式(基于Android13) SystemServer 简介 Android System Server是Android框架的核心组件&#xff0c;运行在system_server进程中&#xff0c;拥有system权限。它在Android系统中扮演重要角色&#xff0c;提供服务管理和通信。 system …

Pycharm中修改注释文本的颜色(详细设置步骤)

下面是在Pycharm中设置注释文本颜色的详细步骤&#xff1a; 下面是修改前后对比&#xff1a; 修改前注释行的颜色&#xff1a; 修改后注释行的颜色&#xff1a; 以上就是Pycharm中修改注释文本颜色的详细步骤&#xff0c;希望能帮到你&#xff01;

pve安装dsm7.2,并启用照片同步

目录 1.文件准备 2. 创建虚拟机 3. 编译引导文件 4. 群晖安装 5. 安装Photos和mmfpeg 6. 安装手机APP 之前安装了pve版本的dsm6.2了&#xff0c;近期换硬盘&#xff0c;加上对dsm6.2的moments性能实在不满意&#xff0c;就产生尝鲜的想法&#xff0c;因为dsm7.0发布很久了…

2024年浙财MBA项目招生信息全面了解

2024年全国管理类硕士联考备考已经到了最火热的阶段&#xff0c;不少考生开始持续将注意力集中在备考的规划中&#xff01;杭州达立易考教育整合浙江省内的MBA项目信息&#xff0c;为大家详细梳理了相关报考参考内容&#xff0c;方便大家更好完成择校以及针对性的备考工作。本期…

Day12-作业(SpringBootWeb登录认证)

作业1&#xff1a;完成课上所讲解的 登录 及 登录校验 的所有功能。[ 必须 &#xff0c;至少敲两遍 - Filter] 作业2&#xff1a;调研第三方加密技术和落地方案&#xff0c;优化登录业务流程。 提示&#xff1a;推荐使用加盐加密的方式&#xff0c;对密码进行加密并校验 作业3…

【快应用】adbutton如何直接下载广告而不跳落地页再下载

【关键词】 原生广告、adbutton、下载 【问题背景】 快应用中的原生广告推出了adbutton组件来直接下载广告app&#xff0c;在使用的时候&#xff0c;点击adbutton按钮的安装文案&#xff0c;不是直接下载广告app&#xff0c;而是跳转到落地页后直接下载&#xff0c;这种情形该…

企业工程项目管理系统源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理) em

​ 工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#…

cad中的曲线区域是如何绘制的

cad中的曲线区域是如何绘制的 最近需要把cad中的设备锁在区域绘画出来&#xff0c;不同设备放在不同区域 组合工具命令PLPE 步骤&#xff1a; 1.先用pl绘制&#xff0c;把设备都是绘制在pl的曲线范围内 2.用pe命令&#xff0c;选择pl的区域进行曲线&#xff08;s&#xff…

基于SpringBoot+Vue的MOBA类游戏攻略分享平台设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

软考A计划-系统集成项目管理工程师-信息文档和配置管理-下

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

华为杯竞赛、高教社杯和数学建模国赛实现逆袭;评奖评优加分冲冲冲!

目录 ⭐ 赛事介绍 ⭐ 参赛好处 ⭐ 辅导比赛 ⭐ 赛事介绍 华为杯全国研究生数学建模竞赛是由华为公司主办的一项面向全国研究生的数学建模竞赛。该竞赛旨在通过实际问题的建模和解决&#xff0c;培养研究生的创新能力和团队合作精神&#xff0c;推动科技创新和应用。华为杯竞…

【超细节】Vue3的属性传递——Props

目录 前言 一、定义 二、使用 1. 在 setup 中&#xff08;推荐&#xff09; 2. 非 setup 中 3. 对象写法的校验类型 4. 使用ts进行类型约束 5. 使用ts时props的默认值 三、注意事项 1. Prop 名字格式 2. 对象或数组类型的默认值 3. Boolean 类型转换 前言 Vue3相较…

代码签名证书是什么?

代码签名证书是什么&#xff1f;有什么作用&#xff1f;代码签名证书是提供软件开发者可以进行代码软件数字签名的认证服务。通过对代码的数字签名可以消除软件在Windows系统被下载安装时弹出的“不明开发商”安全警告&#xff0c;保证代码完整性和不被恶意篡改&#xff0c;使软…

【严重】泛微 e-cology <10.58.3 任意文件上传漏洞

漏洞描述 泛微协同管理应用平台(e-cology)是一套企业大型协同管理平台。 泛微 e-cology 10.58.3之前版本存在任意文件上传漏洞&#xff0c;由于上传接口身份认证缺失&#xff0c;未经过身份验证的攻击者可以构造恶意请求将文件上传至服务器&#xff0c;攻击者可能通过上传jsp…

Python web实战之 Django 的模板语言详解

关键词&#xff1a; Python、web开发、Django、模板语言 概要 作为 Python Web 开发的框架之一&#xff0c;Django 提供了一套完整的 MVC 模式&#xff0c;其中的模板语言为开发者提供了强大的渲染和控制前端的能力。本文介绍 Django 的模板语言。 1. Django 模板语言入门 Dj…

神策新一代分析引擎架构演进

近日&#xff0c;神策数据已经推出全新的神策分析 2.5 版本&#xff0c;该版本支持分析模型与外部数据的融合性接入&#xff0c;构建全域数据融合模型&#xff0c;实现从用户到经营的全链路、全场景分析。新版本的神策分析能够为企业提供更全面、更有效的市场信息和经营策略&am…