当前位置: 首页 > news >正文

在RK3588上实现YOLOv8n高效推理:从模型优化到GPU加速后处理全解析

在RK3588上实现YOLOv8n高效推理:从模型优化到GPU加速后处理全解析

    • 一、背景介绍:为什么选择RK3588与YOLOv8n?
    • 二、性能数据
    • 三、参考链接
    • 四、完整实现流程详解
      • 4.1 安装`ultralytics`
      • 4.2 安装`rknn-toolkit2`
      • 4.3 导出`ONNX`模型
        • 4.3.1 方案一 导出NMS之前的Box【RK3588上int8精度有问题,*放弃该方案*】
        • 4.3.2 方案二 导出box decoding之前的feature map
      • 4.4 生成RKNN模型,用CPU模拟计算
        • 4.4.1 下载测试及量化图片
        • 4.4.2 模型转换脚本
      • 4.5 在RK3588上运行,后处理用numpy
      • 4.6 用`pyopencl`加速后处理
        • 4.6.1 循环执行一个空的Kernel,避免GPU休眠
        • 4.6.2 NPU推理+GPU后处理完整代码

一、背景介绍:为什么选择RK3588与YOLOv8n?

在边缘计算设备上部署目标检测模型时,我们通常会面临两个关键挑战:计算资源受限和实时性要求。RK3588作为新一代旗舰级AIoT芯片,其内置的NPU(神经网络处理单元)提供了6TOPS的算力,非常适合部署轻量级目标检测模型。

YOLOv8n(YOLO You Only Look Once的第八代nano版本)是当前最先进的轻量级检测模型之一,在保持较高精度的同时,模型大小仅6MB左右。但直接将PyTorch模型部署到嵌入式设备会面临两个主要问题:

  1. 后处理耗时严重:模型输出的原始检测框需要经过复杂的解码和非极大值抑制(NMS),在CPU上执行这些操作会成为性能瓶颈
  2. 硬件适配困难:原始模型需要转换为硬件专用格式(如RKNN)才能充分利用NPU加速

本文将详细介绍如何通过模型优化、量化压缩和GPU加速后处理等技术,在RK3588上实现YOLOv8n的高效部署。

二、性能数据

方案FP16(ms)INT8(ms)
numpy后处理第一次 Infer:90 PostProc:308 E2E:399 BoxCount:34
第十次 Infer:90 PostProc:308. E2E:399.s BoxCount:34
第一次 Infer:50 PostProc:307 E2E:358 BoxCount:30
第十次 Infer:43 PostProc:309 E2E:352 BoxCount:30
pyopencl GPU后处理第一次 Infer:89 PostProc:50 E2E:139 BoxCount:31
第十次 Infer:81 PostProc:4 E2E:86 BoxCount:31
第一次 Infer:49 PostProc:49 E2E:98 BoxCount:30
第十次 Infer:43 PostProc:4 E2E:47 BoxCount:30

从上表可以看出两个重要结论:

  1. 量化加速效果显著:INT8量化使推理时间减少45%(从90ms到43ms)
  2. 后处理优化空间巨大:使用GPU加速后处理,端到端时间降低78%

YOLOv8的输出解码包含以下主要步骤:

  1. 特征图解析:处理3个不同尺度的输出层(80x80, 40x40, 20x20)
  2. DFL解码:使用分布聚焦损失解码边界框坐标
  3. Sigmoid计算:对分类置信度进行激活
  4. NMS筛选:过滤重叠检测框

三、参考链接

  • 基于BoxMOT的目标检测与跟踪全流程详解
  • 在RK3588上使用PyOpenCL加速NMS:原理、实现与性能对比

四、完整实现流程详解

4.1 安装ultralytics

git clone https://github.com/mikel-brostrom/ultralytics.git
cd ultralytics
git checkout 8e17ff56a9db8933a1962b88e05547dd2cce9c48
export PYTHONPATH=$PWD:$PYTHONPATH
wget https://huggingface.co/Ultralytics/YOLOv8/resolve/main/yolov8n.pt
pip3.10 install matplotlib

4.2 安装rknn-toolkit2

git clone https://github.com/airockchip/rknn-toolkit2.git
cd rknn-toolkit2/
pip3.10 uninstall rknn-toolkit2 -y
pip3.10 install -r  rknn-toolkit2/packages/arm64/arm64_requirements_cp310.txt
pip3.10 install rknn-toolkit2/packages/arm64/rknn_toolkit2-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
cp rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so /usr/lib/aarch64-linux-gnu/
pip3.10 install rknn-toolkit-lite2/packages/rknn_toolkit_lite2-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

4.3 导出ONNX模型

4.3.1 方案一 导出NMS之前的Box【RK3588上int8精度有问题,放弃该方案
cat> export_onnx.py<<-'EOF'
from ultralytics import YOLO
# 加载模型
model = YOLO("yolov8n.pt")
# 将模型导出为 ONNX 格式
path = model.export(format="onnx")  # 返回导出模型的路径
EOF
python3.10 export_onnx.py
4.3.2 方案二 导出box decoding之前的feature map
cat> export_onnx.py<<-'EOF'
from ultralytics import YOLO
import importlib
def forward(self,x):y=[]for i in range(self.nl):t1 = self.cv2[i](x[i])t2 = self.cv3[i](x[i])y.append(t1)y.append(t2)return ydef patch_func(old_func: str, new_func: callable):items=old_func.split('.')parent='.'.join(items[:-2])m=importlib.import_module(parent)m=getattr(m,items[-2])func_name=items[-1]setattr(m,func_name,new_func)patch_func("ultralytics.nn.modules.head.Detect.forward",forward)
# 加载模型
model = YOLO("yolov8n.pt",task="detect")
import torch
dummy_input = torch.randn(1, 3, 640, 640)
torch.onnx.export(model.model, dummy_input, "yolov8n.onnx", verbose=False, opset_version=11)
EOF
python3.10 export_onnx.py

4.4 生成RKNN模型,用CPU模拟计算

4.4.1 下载测试及量化图片
wget -O img.jpg https://raw.githubusercontent.com/hi20240217/csdn_images/refs/heads/main/img.jpg
4.4.2 模型转换脚本
cat> gen_yolov8n_rknn.py<<-'EOF' 
import os
import urllib
import traceback
import time
import sys
import numpy as np
import cv2
from rknn.api import RKNN
from math import expimport cv2
import numpy as npCLASSES = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light','fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow','elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee','skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard','tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple','sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch','potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone','microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear','hair drier', 'toothbrush']import math# --------- 简化数据结构 ---------
# 这里改用普通Python字典,后续在OpenCL中用struct替代
def create_detect_box(class_id, score, xmin, ymin, xmax, ymax):return {'class_id': class_id,'score': score,'xmin': xmin,'ymin': ymin,'xmax': xmax,'ymax': ymax}# --------- 计算IOU ---------
def iou(box1, box2):xmin = max(box1['xmin'], box2['xmin'])ymin = max(box1['ymin'], box2['ymin'])xmax = min(box1['xmax'], box2['xmax'])ymax = min(box1['ymax'], box2['ymax'])inner_width = xmax - xmininner_height = ymax - yminif inner_width <= 0 or inner_height <= 0:return 0.0inner_area = inner_width * inner_heightarea1 = (box1['xmax'] - box1['xmin']) * (box1['ymax'] - box1['ymin'])area2 = (box2['xmax'] - box2['xmin']) * (box2['ymax'] - box2['ymin'])union = area1 + area2 - inner_areaif union <= 0:return 0.0return inner_area / union# --------- NMS ---------
def nms(detect_boxes, nms_thresh):# 手写简单的降序排序(可移植到OpenCL的并行排序算法)# 这里仍保持Python写法,移植时实现对应的排序内核for i in range(len(detect_boxes)):max_idx = ifor j in range(i+1, len(detect_boxes)):if detect_boxes[j]['score'] > detect_boxes[max_idx]['score']:max_idx = jif max_idx != i:detect_boxes[i], detect_boxes[max_idx] = detect_boxes[max_idx], detect_boxes[i]keep = []suppressed = [False] * len(detect_boxes)for i in range(len(detect_boxes)):if suppressed[i]:continuekeep.append(detect_boxes[i])for j in range(i+1, len(detect_boxes)):if suppressed[j]:continueif detect_boxes[i]['class_id'] != detect_boxes[j]['class_id']:continueif iou(detect_boxes[i], detect_boxes[j]) > nms_thresh:suppressed[j] = Truereturn keep# --------- Sigmoid改写 ---------
def sigmoid(x):# 为避免math.exp溢出,限定输入范围if x < -40.0:return 0.0elif x > 40.0:return 1.0else:return 1.0 / (1.0 + math.exp(-x))# --------- 后处理 ---------
def postprocess(out, meshgrid, strides,img_h, img_w,input_img_w, input_img_h,head_num, map_size,object_thresh, nms_thresh,class_num):detect_result = []output = [o.reshape(-1) for o in out
http://www.xdnf.cn/news/683731.html

相关文章:

  • 电机控制杂谈(26)——电机驱动系统的编码器的测速噪声
  • RK3568DAYU开发板-驱动平台驱动案例--PWM
  • 【Linux】(1)—进程概念-①冯诺依曼体系结构
  • 想查看或修改 MinIO 桶的匿名访问权限(public/private/custom)
  • java基础学习(十八)
  • 大模型微调(面经总结)
  • 代码风格指南
  • 聚焦北京央美备考画室:探寻实力之巅
  • 码蹄集——圆周率II、三个非负整数
  • PCB设计自检表
  • 基于心理健康与数字行为数据的多维度分析
  • JAVA运算符详解
  • Oracle向PG转移建议以及注意点
  • 57页 @《人工智能生命体 新启点》中國龍 原创连载
  • IvorySQL 核心技术解读:双 Parser 架构如何定义数据库兼容性?
  • python训练营打卡第36天
  • 竞赛小算法总结(二):gcdlcm,拓展欧几里得线性同余,逆元(含代码详解)
  • AE的ai图层导到Ai
  • spring4第2课-ioc控制反转-依赖注入,是为了解决耦合问题
  • WIN10 安装dify ollama搭建工作流agent
  • 两种主流检索技术:BM25(基于关键词匹配)和向量相似度检索
  • LVGL(Flex布局)
  • Docker修改镜像存放位置
  • qiankun 子应用怎样通过 props拿到子应用【注册之后挂载之前】主应用中发生变更的数据
  • vue2轮播图组件
  • 计算机网络实验课(二)——抓取网络数据包,并实现根据条件过滤抓取的以太网帧,分析帧结构
  • 如何检查液质联用仪LCMS的真空度
  • 提升前端性能:减少DOM操作
  • 在线项目管理工具对比:Trello、Worktile等20款软件测评
  • Java的Spring Cloud生态中实现SSE(Server-Sent Events)服务端实践