一 背景:
我们都知道如果要在android上面使用rknn推理模型需要按照如下的步骤:
详细请参考笔者的文章:Android11-rk3566平台上采用NCNN,RKNN框架推理yolo11官方模型的具体步骤以及性能比较-CSDN博客
简而言之就是
- 模型转换:把模型文件转换成rknn格式
- 建立一个C++Native项目,在native层连接 librknnrt.so, 调用其中的API进行推理
那在openharmon4.0上面可行吗?根据实际的动手试验答案是:如果完全按照android上面的思路不可行,但是如果我们换一种思路的话,那就可行了。本文将给大家分享一下如何做到。
二 经典思路碰到的问题:
我们使用DevEco IDE(4.0)新建一个Native工程,在native(c++)端实现我们一个so,假设叫做rknnwrapper.so,由于我们要使用librknnrt.so这个库里面的函数,因此就需要在rknnwrapper.so的CMakeLists.txt文件中链接这个库,另外需要把librknnrt.so这个库拷贝到正确的libs目录下面。之后编译链接,一切顺利,于是信心满满的运行了程序,BUT,此时程序出现了异常:rknnwrapper.so加载失败。通过分析我们发现:
librknnrt.so加载失败了,进而导致rknnwrapper.so加载失败,但是为什么会失败呢?
于是我又把rknn-toolkit2-2.3.2里面的资料翻了一遍:功夫不负有心人,通过样例代码发现
Openharmony下面librknnrt.so是rknn-toolkit2-2.3.2\rknpu2\runtime\Linux目录下面的so,而样例代码rknn-toolkit2-2.3.2\rknpu2\examples\rknn_matmul_api_demo目录下面的build-linux.sh脚本里面有详细的交叉编译工具链信息:
所以我就想既然rknn_matmul_api_demo是用gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu这个生产的而且rknn_matmul_api_demo也链接了librknnrt.so,那我把gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/lib下面的所有的库和librknnrt.so一起拷贝到我工程的libs目录下不就行了吗。于是就试了一下,发现还是报同样的错误:librknnrt.so加载时失败了,于是我认为通过DevEco(4.0)开发的Native程序在启动的时候只会加载系统指定的运行时库,就算我把librknnrt.so和依赖的运行时库一起考到libs目录下面也没有任何用处。看来只能想其他办法了
三 如何解决:
通过上面的试验我们得出结论:
- librknnrt.so依赖的运行时库为一个版本,设为runtime1
- DevEco(4.0)编译出来的程序依赖的运行时库是 runtime2
- 鸿蒙系统(4.0)本身依赖的运行时库是runtime2
于是我想出了一个办法如下图:
思路:
1. 写一个服务进程,假设为rknn_server,使用gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu交叉编译工具链编译,其从系统启动的时候就一直常驻在后台,随时准备为客户端提供rknn推理服务
2. 写一个rknn_client.so 使用DevEco(4.0)开发,把librknnrt.so库中提供的接口根据需要暴露出来给给应用层使用,这样rknn_client.so就相当于librknnrt.so的代理
3. rknn_client.so和rknn_server间的进程间通信需要用到内存映射技术,因为推理输入张量和输出张量占用的内存大小往往很大,就拿输入张量做个例子,假设输入张量的格式为“NHWC int8" NHWC分别为 1,640,640,3,那么其占用的内存大小就是 640*640*3=1228800,差不多1.2M,为了保证性能最优,这么大级别的内存拷贝需要采用高性能的IPC技术,比如内存映射。除了传递数据,我们还需要一个通道用来发送命令(比如加载模型命令和推理命令),这时候套接字或者管道就可以用来做这个事情。
运行结果:
- 数据:
模型 | 单次推理耗时 | CPU idle | rknn_server CPU |
yolo11n | 300ms | 280% | 50% |
yolo11n_pose | 380ms | 280% | 50% |
yolo11s_pose | 580ms | 300% | 40% |
说明:
A:rk3566 是4核CPU(400%),NPU算力是0.8TOPS(int8)。
B:Demo的CPU消耗还包含摄像头采集、ISP算法、UI渲染和目标框的渲染,我们看下yolo11n下的top的数据(其他两种情况差不多):
读者可能会问,你的客户端进程CPU怎么这么低,主要的原因是作者把摄像头采集这块优化到了极致,大家可以参考作者的另外一篇文章:
Openharmony4.0摄像头采集+编码器+预览的优化_openharmony 相机预览-CSDN博客,里面摄像头方面的优化思路,上面三种情况下对象检测和姿态检测是在最高分辨率下的实时识别(3264x2448x15fps采集,识别的频率是同一时刻只能有一次识别在进行,如果摄像头采集的帧率大于识别的帧率就采用丢帧策略)
实际效果:
四 结尾:
Openharmony4.0的官方系统代码里面没有rknn驱动的支持,需要大家自行移植并且成功驱动rknpu,网上参考有不少,这里就不在深入说明了 :)
如果大家有更好的思路或者建议,欢迎评论区留言 :)