使用RKNN在Orange Pi 5 (RK3588s) 上部署推理PPO深度学习模型

news2024/9/23 22:31:21

文章目录

  • 一、前言
    • 1️⃣、Orange Pi 是什么?
    • 2️⃣、PPO 是什么?
    • 3️⃣、RKNN 是什么?
    • 3️⃣、ONNX 是什么?
  • 二、项目简介
  • 三、部署流程
    • 1️⃣、PPO 网络结构
    • 2️⃣、PPO 输出模型,模型转换,以及对比检查
    • 3️⃣、.onnx -> .rknn
    • 4️⃣、RK3588s 推理 .rknn 模型
  • 总结

一、前言

本博客皆在展示如何在Orange Pi 5 上使用 RKNN C API 使用C语言来进行模型的部署,不设计以及讨论PPO网络的实现以及细节

当前,深度学习的热潮久退不下。许多爱好者也会使用 Pytorch 等工具在自己的 Windows 环境下结合训练自己的深度学习网络模型,然后再进行模型验证,在仿真环境发现模型能够达到自己的需求,许多人就到此就结束了,因为可能目标本就是学习目的或者是了解一二,仅此而已,也就不必再进一步。但是另一批人希望自己的模型能够部署在一些嵌入式设备上实现从仿真到实验的跳跃。现在有两条比较成熟的方案:
     ~~~~     1. 将嵌入式设备的数据发送到 Windows 上位机,在 Windows 配置好的环境下推理完毕后再将结果发送给嵌入式设备,实现 嵌入式->上位机->嵌入式 数据流的传输与控制。这个方法的优点是 Windows 环境的推理能力较为强劲,在处理一些例如图像分类,图像识别等任务的时候能够很好的完成需求,但是缺点是数据在各个端点传输的时候时间延迟应该会较大,对于一些对时间延迟较为苛刻的任务,此方法不能够保证实时性.
     ~~~~     2. 直接将模型部署在嵌入式设备上,实现端设备直接推理模型的功能。这样可以保证实时性,但是缺点是对嵌入式设备的计算能力需求较大,且部署时需要对厂家提供的API较为熟悉。对于小型网络,选此方法为上上签。

本博客将会展示如何将一个简单 PPO (Proximal Policy Optimization) 算法模型通过使用 RKNN C API 部署到 Orange Pi 5 上并实现一套完整的 推理+控制 流程。


1️⃣、Orange Pi 是什么?

引用自 Orange Pi 5 介绍页,跳转连接

Orange Pi 是深圳市迅龙软件有限公司的开源产品品牌。简单来说就是国产树莓派。本博客使用其旗下的 Orange Pi 5 系列产品。Orange Pi 5 采用了 瑞芯微RK3588S 新一代八核64位处理器,具体为四核A76+四核A55,采用了8nm工艺设计,主频最高可达2.4GHz,集成ARM Mali-G610 MP4 GPU,内嵌高性能3D和2D图像加速模块,内置高达6 Tops算力的AI加速器NPU,拥有4GB/8GB/16GB(LPDDR4/4X),具有高达8K显示处理能力。下图就是 Orange Pi 5 的俯视图了。

请添加图片描述

2️⃣、PPO 是什么?

引用自《PPO(Proximal Policy Optimization)算法原理及实现,详解近端策略优化》,跳转链接。

PPO(Proximal Policy Optimization,近端策略优化)是一种强化学习算法,由 John Schulman 等人在2017年提出。PPO属于策略梯度方法,这类方法直接对策略(即模型的行为)进行优化,试图找到使得期望回报最大化的策略。PPO旨在改进和简化以前的策略梯度算法,如TRPO(Trust Region Policy Optimization,信任域策略优化),它通过几个关键的技术创新提高了训练的稳定性和效率。

PPO的主要特点包括:
裁剪的概率比率:PPO使用一个目标函数,其中包含了一个裁剪的概率比率,这个比率是旧策略和新策略产生动作概率的比值。这个比率被限制在一个范围内,防止策略在更新时做出太大的改变。
多次更新:在一个数据批次上可以安全地进行多次更新,这对于样本效率非常重要,尤其是在高维输入和实时学习环境中。
简单实现:与TRPO相比,PPO更容易实现和调整,因为它不需要复杂的数学运算来保证策略更新的安全性。
平衡探索与利用:PPO尝试在学习稳定性和足够的探索之间取得平衡,以避免局部最优并改进策略性能。
PPO已被广泛应用于各种强化学习场景,包括游戏、机器人控制以及自然语言处理中的序列决策问题。它是目前最流行的强化学习算法之一。

3️⃣、RKNN 是什么?

RKNN 是 Rockchip npu 平台使用的模型类型,以.rknn后缀结尾的模型文件。Rockchip 提供了完整了模型转换 Python 工具,方便用户将自主研发的算法模型转换成 RKNN 模型,同时 Rockchip 也提供了C/C++和Python API 接口。也就是说 RKNN 是来自瑞芯微公司旗下的产品,RK3588s 的 RKNN系列工具有其GitHub链接,跳转连接,以供参考。我们需要借助这个工具实现模型在 Orange Pi 上的部署。我们部署主要使用的是 RKNN API, 所以这里也放上 RKNN API的用户手册以供参考,跳转连接。其余的代码我会在下面的内容里直接贴出,就不附送跳转链接了。

对于 Ternsorflow, PyTorch 等其他模型,想要在 RK3588 平台运行,需要先进行模型转换。可以在搭载 Ubuntu 的PC上使用RKNN-Toolkit2工具将模型转化为 RKNN 格式,将其交叉编译后才可以部署到开发板上。

总体开发流程(以pytorch框架开发,C++部署):

1. 在 PC 端安装 RKNN-Toolkit2 工具
2. 使用 pytorch 搭建网络并训练,保存为 .pth 文件
3. 在 PC 端使用将 .pth 文件转化为 .onnx 文件,接下来使用 RKNN-Toolkit2 将 .onnx 文件转化为 .rknn 文件
4. 使用 RKNN SDK 提供的 C 接口 API 按照官方给定的调用流程交叉编译后即可执行调用 .rknn 模型文件

总结:如果想要部署模型,必须将模型转为 .rknn 格式的文件,设备才能够读取运行。但是由于一些限制以及不可抗力因素,在上位机训练得到的 .pth 文件不建议直接转为 .rknn,此时需要借助一个中间格式的文件,即:.onnx 格式文件

3️⃣、ONNX 是什么?

ONNX是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的深度学习框架( Pytorch, MXNet)可以采用相同格式存储模型数据。简而言之,ONNX是一种便于在各个主流深度学习框架中迁移模型的中间表达格式。本文不着重介绍此格式。

二、项目简介

本次的部署基于一个具体的项目,项目的目标是控制一个末端搭载了动量轮和电机的杆子抵达目标角度。整个系统是水平摆放,所以并不受到重力的影响。传统PID可以有效的控制这个系统,但是传统PID为了迅速让目标接近系统,很有出现超调现象,并且传统PID并不能实现中间过程零力矩的控制方法。使用基于强化学习训练的模型来控制整个系统,可实现零超调。因为强化学习训练本质就是一种求最优策略的过程。整个系统的概念图如下所示,
请添加图片描述
m b , m w m_b,m_w mb,mw 为系统主体和飞轮的重量。 I b I_b Ib 为主体绕枢轴的转动惯量。 I w I_w Iw 为轮子和电机转子绕电机旋转轴的转动惯量。 l w l_w lw 为电机轴与枢轴之间的距离。 l b l_b lb 为系统主体质心与枢轴之间的距离。由于整个系统垂直于重力方向,因此系统不需要考虑重力。 T m T_m Tm 为电机提供的扭矩, C w , C b C_w,C_b Cw,Cb为轮子和系统主体的动摩擦系数。

在这个模型中,我们很容易知道轴绕着一个点旋转,飞轮绕着枢轴点旋转。所以,当我们进行分析时,我们需要分别包括飞轮的平动动能和旋转动能以及轴的旋转动能。
从拉格朗日法中,我们可以知道以下方程,
f ( x ) = { T = 1 2 I b θ ˙ b 2 + 1 2 I w ( θ ˙ b + θ ˙ w ) 2 + 1 2 m w ( l w θ ˙ b ) 2 V = 0 f(x)= \begin{cases} T=\frac{1}{2}I_b\dot\theta_b^2+\frac{1}{2}I_w(\dot\theta_b+\dot\theta_w)^2+\frac{1}{2}m_w(l_w\dot\theta_b)^2\\ V=0 \end{cases} f(x)={T=21Ibθ˙b2+21Iw(θ˙b+θ˙w)2+21mw(lwθ˙b)2V=0
最后我们可以解出此方程为,
θ ¨ b = − T m + C w θ ˙ w − C b θ ˙ b I b + m w l w 2 \ddot\theta_b=\frac{-T_m+C_w\dot\theta_w-C_b\dot\theta_b}{I_b+m_wl_w^2} θ¨b=Ib+mwlw2Tm+Cwθ˙wCbθ˙b
θ ¨ w = ( I b + I w + m w l w 2 ) ( T m − C w θ ˙ b ) I w ( I b + m w l w 2 ) + C b θ ˙ b I b + m w l w 2 \ddot\theta_w=\frac{(I_b+I_w+m_wl_w^2)(T_m-C_w\dot\theta_b)}{I_w(I_b+m_wl_w^2)}+\frac{C_b\dot\theta_b}{I_b+m_wl_w^2} θ¨w=Iw(Ib+mwlw2)(Ib+Iw+mwlw2)(TmCwθ˙b)+Ib+mwlw2Cbθ˙b
那么,现在拥有了模型,就可以基于模型来进行强化学习的训练了。

整个模型的控制大概是,
1. 实时给模型输入三个系统的当前状态,即: θ b , θ ˙ b , θ ˙ w \theta_b,\dot\theta_b,\dot\theta_w θb,θ˙b,θ˙w
2. 模型在接收到三个变量后,会放进网络中运算,给出三个概率

三个概率对应着的是反向最大力矩,零力矩,正向最大力矩的概率。所以可以说系统只能够以这三个状态进行运动。最后模型训练成功后可以得到如下的仿真,
在这里插入图片描述

三、部署流程

1️⃣、PPO 网络结构

本次在上位机训练的 PPO 的网络结构如下所示,创建了一个 1x3 输入,1x3输出的网路,中间层使用线性层链接。由于篇幅有限,且本次重点不在于PPO网络,故不展示完全代码。

# 创建价值网络
model = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.ReLU(),  
                            torch.nn.Linear(nnn * 2, nnn), torch.nn.ReLU(),
                            torch.nn.Linear(nnn, 1))
for module in model.modules():
    if isinstance(module, torch.nn.Linear):
        torch.nn.init.orthogonal_(module.weight) 
nnn = 512
# 创建策略网络
policy = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.Tanh(),
                             torch.nn.Linear(nnn * 2, nnn), torch.nn.Tanh(),
                             torch.nn.Linear(nnn, 3), torch.nn.Softmax(dim=1)) 

2️⃣、PPO 输出模型,模型转换,以及对比检查

经过训练,最后能够得到一个 .pth 文件,这里我分享一下我的 .pth文件,下载连接。 随后,我们需要将其转换为 .onnx 中间格式文件。代码如下:

import torch

if torch.cuda.is_available():
    device = torch.device("cuda:0")
    print("Running on the GPU")
else:
    device = torch.device("cpu")
    print("Running on the CPU")
torch.cuda.set_device(device)

nnn = 512
# 实例化策略网络
policy = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.Tanh(),  # 双曲正切激活函数
                             torch.nn.Linear(nnn * 2, nnn), torch.nn.Tanh(),
                             torch.nn.Linear(nnn, 3), torch.nn.Softmax(dim=1))  # 计算每个动作的概率
policy.to(device)
policy.load_state_dict(
    torch.load('C:/Users/Administrator/Desktop/Cases/RL-balance-robot/horizontal/training/outputs/5degrees-PPO-zero-torque.pth'))
policy.eval()
dummy_input = torch.randn(1, 3, device='cuda').reshape(1, 3)
torch.onnx.export(policy, dummy_input, "PPO.onnx", opset_version=11, verbose=True)

这里需要注意,因为我的输入是 1x3, 所以这里设置 torch.randn(1,3)。 如果你们的输入是1x5,则相应的应该换为torch.randn(1,5)。
这样我们就得到了一个 .onnx 中间格式的模型文件了。

现在我我们来测量模型转换是否成功。测试的方法是使用 .pth 模型文件,随机输入数据,看输出的三个概率是多大。然后再载入转换后的 .onnx 模型文件,输入同样的数据,看输出的结果是否和 .pth 模型文件输出的一样。若相同,则转换成功。测试 .pth 模型文件的代码如下所示,

import torch

device = torch.device("cpu")
nnn = 512
policy1 = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.Tanh(),
                              torch.nn.Linear(nnn * 2, nnn), torch.nn.Tanh(),
                              torch.nn.Linear(nnn, 3), torch.nn.Softmax(dim=1))
policy1.load_state_dict(torch.load('C:/Users/Administrator/Desktop/Cases/RL-balance-robot/horizontal/training/outputs/5degrees-PPO-zero-torque.pth'))
policy1.to('cpu')
prob = policy1(torch.FloatTensor([[10, 200, 200]]))
print(prob)

输出结果如下所示,

tensor([[1.0021e-28, 1.5017e-13, 1.0000e+00]], grad_fn=<SoftmaxBackward0>)

可以看出,在此时的输入条件下,模型预测的结果第三个数最大。

现在看看 .onnx 文件输入相同的数据,是否能够和 .pth 模型输出一样的结果。测试 .onnx 模型文件的代码如下所示,

import onnx
import onnxruntime

# Load the ONNX model
model = onnx.load("PPO.onnx")
# Check that the IR is well formed
onnx.checker.check_model(model)
# Print a human readable representation of the graph
print(onnx.helper.printable_graph(model.graph))

session = onnxruntime.InferenceSession('PPO.onnx', None)
raw_result = session.run([], {"onnx::Gemm_0": [[10, 200, 200]]})
print(raw_result)

输出结果如下所示,

[array([[1.0021013e-28, 1.5016719e-13, 1.0000000e+00]], dtype=float32)]

.onnx 模型文件在输入数据相同的情况下,输出和 .pth 文件一样

为了预防偶然情况,本博客又多测试了几组数据,

输入数据.onnx预测结果.pth预测结果
[90,200,0][5.2034653e-33, 9.1289922e-17, 1.0000000e+00][5.2035e-33, 9.1290e-17, 1.0000e+00]
[-50,0,300][1.0000000e+00, 1.9378372e-13, 1.6095759e-31][1.0000e+00, 1.9378e-13, 1.6096e-31]
[30,50,50][2.486136e-35, 7.525642e-19, 1.000000e+00][2.4861e-35, 7.5257e-19, 1.0000e+00]
[-60,2000,0][4.5575372e-27, 7.3239895e-12, 1.0000000e+00][4.5576e-27, 7.3240e-12, 1.0000e+00]
[80,0,-3000][8.3561228e-28, 1.2708616e-14, 1.0000000e+00][8.3561e-28, 1.2709e-14, 1.0000e+00]

从表格测试结果来看,模型转换完全没问题。

3️⃣、.onnx -> .rknn

接下来我们需要用到 Linux 系统,这里使用的是虚拟机上的 Ubuntu 20.04 系统。我们需要在 Linux系统上将转换的 .onnx 文件再次转换为 .rknn 文件。使用的Ubuntu系统如下图所示:
在这里插入图片描述
我们还需要安装 RKNN-Toolkit2 环境以进行模型的转换,由于我已经安装好了,所以我这里就不再带领大家进行这一步了。这里给大家一个教程自行进行安装。这里是跳转链接

在安装 RKNN-Toolkit2 环境完毕以后,使用如下代码进行模型的转换:

from rknn.api import RKNN
import os

if __name__ == '__main__':
    platform = 'rk3588s'

    '''step 1: create RKNN object'''
    rknn = RKNN()

    '''step 2: load the .onnx model'''
    rknn.config(target_platform='rk3588s')
    print('--> Loading model')
    ret = rknn.load_onnx('PPO.onnx')
    if ret != 0:
        print('load model failed')
        exit(ret)
    print('done')

    '''step 3: building model'''
    print('-->Building model')
    ret = rknn.build(do_quantization=False)
    if ret != 0:
        print('build model failed')
        exit()
    print('done')

    '''step 4: export and save the .rknn model'''
    OUT_DIR = 'rknn_models'
    RKNN_MODEL_PATH = './{}/PPO.rknn'.format(OUT_DIR)
    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')

    '''step 5: release the model'''
    rknn.release()

这样我们就可以得到一个转换后的 .rknn 模型文件

4️⃣、RK3588s 推理 .rknn 模型

那么现在我们拥有了.rknn模型文件,理论上应该可以进行部署了,但是在此之前我们应该也在我们的Orange Pi 上安装好 RKNN 的运行环境。这里我们需要在 Orange Pi 上安装的是 RKNPU2。RKNPU2 提供访问Rockchip的NPU的便利接口——动态链接库librknnrt.so和C头文件rknn_api.h,我们可以编写C++版本的AI应用并利用RKNPU2加速。这里安装不详细介绍,提供一个跳转链接。

那么在安装完毕后,我们的板子理论上就可以运行 .rknn 模型文件了。首先打开MobaXterm进行ssh连接Orange Pi 然后将刚刚的模型文件上传到板子上。然后参考 RKNN C API 的用户手册,进行代码编写。

RK3588上有两组API可以使用,分别是通用API接口零拷贝流程API接口。两组API的主要区别在于,通用接口API每次更新帧数据,需要将外部模块分配的数据拷贝到NPU运行时的输入内存。而零拷贝流程的接口会直接使用预先分配的内存(包括NPU运行时创建的或外部其他框架创建的,比如DRM框架),减少了内存拷贝的花销。当用户输入数据只有虚拟地址时,只能使用通用API接口,当用户输入数据有物理地址或者fd时,两组接口都可以使用。对于通用API接口,首先初始化rknn_input结构体,帧数据包含在该结构体中,使用RKNN_inputs_set函数设置模型输入,等待推理结束后,使用rknn_outputs_get函数获取推理的输出,进行后处理。在每次推理前,更新帧数据。从RKNPU用户手册里摘抄的输入/输出内存由运行时分配的零拷贝API调用流程如下所示,
在这里插入图片描述
这里就不介绍通用API的流程了。虽然通用API可以将模型部署在rk3588s的npu之上,运行更快,但是我在尝试的时候发现我的模型使用通用API部署精度会大打折扣,目前还不知道有什么方法可以解决。于是本篇幅着重介绍零拷贝API的使用,这里给出一个零拷贝接口的参考例程。但是注意,本例程使用的零拷贝API无法将模型部署在npu之上,整个程序由cpu运行。初步估计可能是 rknn 给的 C 接口接受的是 float16 的变量类型,但是由 .onnx 转换的 .rknn 文件是 float32 类型的,故npu无法运行,会被切换至cpu运行。

/*-------------------------------------------
                  Includes
-------------------------------------------*/
#include "rknn_api.h"
#include <sys/time.h>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

/*-------------------------------------------
                  RKNN
-------------------------------------------*/
const int state_row = 1;  // input dimension
const int state_col = 3;   // input dimension
const int act_dim = 3;     // input dimension
float input_data[state_row][state_col] = {0.0 ,0.0 ,0.0}; // input matrix
int output_index = 0;    //index of the max value   

static void dump_tensor_attr(rknn_tensor_attr *attr);
static inline int64_t getCurrentTimeUs();

/*-------------------------------------------
                  Main Functions
-------------------------------------------*/
int ret = 0;
rknn_context ctx = 0;
rknn_sdk_version sdk_ver;
rknn_input_output_num io_num;
rknn_tensor_attr input_attrs;
rknn_tensor_attr output_attrs;
int main(int argc, char *argv[])
{
   char *model_path = argv[1];
   // Load RKNN model
   int model_len = 0;
   ret = rknn_init(&ctx, &model_path[0], 0, 0, NULL);
   if (ret < 0){
      printf("rknn_init fail! ret=%d\n", ret);
   }

   // Get sdk and driver version
   ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &sdk_ver, sizeof(sdk_ver));
   if (ret != RKNN_SUCC){
      printf("rknn_query fail! ret=%d\n", ret);
      return -1;
   }
   printf("rknn_api/rknnrt version: %s, driver version: %s\n", sdk_ver.api_version, sdk_ver.drv_version);

   // Get Model Input Output Info
   ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
   if (ret != RKNN_SUCC){
      printf("rknn_query fail! ret=%d\n", ret);
      return -1;
   }
   printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output);

   printf("input tensors:\n");
   memset(&input_attrs, 0, sizeof(rknn_tensor_attr));
   input_attrs.index = 0;
   ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs), sizeof(rknn_tensor_attr));
   if (ret < 0){
      printf("rknn_init error! ret=%d\n", ret);
      return -1;
   }
   input_attrs.type = RKNN_TENSOR_FLOAT32;
   input_attrs.size = input_attrs.size_with_stride = input_attrs.n_elems * sizeof(float);
   dump_tensor_attr(&input_attrs);

   printf("output tensors:\n");
   memset(&output_attrs, 0, sizeof(rknn_tensor_attr));
   output_attrs.index = 0;
   ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs), sizeof(rknn_tensor_attr));
   if (ret != RKNN_SUCC){
      printf("rknn_query fail! ret=%d\n", ret);
      return -1;
   }
   output_attrs.type = RKNN_TENSOR_FLOAT32;
   output_attrs.size = output_attrs.size_with_stride = output_attrs.n_elems * sizeof(float);
   dump_tensor_attr(&output_attrs);


   // create input tensor memory
   rknn_tensor_mem *input_mems;
   input_mems = rknn_create_mem(ctx, input_attrs.size);

   // create output tensor memory
   rknn_tensor_mem *output_mems;
   output_mems = rknn_create_mem(ctx, output_attrs.size);

   // Set input tensor memory
   ret = rknn_set_io_mem(ctx, input_mems, &input_attrs);
   if (ret < 0){
      printf("rknn_set_io_mem fail! ret=%d\n", ret);
      return 1;
   }
   ret = rknn_set_io_mem(ctx, output_mems, &output_attrs);
   if (ret < 0){
      printf("rknn_set_io_mem fail! ret=%d\n", ret);
      return 1;
   }

 
   input_data[0][0] = -50;
   input_data[0][1] = 0;
   input_data[0][2] = 300;

   // copy input data to input tensor memory
   memcpy(input_mems->virt_addr, input_data, sizeof(input_data));

   // Run
   int64_t start_us = getCurrentTimeUs();
   ret = rknn_run(ctx, NULL);
   int64_t elapse_us = getCurrentTimeUs() - start_us;
   if (ret < 0){
      printf("rknn run error %d\n", ret);
      return 1;
   }
   printf("Elapse Time = %.2fms\n", elapse_us / 1000.f);
   
   // print the result
   float *buffer = (float *)(output_mems->virt_addr);
   output_index = max_element(buffer,buffer+3) - buffer;

   for (int i = 0; i < state_col; i++){
      cout << input_data[0][i] << endl;
   }
   for (int i = 0; i < act_dim; i++){
      cout << buffer[i] << endl;
   }
   cout << output_index << endl;

   // Destroy rknn memory
   rknn_destroy_mem(ctx, input_mems);
   rknn_destroy_mem(ctx, output_mems);
   // Destroy
   rknn_destroy(ctx);

   return 0;
}

static inline int64_t getCurrentTimeUs()
{
   struct timeval tv;
   gettimeofday(&tv, NULL);
   return tv.tv_sec * 1000000 + tv.tv_usec;
}

static void dump_tensor_attr(rknn_tensor_attr *attr)
{
   printf("  index=%d, name=%s, n_dims=%d, dims=[%d, %d, %d, %d], n_elems=%d, size=%d, fmt=%s, type=%s, qnt_type=%s, "
   "zp=%d, scale=%f\n",
   attr->index, attr->name, attr->n_dims, attr->dims[0], attr->dims[1], attr->dims[2], attr->dims[3],
   attr->n_elems, attr->size, get_format_string(attr->fmt), get_type_string(attr->type),
   get_qnt_type_string(attr->qnt_type), attr->zp, attr->scale);
}

其中,通过直接赋值进行输入数据的更新,第一行的意思是 θ b = − 5 0 ∘ \theta_b=-50^\circ θb=50,第二行的意思是 θ ˙ d = 0 ∘ / s \dot\theta_d=0^\circ/s θ˙d=0/s,第三行的意思是 θ w = 30 0 ∘ / s \theta_w=300^\circ/s θw=300/s

input_data[0][0] = -50;
input_data[0][1] = 0;
input_data[0][2] = 300;

运行结果如下所示,
在这里插入图片描述可以看到本模型使用了0.47ms就运行完毕一次循环,输入数据是[-50,0,300],输出概率为[1,6.10948e-05,6.10948e-05],因为0号位的概率最大,所以最后会实行0号位的动作。

为了防止偶然误差,输入和上方测试一样的数据,进行数据输出结果的对比,消除偶然误差。

输入数据.rknn预测结果
[90,200,0][6.10948e-05,6.10948e-05,1]
[-50,0,300][1,6.10948e-05,6.10948e-05]
[30,50,50][6.10948e-05,6.10948e-05,1]
[-60,2000,0][6.10948e-05,6.10948e-05,1]
[80,0,-3000][6.10948e-05,6.10948e-05,1]

由于数据类型的限制,rknn输出的值最小为 6.10948e-05, 但总体结果匹配一致,可以证明此次部署成功。

总结

本次展示了如何在 OrangePi 5 上部署深度学习模型的例子,皆在展示可行性。但问题仍然存在,例如尝试使用通用API接口部署失败,无法使用npu运行模型等等。博主仍在调查研究当中,希望早日解决此类问题。

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

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

相关文章

httplib库:用C++11搭建轻量级HTTP服务器

目录 引言 一. httplib库概述 二. httplib核心组件 2.1 数据结构 2.2 类和函数 2.3 服务器搭建 ​编辑 结语 引言 在现代软件开发中&#xff0c;HTTP服务是网络应用的基础。对于需要快速搭建HTTP服务器或客户端的场景&#xff0c;使用成熟的第三方库可以极大提高开发效…

微软运行库全集合:一站式解决兼容性问题

开发者在部署应用程序时经常遇到因缺少运行库而引发的兼容性问题。为了解决这一问题&#xff0c;电脑天空推荐微软常用运行库合集&#xff0c;一个集成了微软多个关键运行库组件的软件包。 &#x1f4da; 包含组件概览&#xff1a; Visual Basic Virtual Machine&#xff1a;…

电销机器人助力企业节约成本提升业绩

电销系统机器人 的出现对企业来说不但仅是提高一点工作效率这么简单。首先从拨打电话上来看&#xff0c;电销系统机器人每日能够拨打几千通、上万通电话无上线&#xff0c;都可自行设置&#xff0c;并且并不会感觉到累&#xff0c;更不会由于被挂掉电话而影响心情&#xff0c;这…

KNN算法-opencv的运用

文章目录 opencv介绍与安装KNN算法中opencv的运用1.数据介绍2.图片处理3.图像切分与重组4.分配标签5.模型构建与训练6.预测结果7.模拟测试8.代码及详注 opencv介绍与安装 OpenCV&#xff08;Open Source Computer Vision Library&#xff0c;开源计算机视觉库&#xff09;是一…

ShareSDK Twitter

创建应用 1.登录Twitter控制台并通过认证 2.点击Developer Portal进入Twitter后台 3.点击Sign up for Free Account创建应用 4.配置应用信息 以下为创建过程示例&#xff0c;图中信息仅为示例&#xff0c;创建时请按照真实信息填写&#xff0c;否则无法正常使用。 权限申请…

智能安全守护,寺庙安全用电解决方案

在四川省蓬溪县城北&#xff0c;高峰山以其千年的历史沉淀和独特的文化风貌&#xff0c;默默诉说着道教与佛教交融的传奇。然而&#xff0c;2017年5月31日凌晨的一声巨响&#xff0c;打破了这里的宁静&#xff0c;一场突如其来的大火&#xff0c;让这座承载着无数信徒信仰与梦想…

10步搞定Python爬虫从零到精通!

学习Python网络爬虫可以分为以下几个步骤&#xff0c;每一步都包括必要的细节和示例代码&#xff0c;以帮助你从零开始掌握这一技能。 第一步&#xff1a;理解网络爬虫基础 什么是网络爬虫&#xff1f; 网络爬虫是一种自动化程序,用来从互联网上收集数据.它通过发送 HTTP 请求…

大学生科创项目在线管理系统的设计与实现

TOC springboot267大学生科创项目在线管理系统的设计与实现 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电…

爬虫案例4——爬取房天下数据

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正 任务&#xff1a;从房天下网中爬取小区名称、地址、价格和联系电话 目标网页地址&#xff1a;https://newhouse.fang.com/house/s/ 一、思路和过程 目标网页具体内容如下&#xff1a; ​​​​ …

揭秘面试必备:高频算法与面试题全面解析

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

Web安全:SqlMap工具

一、简介 sqlmap 是一款开源的渗透测试工具&#xff0c;可以自动化进行SQL注入的检测、利用&#xff0c;并能接管数据库服务器。它具有功能强大的检测引擎,为渗透测试人员提供了许多专业的功能并且可以进行组合&#xff0c;其中包括数据库指纹识别、数据读取和访问底层文件系统…

SystemUI手势操作隐藏显示导航栏

在Android 12中&#xff0c;通过SystemUI手势操作来隐藏和显示导航栏主要涉及对系统UI的定制和编程控制。以下是一些实现这一功能的方法&#xff1a; 第一类. 使用WindowInsetsController Android 12引入了一个新的WindowInsetsController类&#xff0c;它允许开发者更好地控…

加速科技精彩亮相2024中国(深圳)集成电路峰会

8月16日&#xff0c;2024中国&#xff08;深圳&#xff09;集成电路峰会&#xff08;简称“ICS2024峰会”&#xff09;在深圳如期开展&#xff0c;为行业带来一场技术盛宴。在这场盛会中&#xff0c;加速科技携2款核心产品——ST2500EX、ST2500E重磅亮相&#xff0c;凭借领先的…

【leetcode详解】特殊数组II : 一题代表了一类问题(前缀和思想)

前缀和的优势 给定一个数组&#xff0c;前缀和的特点在于&#xff0c;任意给出一对始末位置&#xff0c;能够用O(1)的时间复杂度得到始末位置之间所有元素的某种关系。 题型分析 这道题目正是“给出始末位置&#xff0c;检测其中元素特点”那一类&#xff0c;那我们就想&#…

【机器学习西瓜书学习笔记——概率图模型】

机器学习西瓜书学习笔记【第十四章】 第十四章 概率图模型概率图模型分类14.1 隐马尔可夫模型贝叶斯网络马尔科夫链隐马尔科夫模型 14.2 马尔可夫随机场( M R F MRF MRF)马尔可夫场定理算法原理概率推理参数学习算法对比 14.3 条件随机场( C R F CRF CRF)优缺点优点缺点 链式条…

Redis -LFU(Least Frequently Used,最少使用频率)缓存淘汰算法

在 Redis 的 LFU&#xff08;Least Frequently Used&#xff0c;最少使用频率&#xff09;缓存淘汰算法中&#xff0c;lru 字段被拆分成两部分&#xff1a;高 16 位存储 ldt&#xff08;Last Decrement Time&#xff09;&#xff0c;低 8 位存储 logc&#xff08;Logistic Coun…

【图像特效系列】卡通特效的实践 | 包含代码和效果图

目录 一 卡通特效 代码 效果图 图像特效系列主要是对输入的图像进行处理,生成指定特效效果的图片。图像素描特效会将图像的边界都凸显出来;图像怀旧特效是指图像经历岁月的昏暗效果;图像光照特效是指图像存在一个类似于灯光的光晕特效,图像像素值围绕光照中心点呈圆形范…

【就业】中国铁路人才招聘

中国国家铁路集团有限公司&#xff0c;是中国财政部直接出资的大型国有企业&#xff0c;与国资委直接管理的央企不同&#xff0c;它是由财政部代表国务院履行出资人职责 &#xff0c;由中央管理的国有独资公司。其前身是由国家铁道部政企分离后剥离出来的全民所有制企业&#x…

Docker 基础—— 数据卷

1.数据卷 容器是隔离环境&#xff0c;容器内程序的文件、配置、运行时产生的容器都在容器内部&#xff0c;我们要读写容器内的文件非常不方便&#xff0c;例如&#xff1a; 如果要升级MySQL版本&#xff0c;需要销毁旧容器&#xff0c;那么数据岂不是跟着被销毁了&#xff1f; …

idea安装二进制文本阅读插件

引言 在软件开发过程中&#xff0c;有时需要查看二进制文件的内容以调试或分析问题。虽然有许多专用工具可以处理这类任务&#xff0c;但直接在 IDE 内集成这些功能无疑更加方便高效。本文将介绍如何在 IntelliJ IDEA 2023中安装和配置一个名为 BinEd的插件&#xff0c;以及如…