ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3576 NPU部署MobileNet全流程实战指南

RK3576 NPU部署MobileNet全流程实战指南 1. 为什么要在RK3576上折腾MobileNet部署RK3576这颗芯片最近在边缘计算圈子里热度不低四核A72加四核A53的CPU架构配上6TOPS算力的NPU用来跑轻量级视觉模型非常合适。MobileNet作为移动端视觉模型的经典代表参数量小、计算量低和RK3576的NPU搭配起来做实时分类、目标检测的骨干网络都很稳。但问题在于很多人拿到开发板之后卡在了从零部署这一步——模型怎么转换、NPU怎么调用、交叉编译环境怎么搭每一步都有坑。这篇内容就是把我自己在RK3576上从零跑通MobileNet的完整过程整理出来。从环境准备、模型转换、交叉编译到板端推理每一步都有可复现的命令和代码。适合手里有RK3576开发板、想跑NPU推理但不知道从哪下手的嵌入式工程师也适合做边缘AI产品选型、需要评估RK3576实际推理性能的开发者。读完你至少能拿到一套能直接跑的MobileNet部署方案以及踩坑之后总结出来的排查思路。整个流程的核心工具链是RKNN-Toolkit2这是瑞芯微官方提供的模型转换和推理SDK。PC端负责模型转换和量化板端负责实际推理。中间涉及交叉编译的部分主要是把推理程序编译成板子能执行的二进制文件。下面按实际操作的顺序展开每一步我都会说清楚为什么这么做以及不做会出什么问题。2. 环境准备与工具链选型2.1 PC端环境搭建Ubuntu版本和Python依赖的坑RKNN-Toolkit2对PC端环境有比较明确的要求。我实测下来Ubuntu 20.04和22.04都能跑但推荐用20.04因为官方wheel包对Python版本的支持在3.8到3.10之间最稳。如果你用Ubuntu 22.04自带的Python 3.10问题不大但要注意pip源和依赖版本。安装步骤不复杂但有几个细节容易翻车。首先创建一个独立的虚拟环境别在系统Python里直接装不然后面依赖冲突很难排查python3 -m venv rknn_env source rknn_env/bin/activate pip install --upgrade pip然后去瑞芯微的GitHub仓库下载RKNN-Toolkit2的wheel包。注意版本要和板端NPU驱动匹配我用的版本是1.6.0对应NPU驱动版本0.9.6以上。安装命令pip install rknn_toolkit2-1.6.0-cp310-cp310-linux_x86_64.whl装完之后验证一下python3 -c from rknn.api import RKNN; print(RKNN Toolkit2 loaded)如果报错说找不到librknnrt.so说明运行时库没装。这个库在wheel包里其实已经带了但有时候路径不对手动指定一下export LD_LIBRARY_PATH$LD_LIBRARY_PATH:$(python3 -c import rknn; print(rknn.__path__[0]))/libs注意RKNN-Toolkit2的版本和板端NPU驱动版本必须匹配。我一开始用1.5.0的Toolkit配0.9.8的驱动转换出来的模型在板端加载直接报错换成1.6.0之后问题消失。版本对应关系在官方文档的Release Note里有表格部署前一定先确认。2.2 板端NPU驱动确认与系统检查板子拿到手之后先确认NPU驱动是否正常加载。RK3576的NPU驱动在Linux内核里是独立模块Android系统下默认已经集成。检查命令cat /sys/kernel/debug/rknpu/version正常输出类似RKNPU driver version: 0.9.6如果没有这个节点说明驱动没加载或者内核配置没开。Ubuntu系统下可能需要手动insmodAndroid系统一般不用管。另外确认一下板子的系统版本RK3576支持Android 14和Ubuntu 22.04两种主要系统我这次用的是Ubuntu 22.04因为交叉编译和调试更方便。还有一个容易忽略的点板端的librknnrt.so版本。这个库在板子的/usr/lib/目录下版本要和PC端Toolkit匹配。检查ls -l /usr/lib/librknnrt.so如果版本不一致把PC端Toolkit里带的librknnrt.so推到板子上替换掉。替换之前备份原文件万一出问题还能恢复。2.3 交叉编译工具链的选择与安装交叉编译工具链的选择取决于板子的系统。Ubuntu系统用aarch64-linux-gnu工具链Android系统用NDK。我这次用Ubuntu所以选aarch64-linux-gnu。安装命令sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu验证aarch64-linux-gnu-gcc --version输出应该是gcc 9.4.0或更高版本。如果版本太低编译C17代码可能会报错建议用Ubuntu 20.04自带的9.4.0版本够用。提示交叉编译工具链的版本要和板端glibc版本兼容。板端Ubuntu 22.04的glibc是2.35PC端工具链的glibc不能高于这个版本否则编译出来的程序在板子上跑不起来。用aarch64-linux-gnu-gcc -print-file-namelibc.so.6可以查看工具链的glibc版本。3. MobileNet模型转换与量化实操3.1 模型来源与格式选择MobileNet的模型来源有几个选择从TensorFlow官方模型库下载预训练的pb文件或者从PyTorch的torchvision里导出onnx。我推荐用onnx格式因为RKNN-Toolkit2对onnx的支持最成熟转换过程中的算子兼容性问题最少。从torchvision导出onnx的代码import torch import torchvision.models as models model models.mobilenet_v2(pretrainedTrue) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, mobilenet_v2.onnx, input_names[input], output_names[output], opset_version11)opset_version选11别选太高的版本RKNN-Toolkit2对opset 11的支持最稳定。导出之后用onnxsim简化一下模型去掉多余的算子pip install onnxsim onnxsim mobilenet_v2.onnx mobilenet_v2_sim.onnx简化之后的模型转换成功率更高而且推理速度也会略有提升。3.2 RKNN模型转换脚本详解转换脚本是整个流程的核心我直接贴完整代码然后逐段解释from rknn.api import RKNN rknn RKNN(verboseTrue) # 配置模型预处理参数 rknn.config( mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], target_platformrk3576, quantized_dtypeasymmetric_quantized-8, optimization_level3 ) # 加载onnx模型 ret rknn.load_onnx(modelmobilenet_v2_sim.onnx) if ret ! 0: print(Load model failed) exit(ret) # 构建RKNN模型 ret rknn.build(do_quantizationTrue, dataset./dataset.txt) if ret ! 0: print(Build model failed) exit(ret) # 导出RKNN模型 ret rknn.export_rknn(./mobilenet_v2.rknn) if ret ! 0: print(Export model failed) exit(ret) rknn.release()mean_values和std_values是ImageNet的标准预处理参数MobileNet训练时用的就是这个。如果你用的模型预处理不一样这里要对应修改否则量化后的精度会掉得很厉害。target_platform填rk3576这个参数决定了Toolkit在转换时针对哪个NPU架构做优化。填错的话模型能转出来但板端加载可能失败。quantized_dtype选asymmetric_quantized-8这是8位非对称量化精度和速度的平衡最好。如果对精度要求极高可以用float16但推理速度会慢一倍左右。dataset.txt是量化校准数据集列表里面每行是一个图片路径。校准数据集的数量建议在100到200张之间太少量化误差大太多转换时间太长。图片要是模型实际推理时遇到的同类数据比如你做的是活体检测校准集就用活体检测的图片别用ImageNet的通用图片。3.3 量化精度调优与常见报错处理量化之后精度掉点是常态关键看掉多少。我实测MobileNet V2在ImageNet上的Top-1精度从71.8%掉到70.5%左右掉1.3个百分点可以接受。如果掉超过3个百分点就要检查校准集和预处理参数。常见报错一E build: The model has unsupported op: xxx。这说明onnx里有RKNN不支持的算子。解决办法是用onnxsim简化或者手动替换算子。MobileNet里常见的HardSwish在opset 11里是支持的但如果你的模型用了自定义算子就要改模型结构。常见报错二E quantize: The dataset is too small。校准集数量不够至少加到100张。常见报错三W build: The model has multi-input, please check。多输入模型转换时要注意输入顺序RKNN-Toolkit2对多输入的支持有限建议改成单输入。实操心得量化校准集一定要用真实场景的数据。我一开始偷懒用了ImageNet的验证集结果量化后在板端跑实际场景的图片精度掉了5个百分点。换成实际场景的200张图片重新量化精度恢复到只掉1.2个百分点。这个坑花了我两天时间排查。4. 板端推理程序开发与交叉编译4.1 推理程序框架设计板端推理程序的核心逻辑是加载rknn模型、初始化NPU上下文、读取图片、预处理、推理、后处理。我用C写因为性能比Python好而且交叉编译之后部署方便。程序框架分三个文件main.cpp负责流程控制preprocess.cpp负责图片预处理postprocess.cpp负责结果解析。先看main.cpp的核心部分#include stdio.h #include stdlib.h #include string.h #include rknn_api.h #include opencv2/opencv.hpp int main(int argc, char** argv) { if (argc ! 3) { printf(Usage: %s model_path image_path\n, argv[0]); return -1; } // 读取模型文件 FILE* fp fopen(argv[1], rb); fseek(fp, 0, SEEK_END); int model_size ftell(fp); fseek(fp, 0, SEEK_SET); unsigned char* model_data (unsigned char*)malloc(model_size); fread(model_data, 1, model_size, fp); fclose(fp); // 初始化RKNN上下文 rknn_context ctx; int ret rknn_init(ctx, model_data, model_size, 0, NULL); if (ret 0) { printf(rknn_init failed: %d\n, ret); return -1; } // 查询输入输出属性 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(io_num)); printf(input num: %d, output num: %d\n, io_num.n_input, io_num.n_output); // 读取图片并预处理 cv::Mat img cv::imread(argv[2]); cv::Mat resized; cv::resize(img, resized, cv::Size(224, 224)); cv::Mat rgb; cv::cvtColor(resized, rgb, cv::COLOR_BGR2RGB); // 设置输入 rknn_input inputs[1]; memset(inputs, 0, sizeof(inputs)); inputs[0].index 0; inputs[0].type RKNN_TENSOR_UINT8; inputs[0].size 224 * 224 * 3; inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].buf rgb.data; ret rknn_inputs_set(ctx, 1, inputs); if (ret 0) { printf(rknn_inputs_set failed: %d\n, ret); return -1; } // 推理 ret rknn_run(ctx, NULL); if (ret 0) { printf(rknn_run failed: %d\n, ret); return -1; } // 获取输出 rknn_output outputs[1]; memset(outputs, 0, sizeof(outputs)); outputs[0].want_float 1; ret rknn_outputs_get(ctx, 1, outputs, NULL); if (ret 0) { printf(rknn_outputs_get failed: %d\n, ret); return -1; } // 后处理找最大概率的类别 float* output_data (float*)outputs[0].buf; int max_idx 0; float max_val output_data[0]; for (int i 1; i 1000; i) { if (output_data[i] max_val) { max_val output_data[i]; max_idx i; } } printf(Predicted class: %d, confidence: %.2f\n, max_idx, max_val); // 释放资源 rknn_outputs_release(ctx, 1, outputs); rknn_destroy(ctx); free(model_data); return 0; }这段代码里几个关键点rknn_init的第四个参数是flag填0表示默认模式。rknn_inputs_set的输入格式用NHWC因为OpenCV读出来的图片是HWC排列转成NHWC正好匹配。want_float设为1输出就是float类型方便后处理。4.2 交叉编译Makefile编写交叉编译的Makefile要指定工具链、头文件路径和库路径。RKNN的SDK里带了交叉编译需要的头文件和库在rknpu2/runtime/Linux/librknn_api/目录下。Makefile内容CXX aarch64-linux-gnu-g TARGET mobilenet_demo SRCS main.cpp OBJS $(SRCS:.cpp.o) RKNN_API_PATH ./rknpu2/runtime/Linux/librknn_api OPENCV_PATH /usr/aarch64-linux-gnu CXXFLAGS -stdc11 -O2 -I$(RKNN_API_PATH)/include -I$(OPENCV_PATH)/include/opencv4 LDFLAGS -L$(RKNN_API_PATH)/aarch64 -lrknnrt -L$(OPENCV_PATH)/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs $(TARGET): $(OBJS) $(CXX) -o $ $^ $(LDFLAGS) %.o: %.cpp $(CXX) $(CXXFLAGS) -c $ -o $ clean: rm -f $(OBJS) $(TARGET)编译命令make编译成功后会生成mobilenet_demo可执行文件。用file命令检查一下架构file mobilenet_demo输出应该是ELF 64-bit LSB executable, ARM aarch64说明交叉编译成功。注意OpenCV的交叉编译版本要提前准备好。如果板子上已经有OpenCV的so文件可以直接用板端的库但头文件要用PC端的。我试过用PC端的x86 OpenCV库去链接编译能过但板子上跑不起来报架构不匹配。所以LDFLAGS里的库路径一定要指向aarch64版本的库。4.3 板端部署与运行验证把编译好的可执行文件、rknn模型文件和测试图片推到板子上adb push mobilenet_demo /data/local/tmp/ adb push mobilenet_v2.rknn /data/local/tmp/ adb push test.jpg /data/local/tmp/如果板子跑的是Ubuntu系统用scp或者U盘拷贝也行。推完之后给可执行权限adb shell chmod x /data/local/tmp/mobilenet_demo运行adb shell /data/local/tmp/mobilenet_demo /data/local/tmp/mobilenet_v2.rknn /data/local/tmp/test.jpg正常输出input num: 1, output num: 1 Predicted class: 281, confidence: 0.87class 281对应的是tabby cat说明推理结果正确。如果报错librknnrt.so not found把PC端Toolkit里的librknnrt.so推到板子的/usr/lib/目录下adb push librknnrt.so /usr/lib/ adb shell ldconfig如果报错rknn_init failed: -1大概率是模型版本和驱动版本不匹配检查Toolkit版本和板端驱动版本是否对应。5. 性能调优与常见问题排查5.1 推理耗时分析与优化方向MobileNet V2在RK3576 NPU上的单帧推理耗时我实测下来在8到12毫秒之间取决于NPU频率和模型量化方式。用rknn_query查询耗时rknn_perf_detail perf_detail; rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, perf_detail, sizeof(perf_detail)); printf(Total time: %f ms\n, perf_detail.total_time);如果耗时超过15毫秒可以从几个方向优化。第一检查NPU频率是否跑满用cat /sys/class/devfreq/fdab0000.npu/cur_freq查看当前频率如果是低频率状态手动设成高性能模式echo performance /sys/class/devfreq/fdab0000.npu/governor第二检查模型是否用了float16量化float16比int8慢一倍左右如果精度允许就换成int8。第三检查输入图片的预处理是否在CPU上做如果预处理耗时占比高可以考虑用NPU做预处理但RKNN-Toolkit2对预处理的支持有限一般还是CPU做。5.2 常见问题速查表问题现象可能原因解决方法rknn_init failed: -1模型版本与驱动不匹配检查Toolkit版本和板端驱动版本确保对应rknn_init failed: -2模型文件损坏重新导出rknn模型检查文件大小是否正常rknn_run failed: -1输入数据格式不对检查inputs的type和fmt是否与模型匹配推理结果全为0量化校准集不对用真实场景数据重新量化精度掉超过3%预处理参数不匹配检查mean_values和std_values是否与训练时一致板端运行报架构错误交叉编译工具链不对用aarch64-linux-gnu-g重新编译librknnrt.so not found板端缺少运行时库从PC端Toolkit拷贝librknnrt.so到板端/usr/lib/NPU频率上不去governor模式不对设置governor为performance5.3 独家避坑经验分享第一个坑量化校准集的图片格式。RKNN-Toolkit2读校准集图片时默认用OpenCV读取返回的是BGR格式。但模型训练时用的是RGB格式。如果预处理参数里没有做BGR到RGB的转换量化后的精度会掉得很厉害。解决办法是在config里加quantized_algorithmnormal或者在dataset.txt里用已经转成RGB的图片。第二个坑交叉编译时OpenCV的版本。板端Ubuntu 22.04自带的OpenCV是4.5.4PC端交叉编译工具链里的OpenCV版本如果高于这个链接时会报符号找不到。解决办法是用板端同版本的OpenCV头文件和库或者静态链接OpenCV。第三个坑adb连接不稳定。RK3576的Ubuntu系统默认没开adb需要手动启动adbd服务sudo systemctl start adbd如果adb devices识别不到板子检查USB线是不是数据线有些线只能充电不能传数据。另外Ubuntu系统下可能需要加udev规则echo SUBSYSTEMusb, ATTR{idVendor}2207, MODE0666 | sudo tee /etc/udev/rules.d/51-android.rules sudo udevadm control --reload-rules第四个坑HDMI插入后媒体声音消失。这个问题在RK3576的Android 14上比较常见原因是HDMI音频输出和板载音频输出冲突。解决办法是在设置里手动切换音频输出设备或者修改audio_policy_configuration.xml把HDMI音频的优先级调低。不过这个和MobileNet部署关系不大只是顺便提一下。6. 从单帧推理到视频流实时处理单帧推理跑通之后下一步自然是做视频流的实时处理。思路很简单用OpenCV的VideoCapture读摄像头或者视频文件逐帧推理把结果画在画面上显示出来。但实际做的时候有几个性能瓶颈要注意。第一个瓶颈是图片预处理。每帧都要做resize和颜色空间转换如果分辨率是1080presize到224x224的耗时在2到3毫秒左右加上推理的10毫秒一帧总共13毫秒理论上能跑到70帧以上。但实际测试下来OpenCV的imread和imshow耗时也不小整体帧率在30到40帧之间。第二个瓶颈是内存拷贝。rknn_inputs_set会把输入数据拷贝到NPU的共享内存如果每帧都重新分配内存开销很大。优化方法是用rknn_inputs_set的零拷贝模式直接传物理地址但需要驱动支持。我实测下来普通模式下30帧的帧率已经够用如果追求极致性能再考虑零拷贝。视频流处理的代码框架cv::VideoCapture cap(0); if (!cap.isOpened()) { printf(Cannot open camera\n); return -1; } while (true) { cv::Mat frame; cap frame; if (frame.empty()) break; cv::Mat resized, rgb; cv::resize(frame, resized, cv::Size(224, 224)); cv::cvtColor(resized, rgb, cv::COLOR_BGR2RGB); // 设置输入并推理 rknn_inputs_set(ctx, 1, inputs); rknn_run(ctx, NULL); rknn_outputs_get(ctx, 1, outputs, NULL); // 后处理并显示 float* output_data (float*)outputs[0].buf; int max_idx 0; float max_val output_data[0]; for (int i 1; i 1000; i) { if (output_data[i] max_val) { max_val output_data[i]; max_idx i; } } char label[64]; sprintf(label, Class: %d, Conf: %.2f, max_idx, max_val); cv::putText(frame, label, cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2); cv::imshow(MobileNet on RK3576, frame); rknn_outputs_release(ctx, 1, outputs); if (cv::waitKey(1) q) break; }这段代码在板子上跑的时候要注意OpenCV的highgui模块需要GTK或者Qt支持。如果板端Ubuntu没装桌面环境imshow会报错。解决办法是用framebuffer显示或者把结果通过RTSP推流出去。我一般用RTSP推流方便在PC端查看。实操心得视频流处理时rknn_outputs_get之后一定要调用rknn_outputs_release否则内存会持续增长跑几分钟之后板子就卡死了。这个坑我踩过一开始没释放输出跑了5分钟内存从200MB涨到1.5GB系统直接OOM。7. 模型扩展与多模型切换的工程化思路实际产品里不可能只跑一个MobileNet通常需要多个模型切换比如分类模型加检测模型。RKNN-Toolkit2支持多模型加载但每个模型都要单独初始化一个rknn_context。内存占用会叠加RK3576的NPU内存有限同时加载太多模型会失败。工程化的做法是模型池化初始化时只加载一个模型推理完成后销毁切换模型时重新加载。但这样每次切换都要重新初始化耗时在50到100毫秒之间。如果切换频率不高可以接受。如果切换频繁就要做模型预加载把常用的模型常驻内存。另一个思路是用一个多任务模型替代多个单任务模型。比如MobileNet V2做骨干网络后面接两个head一个做分类一个做检测。这样只需要加载一个模型NPU利用率更高。但模型转换时要确保RKNN-Toolkit2支持多输出实测下来是支持的只要onnx里定义清楚输出节点就行。模型版本管理也是个问题。产品迭代时模型会更新板端要能兼容新旧版本。我的做法是在rknn模型文件名里加版本号程序启动时扫描目录加载最新版本的模型。同时保留一个回滚机制如果新模型推理异常自动切换到旧版本。最后说一个实际部署时的经验RK3576的NPU在跑MobileNet时功耗大概在1.5W左右加上CPU和内存的功耗整板功耗在3到4W。如果是电池供电的场景要考虑功耗预算。优化方法是降低NPU频率或者用更小的模型比如MobileNet V3 Small推理耗时能降到5毫秒以内功耗也相应降低。
返回列表