草庐IT

RKNN模型训练转换部署

wantao1008hh 2023-07-10 原文

1. 背景

做了一个手机版本的人流量计数,显然不能最终真实环境实施,因为数据集不够,并且硬件还需要搭载其他设备,所以使用的是安卓工控机,但推理速度非常慢,接近500ms,换一个好的CPU,比如3568工控机等,速度也是在150ms左右,但是发现了 Firefly相关文档瑞芯微 ,可以用NPU做计算,那开始吧。

2. 先快速体验一下NPU

ROC-RK3568-PC板子这是一款次旗舰级,3588性能会更高,目前使用的3568做的开发。

rknn-toolkit 主要是针对RK1808/RK1806/RV1109/RV1126,对应需要的库地址 rknpu所以这个忽略,使用下面的工具

rknn-toolkit2 主要是针对RK3566/RK3568/RK3588/RK3588S/RV1103/RV1106,对应需要的库地址 rknpu2

使用说明可以参考 NPU使用

  1. 下载rknpu2 ,将so库导入到对应的目录,这是为了简单的测试NPU功能,后面安卓使用,还是会放到项目的jnilib下
adb root && adb remount
adb push rknpu2_1.4.0/runtime/RK356X/Android/librknn_api/arm64-v8a/* /vendor/lib64
adb push rknpu2_1.4.0/runtime/RK356X/Android/librknn_api/arm64-v8a/* /vendor/lib
  1. 将 demo 放入 ROC-RK3568-PC ,运行 demo 如下
:/ # cd /data/rknn_ssd_demo_Android/    (Linux 系统使用 rknn_ssd_demo_Linux 即可)
:/data/rknn_ssd_demo_Android # chmod 777 rknn_ssd_demo
:/data/rknn_ssd_demo_Android # export LD_LIBRARY_PATH=./lib
:/data/rknn_ssd_demo_Android # ./rknn_ssd_demo model/RK356X/ssd_inception_v2.rknn model/road.bmp (Linux 为 bus.jpg)
Loading model ...
rknn_init ...
model input num: 1, output num: 2
input tensors:
  index=0, name=Preprocessor/sub:0, n_dims=4, dims=[1, 300, 300, 3], n_elems=270000, size=270000, fmt=NHWC, type=UINT8, qnt_type=AFFINE, zp=0, scale=0.007812
output tensors:
  index=0, name=concat:0, n_dims=4, dims=[1, 1917, 1, 4], n_elems=7668, size=30672, fmt=NHWC, type=FP32, qnt_type=AFFINE, zp=53, scale=0.089455
  index=1, name=concat_1:0, n_dims=4, dims=[1, 1917, 91, 1], n_elems=174447, size=697788, fmt=NHWC, type=FP32, qnt_type=AFFINE, zp=53, scale=0.143593
rknn_run
loadLabelName
ssd - loadLabelName ./model/coco_labels_list.txt
loadBoxPriors
person @ (13 125 59 212) 0.984696
person @ (110 119 152 197) 0.969119
bicycle @ (171 165 278 234) 0.969119
person @ (206 113 256 216) 0.964519
car @ (146 133 216 170) 0.959264
person @ (49 133 58 156) 0.606060
person @ (83 134 92 158) 0.606060
person @ (96 135 106 162) 0.464163

这就体验完成了。接下来模型转换环境搭建。

3. RKNN模型转换

  1. 下载rknn-toolkit2 ,
    RKNN-Toolkit2 目前版本适用系统Ubuntu,建议使用Docker, 我装的是双系统, 切换起来难受。必须是
    Ubuntu 18.04 python 3.6 / Ubuntu 20.04 python 3.8,只能选其一, 只能选其一, 只能选其一
  2. 装好系统后,安装需要的Python环境,以18.04为例
#Python3.6
cat doc/requirements_cp36-1.3.0.txt
numpy==1.16.6
onnx==1.7.0
onnxoptimizer==0.1.0
onnxruntime==1.6.0
tensorflow==1.14.0
tensorboard==1.14.0
protobuf==3.12.0
torch==1.6.0
torchvision==0.7.0
psutil==5.6.2
ruamel.yaml==0.15.81
scipy==1.2.1
tqdm==4.27.0
requests==2.21.0
opencv-python==4.4.0.46
PuLP==2.4
scikit_image==0.17.2
# if install bfloat16 failed, please install numpy manually first. "pip install numpy==1.16.6"
bfloat16==1.1
flatbuffers==1.12
  1. 建议使用 virtualenv 管理 Python 环境,因为系统中可能同时有多个版本的 Python 环境,以 Python3.6 为例
# 1)安装virtualenv 环境、Python3.6 和 pip3
sudo apt-get install virtualenv \
sudo apt-get install python3 python3-dev python3-pip
# 2)安装相关依赖
sudo apt-get install libxslt1-dev zlib1g zlib1g-dev libglib2.0-0 libsm6 \
libgl1-mesa-glx libprotobuf-dev gcc
# 3)使用 virtualenv 管理 Python 环境并安装 Python 依赖,Python3.6用requirements_cp36-1.3.0.txt
virtualenv -p /usr/bin/python3 venv
source venv/bin/activate
pip3 install -r doc/requirements_cp36-*.txt
# 4)安装 RKNN-Toolkit2,如rknn_toolkit2-1.3.0_11912b58-cp36-cp36m-linux_x86_64.whl
sudo pip3 install packages/rknn_toolkit2*cp36*.whl
# 5)检查RKNN-Toolkit2是否安装成功,可按ctrl+d组合键退出
(venv) firefly@T-chip:~/rknn-toolkit2$ python3
>>> from rknn.api import RKNN
>>>
  1. 我使用的是yolov5模型, 不使用rknn-toolkit2进行转换,yolo模型使用官方推荐的rknn_model_zoo , 使用 airockchip 修改后的 yolov5/ yolov7/ YOLOX 仓库进行模型训练,并导出对应的onnx,这样在输入输出时与Demo能保持一致。如下图所示,左边是支持的转换模型结构,右边是yolov5官方的,我们需要得到左边的这种,而不是右边的。除非你很了解模型结构,并能够正确处理输出结果


  2. 使用 airockchip 修改后的 yolov5 模型训练, 人头数据集 ,权重也可以使用他提供的yolov5s_relu.pt
  3. 导出onnx模型
导出模型时 python export.py --rknpu {rk_platform} 即可导出优化模型
(rk_platform支持 rk1808, rv1109, rv1126, rk3399pro, rk3566, rk3568, rk3588, rv1103, rv1106)
  1. 模型转换参考rknn_model_zoo/models/CV/object_detection/yolo/RKNN_model_convert
  2. 推理测试参考rknn_model_zoo/models/CV/object_detection/yolo/RKNN_python_demo

4. 安卓上使用RKNN模型

方式 1. 使用rknn_yolov5_android_apk_demo , 图像输入时,数据的处理缺少对应的rga库,我这个板子底层驱动应该被联想改过,想让他们集成,说要加钱,没弄了。

方式 2. 使用RK356X NPU Demo 对比上面的Demo,图像的输入只用direct_texture处理的data,只需要修改一下后处理函数,参照rknn_yolo_demo ,可以看出并没有使用sigmoid处理 。

static int process_i8(int8_t *input, int *anchor, int grid_h, int grid_w, int height,
           int width, int stride,
           std::vector<float> &boxes, std::vector<float> &boxScores, std::vector<int> &classId,
           float threshold, int32_t zp, float scale) {
    int validCount = 0;
    int grid_len = grid_h * grid_w;
    float thres = threshold;
    auto thres_i8 = qnt_f32_to_affine(thres, zp, scale);
    // puts("==================================");
    // printf("threash %f\n", thres);
    // printf("thres_i8 %u\n", thres_i8);
    // printf("scale %f\n", scale);
    // printf("zp %d\n", zp);
    // puts("==================================");

    //printf("it goes here: file %s, at line %d\n", __FILE__, __LINE__);
    for (int a = 0; a < 3; a++) {
        for (int i = 0; i < grid_h; i++) {

            for (int j = 0; j < grid_w; j++) {

                int8_t box_confidence = input[(YOLOV5_PROP_BOX_SIZE * a + 4) * grid_len +
                                              i * grid_w + j];
                //printf("The box confidence in i8: %d\n", box_confidence);
                if (box_confidence >= thres_i8) {
                    // printf("box_conf %u, thres_i8 %u\n", box_confidence, thres_i8);
                    int offset = (YOLOV5_PROP_BOX_SIZE * a) * grid_len + i * grid_w + j;
                    int8_t *in_ptr = input + offset;

                    int8_t maxClassProbs = in_ptr[5 * grid_len];
                    int maxClassId = 0;
                    for (int k = 1; k < YOLOV5_OBJ_CLASS_NUM; ++k) {
                        int8_t prob = in_ptr[(5 + k) * grid_len];
                        if (prob > maxClassProbs) {
                            maxClassId = k;
                            maxClassProbs = prob;
                        }
                    }

                    float box_conf_f32 = deqnt_affine_to_f32(box_confidence, zp, scale);
                    float class_prob_f32 = deqnt_affine_to_f32(maxClassProbs, zp, scale);
                    float limit_score = box_conf_f32 * class_prob_f32;
//                    LOGI("limit score: %f\n", limit_score);
                    if (limit_score > YOLOV5_CONF_THRESHOLD) {
                        float box_x, box_y, box_w, box_h;

                        box_x = deqnt_affine_to_f32(*in_ptr, zp, scale) * 2.0 - 0.5;
                        box_y = deqnt_affine_to_f32(in_ptr[grid_len], zp, scale) * 2.0 - 0.5;
                        box_w = deqnt_affine_to_f32(in_ptr[2 * grid_len], zp, scale) * 2.0;
                        box_h = deqnt_affine_to_f32(in_ptr[3 * grid_len], zp, scale) * 2.0;
                        box_w = box_w * box_w;
                        box_h = box_h * box_h;

                        box_x = (box_x + j) * (float) stride;
                        box_y = (box_y + i) * (float) stride;
                        box_w *= (float) anchor[a * 2];
                        box_h *= (float) anchor[a * 2 + 1];
                        box_x -= (box_w / 2.0);
                        box_y -= (box_h / 2.0);

                        boxes.push_back(box_x);
                        boxes.push_back(box_y);
                        boxes.push_back(box_w);
                        boxes.push_back(box_h);
                        boxScores.push_back(box_conf_f32 * class_prob_f32);
                        classId.push_back(maxClassId);
                        validCount++;
                    }
                }
            }
        }
    }
    return validCount;
}

遇到的问题

  1. rknn-toolkit2/examples/onnx/yolov5/ 使用这个转换,也是可以的,但是YOLO模型最好使用rknn_model_zoo 进行转换,因为包含了训练-导出-转换-测试一系列套餐。
  2. 推理结果出现花屏问题,是sigmoid函数造成的,因为模型结果中没有sigmoid的处理,而你的测试方法用到了,可以查看上面的第8步的,去掉相关函数。
  3. 连扳进行模型转换时,需要启动rknn-server,具体操作参考rknpu2/rknn_server_proxy
  4. 尝试直接将摄像的数据,转成bitmap,转cv Mat,作为输入,但是没有效果,失败了。

总结

凡事都试一试

其他的一些AI库。
TensorflowLite
腾讯TNN
小米Mace
阿里MNN
计算机视觉学习视频,极力推荐北京邮电大学鲁鹏讲的
计算机视觉与深度学习

有关RKNN模型训练转换部署的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  4. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  6. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  7. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  8. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  9. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  10. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

随机推荐