公司买了块瑞芯微的移动开发板,准备将公司的主营业务的AI
模型,从服务器主机,移动到开发板上面。所以,就选择了瑞芯微的RK3588
的板子。
从目前市面上出现的板子来看,主要的还是以瑞芯微的板子为主,比如鸣辰1号等等。接着,也就有了我学习的内容。
这里将我学习的过程记录和分享到这里,一是帮助新手快速入手,二是帮助自己进行记录,以便在我忘记之前如何弄的时候,及时的可以快速查看。参考资料主要是来自于官方给的文档,还有网上热心网友的分享,在此感谢。
其中,RK3588的主要参考链接集合到这里,如下:
- ROC-RK3588S-PC网页教程:https://wiki.t-firefly.com/zh_CN/ROC-RK3588S-PC/index.html
- Core-3588J资料下载:https://www.t-firefly.com/doc/download/161.html
- 技术文档:https://wiki.t-firefly.com/zh_CN/ROC-RK3588S-PC/index.html
- 官方论坛首页:https://dev.t-firefly.com/portal.php?mod=topic&topicid=11
- 发邮件,请教官方问题
下面的内容,也是基于上述资料进行展开的,只是对自己感兴趣和遇到的一些问题做了些记录,整体上是大差不差。
一、RK3588基本内容
板子的相关资料,官方和网络开放的一些资料,对其中对了很详尽的描述和介绍,我在这里就不做进一步的赘述了。贴一个功能和输入、输出接口的官方介绍如下图这样:
下文主要参照文档:RKNN SDK 快速上手指南
,这个文件是官方的文档,自行查找即可,建议直接参照这个文档,会描述的比较详尽,接近于手把手教学。配合网页教程,会更佳。
RKNN SDK的下载地方,包括了NPU Demo和配置所需要的文件。有1.3.0
和1.2.0
两个版本,我选择的是1.3.0
的版本进行实验。文档包括:
- RKNN Toolkit2 快速上手指南(
Rockchip_Quick_Start_RKNN_Toolkit2_CN-1.3.0.pdf
) - RKNN-Toolkit2 用户使用指南(
Rockchip_User_Guide_RKNN_Toolkit2_CN-1.3.0.pdf
)
都存储在:RK_NPU_SDK_1.3.0\rknn-toolkit2-1.3.0\doc
文件夹下,这两个文档比较重要,能够帮助我们快速部署和快速转换自己的模型,后面会主要用到这两个文档。
二、入门上手
2.1、安装ADB
ADB的安装与使用,也可以参考这里:ADB 使用
2.1.1、ADB是个什么东西?
ADB全称Android Debug Bridge
,中文翻安卓调试桥
。专业术语就不讲解释了,简单来说就是可以通过这个命令用电脑控制手机,或者是其他的安卓设备,比如这个板子,就是安卓的系统。
所以,为了控制这个板子,就要在自己电脑上面,搞个ADB
,有了这个东西,才能与自己的板子通信,给他发指令,控制他去干活。下面就按照官方教程,在自己的电脑上,进行安装吧。
拿到时候,我也没搞明白,为啥不直接把板子脸上屏蔽,直接操作。刷Linux系统是可以的,我这里还是用出厂自带的安卓系统,就通过这个安卓调试桥,用PC作为媒介,给板子发命令和传送和查看板子内的文件
adb常用命令如下,这个也是后面常用到的命令:
adb root 以root权限重启adb服务
adb reboot 重启设备
adb kill-server 终止adb服务进程
adb remount 将system分区重新挂载为可读写分区
adb push <local> <remote> 将本地文件复制到设备
adb pull <remote> <local> 从设备复制文件到本地
adb shell 进入板子系统
更多adb命令,参考这里:adb命令大全
2.1.2、window下安装ADB
就按照他说的来,先去下载驱动,把驱动安装好。然后再来下载这个adb.zip,解压后进行安装。没有遇到什么问题,测试发现安装成功,就完事了。
我也没搞明白,为啥去用window去首先做了,可能就是因为不了解,先按教程说的做得了。
这块内容有很详尽的介绍,并且我也是参照这块做的,没有发现什么问题,就不做过多解释了。
下面是在 ROC-RK3588S-PC
上运行demo
的展示结果:
在ROC-RK3588S-PC网页教程
的NPU使用
一节的步骤,可以实现对官方demo的复现,打印结果如下:(根据你自己运行的demo的不同,打印的内容也不同)
这是rknn_ssd_demo
,也就是目标检测算法SSD的RKNN的版本实现方法。其中
- 加载的模型是.rknn
- rknn_ssd_demo是经过编译后的c语言版本,速度有所提升
在下一节中,主要就是仿照官方的方法:
- 将自己pytorch训练的yolov5的模型,给转成.rknn版本
- 生成yolov5的c语言版本,进行加速
2.2、更新板子的 rknn_server 和 librknnrt.so
正常输出运行结果:
2.3、创建和激活的rknn虚拟环境
前面的环境按照,都是为了这一刻,能够激活环境,进行操作。进入激活的python环境,需要这样操作:
root@zyyl-Lenovo:~# adb devices
List of devices attached
c21389c12d9b5c0b device
root@zyyl-Lenovo:~# adb root && adb remount
adbd is already running as root
remount succeeded
root@zyyl-Lenovo:~# virtualenv -p /usr/bin/python3 venv # 选择一个python解释器来创建虚拟化环境
created virtual environment CPython3.8.10.final.0-64 in 749ms
creator CPython3Posix(dest=/root/venv, clear=False, global=False)
seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, pkg_resources=latest, via=copy, app_data_dir=/root/.local/share/virtualenv/seed-app-data/v1.0.1.debian.1)
activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
root@zyyl-Lenovo:~# source venv/bin/activate # 激活虚拟化环境
(venv) root@zyyl-Lenovo:~#
root@zyyl-Lenovo:~# deactive # 退出虚拟化环境
虚拟环境的部分,更多可参考这里:virtualenv介绍及基本使用
创建虚拟环境的一些记录:假设我在某个位置创建了一个虚拟环境,那么就会创建一个 venv
的文件夹,如下所示:
那么,我在下次采用source venv/bin/activate
激活虚拟环境的时候,就需要在文件夹下面进行操作。
下面是运行官方example
时候,打印的结果。还好官方已经给出了yolov5
的案例,我们就可以先参照这里,对自己的模型,进行转换了。
三、模型转换
下面以paddlepaddle OCR
任务为例,简单的给个模型转换的过程和代码,如下
3.1、onnx 2 rknn
import cv2
from rknn.api import RKNN
import numpy as np
import onnxruntime as ort
"""
/home/hkl/project/ISSUES/RKNN_SDK/RK_NPU_SDK_1.3.0/rknpu2_1.3.0/examples/rknn_paddlle_ocr
"""
onnx_model = 'onnx_github/repvgg_s.onnx' #onnx路径
save_rknn_dir = 'onnx_github/repvgg_s.rknn'#rknn保存路径
def norm(img):
mean = 0.5
std = 0.5
img_data = (img.astype(np.float32)/255 - mean) / std
return img_data
if __name__ == '__main__':
# Create RKNN object
rknn = RKNN(verbose=True)
# image = np.random.randn(1,3,32,448).astype(np.float32) # 创建一个np数组,分别用onnx和rknn推理看转换后的输出差异,检测模型输入是1,3,640,640 ,识别模型输入是1,3,32,448
image = cv2.imread('./imgs_data_rec/14.jpg')
image = cv2.resize(image, (448, 32))
image = np.expand_dims(image, axis=0) # 添加批次维度
image = np.transpose(image, (0, 3, 1, 2)) # 重新排列通道维度
onnx_net = ort.InferenceSession(onnx_model) # onnx推理
onnx_infer = onnx_net.run(None, {'input': norm(image)}) # 如果是paddle2onnx转出来的模型输入名字默认是 "x"
# pre-process config
print('--> Config model')
rknn.config(mean_values=[[127.5, 127.5, 127.5]], std_values=[[127.5, 127.5, 127.5]],
target_platform='rk3588') # 需要输入为RGB#####需要转化一下均值和归一化的值
print('done')
# Load ONNX model
print('--> Loading model %s' % onnx_model)
ret = rknn.load_onnx(model=onnx_model)
if ret != 0:
print('Load %s failed!' % onnx_model)
exit(ret)
print('done')
# Build model
print('--> Building model')
# rknn.build(do_quantization=False)
ret = rknn.build(do_quantization=True, dataset='datasets_rec.txt')
#do_quantization是否对模型进行量化,datase量化校正数据集,pre_compil模型预编译开关,预编译 RKNN 模型可以减少模型初始化时间,但是无法通过模拟器进行推理或性能评估
if ret != 0:
print('Build net failed!')
exit(ret)
print('done')
# Export RKNN model
print('--> Export RKNN model')
ret = rknn.export_rknn(save_rknn_dir)
if ret != 0:
print('Export rknn failed!')
exit(ret)
ret = rknn.init_runtime(target='rk3588', device_id="1f2bb12b381c00c1") # 两个参数分别是板子型号和device_id,device_id在双头usb线连接后通过 adb devices查看
if ret != 0:
print('init runtime failed.')
exit(ret)
print('done')
# Inference
print('--> Running model')
outputs = rknn.inference(inputs=[image])
# perf
print('--> Begin evaluate model performance')
perf_results = rknn.eval_perf(inputs=[image]) # 模型评估
print('done')
print()
print("->>模型前向对比!")
print("--------------rknn outputs--------------------")
print(outputs[0])
print()
print("--------------onnx outputs--------------------")
print(onnx_infer[0])
print()
std = np.std(outputs[0]-onnx_infer[0])
print(std) # 如果这个值比较大的话,说明模型转换后不太理想
rknn.release()
参考链接:【工程部署】手把手教你在RKNN上部署OCR服务(上)
3.2、 rknn predict
这里的预测部分,是以paddle OCR
官方给出的案例。对于瑞芯微官方的文档里面,也有好几个案例可供参考,其中提供了python
的版本和C++
的版本,建议去那里看看。
import numpy as np
import cv2
from rknn.api import RKNN
import torch
# from label_convert import CTCLabelConverter
class CTCLabelConverter(object):
""" Convert between text-label and text-index """
def __init__(self, character):
# character (str): set of the possible characters.
dict_character = []
with open(character, "rb") as fin:
lines = fin.readlines()
for line in lines:
line = line.decode('utf-8').strip("\n").strip("\r\n")
dict_character += list(line)
# dict_character = list(character)
self.dict = {}
for i, char in enumerate(dict_character):
# NOTE: 0 is reserved for 'blank' token required by CTCLoss
self.dict[char] = i + 1
#TODO replace ‘ ’ with special symbol
self.character = ['[blank]'] + dict_character+[' '] # dummy '[blank]' token for CTCLoss (index 0)
def encode(self, text, batch_max_length=None):
"""convert text-label into text-index.
input:
text: text labels of each image. [batch_size]
output:
text: concatenated text index for CTCLoss.
[sum(text_lengths)] = [text_index_0 + text_index_1 + ... + text_index_(n - 1)]
length: length of each text. [batch_size]
"""
length = [len(s) for s in text]
# text = ''.join(text)
# text = [self.dict[char] for char in text]
d = []
batch_max_length = max(length)
for s in text:
t = [self.dict[char] for char in s]
t.extend([0] * (batch_max_length - len(s)))
d.append(t)
return (torch.tensor(d, dtype=torch.long), torch.tensor(length, dtype=torch.long))
def decode(self, preds, raw=False):
""" convert text-index into text-label. """
preds_idx = preds.argmax(axis=2)
preds_prob = preds.max(axis=2)
result_list = []
for word, prob in zip(preds_idx, preds_prob):
if raw:
result_list.append((''.join([self.character[int(i)] for i in word]), prob))
else:
result = []
conf = []
for i, index in enumerate(word):
if word[i] != 0 and (not (i > 0 and word[i - 1] == word[i])):
result.append(self.character[int(index)])
conf.append(prob[i])
result_list.append((''.join(result), conf))
return result_list
def narrow_224_32(image, expected_size=(224,32)):
ih, iw = image.shape[0:2]
ew, eh = expected_size
scale = eh / ih
# scale = eh / max(iw,ih)
nh = int(ih * scale)
nw = int(iw * scale)
image = cv2.resize(image, (nw, nh), interpolation=cv2.INTER_CUBIC)
top = 0
bottom = eh - nh - top
left = 0
right = ew - nw - left
new_img = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
return new_img
if __name__ == '__main__':
dict_path = r"./onnx_github/dict_text.txt"
converter = CTCLabelConverter(dict_path)
# Create RKNN object
rknn = RKNN()
ret = rknn.load_rknn('onnx_github/rec.rknn')
# Set inputs
img = cv2.imread('imgs_data_rec/21.jpg')
# img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# origin_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
image = narrow_224_32(img, expected_size=(448, 32))
# init runtime environment
print('--> Init runtime environment')
ret = rknn.init_runtime(target='rk3588',device_id="1f2bb12b381c00c1")
if ret != 0:
print('Init runtime environment failed')
exit(ret)
print('done')
# Inference
print('--> Running model')
outputs = rknn.inference(inputs=[image])
# perf
print('--> Begin evaluate model performance')
perf_results = rknn.eval_perf(inputs=[image])
print('done')
feat_2 = torch.from_numpy(outputs[0])
print(feat_2.size())
txt = converter.decode(feat_2.detach().cpu().numpy())
print(txt)
cv2.imshow("img",img)
cv2.waitKey()
rknn.release()
四、总结
关于瑞芯微RK3588
芯片的第一篇文章,主要就是记录了一些基本的环境按照,Linux
系统与板子的联调,以及转换模型,是现在虚拟环境,和板端的运行。
第一阶段肯定还是先实操瑞芯微官方给的一些案例,这样出现了问题,可以及时的找到解决方法。下一篇文章就是根据官方给的yolo v5
的案例,完整的记录模型转换中的步骤,和遇到的问题。
最后,如果您觉得本篇文章对你有帮助,欢迎点赞 👍,让更多人看到,这是对我继续写下去的鼓励。本系列文章都不会设定 VIP
和付费,如果能再点击下方的红包打赏,给博主来一杯咖啡,那就太好了。💪