- 人工智能
- 推理引擎
- 模型量化
- 模型优化
- 边缘计算
- 开发工具
【免费下载链接】rknn-toolkit2
导读
本文以 rknn-toolkit2 仓库 中 rknpu2/examples/rknn_yolov5_demo 示例为核心,完整讲解如何在 Rockchip(RKNN)NPU 平台上将 YOLOv5s 模型从 ONNX 导出为 RKNN 格式,并在 Android 与 Aarch64 Linux 设备(RK3562 / RK3566 / RK3568 / RK3576 / RK3588 / RV1126B)上编译、部署、运行图片与视频流检测任务。文章同时深入源码剖析该 Demo 的 CPU 后处理实现(含 anchor、NMS、反量化细节)与 RGA 图像预处理链路,帮助读者在复用 Demo 时快速对齐自训模型参数、规避常见部署陷阱。
1. 示例概览:Demo 能做什么
rknn_yolov5_demo是 rknn-toolkit2 仓库中面向 NPU 推理运行时(RKNN Runtime)提供的官方 YOLOv5 检测示例,包含三部分能力:
- 单张图片推理:加载
.rknn模型与图片,输出检测框、类别与置信度,并保存标注结果图(默认输出./out.jpg); - 视频码流推理:基于 MPP(Rockchip 媒体处理平台)硬解码 H.264 / H.265 文件,逐帧检测后硬编码输出到
out.h264; - RTSP 网络流推理:基于 ZLMediaKit 拉取 RTSP 视频流进行实时检测(仅 Linux 平台)。
Demo 目录结构如下(相对仓库根目录):
rknpu2/examples/rknn_yolov5_demo/ ├── CMakeLists.txt # 构建脚本,链接 rknnrt/rga/opencv/mpp/zlmediakit ├── build-android.sh # Android 交叉编译脚本 ├── build-linux.sh # Aarch64 Linux 交叉编译脚本 ├── convert_rknn_demo/yolov5/ # ONNX → RKNN 转换脚本(onnx2rknn.py) ├── include/ │ ├── postprocess.h # 后处理阈值、类别数等宏定义 │ ├── preprocess.h # letterbox / RGA 缩放接口 │ ├── drm_func.h / rga_func.h # DRM / RGA 硬件加速接口 ├── model/ │ ├── RK3562/ RK3566_RK3568/ RK3576/ RK3588/ RV1126B/ │ │ └── yolov5s-640-640.rknn # 各平台预编译模型 │ ├── bus.jpg # 测试图片 │ └── coco_80_labels_list.txt # COCO 80 类标签文件 ├── src/ │ ├── main.cc # 图片推理主程序 │ ├── main_video.cc # 视频/RTSP 推理主程序 │ ├── postprocess.cc # CPU 后处理(anchor 解码 + NMS) │ └── preprocess.cc # letterbox 与 RGA 缩放实现 └── utils/ # mpp_decoder / mpp_encoder / drawing其中model/<平台>/yolov5s-640-640.rknn是各平台预编译好的 640×640 输入模型,可直接用于部署验证。
2. 前置条件与注意事项(务必逐条核对)
官方 README 明确列出的 5 条注意事项是部署成败的关键,下面结合源码逐一展开。
2.1 rknn-toolkit2 版本要求
模型转换端需使用rknn-toolkit2 版本 ≥ 1.4.0。当前仓库自带版本为 2.3.2(见 rknn-toolkit2/packages 下的 wheel 包与 rknn-toolkit2/doc/changelog-2.3.2.txt),运行时依赖与安装方式见 rknn-toolkit2/packages/x86_64/requirements_cp310-2.3.2.txt。
2.2 自训模型必须对齐 anchor 等后处理参数
切换为自己训练的模型时,必须保证 anchor 与后处理参数一致,否则会导致后处理解析出错。Demo 内置模型使用的 anchor 硬编码在 postprocess.cc:
const int anchor0[6] = {10, 13, 16, 30, 33, 23}; // stride 8 的特征层 const int anchor1[6] = {30, 61, 62, 45, 59, 119}; // stride 16 的特征层 const int anchor2[6] = {116, 90, 156, 198, 373, 326}; // stride 32 的特征层这三个 anchor 组分别对应 YOLOv5 输出头 stride 8 / 16 / 32 三个尺度,与官方 COCO 预训练模型一致。自训模型若改动 anchor,需要同步修改这三处常量。
2.3 类别数与阈值配置
官方与 Rockchip 预训练模型均为COCO 80 类。若自训模型类别数不同,必须修改 include/postprocess.h 中的宏:
#define OBJ_NAME_MAX_SIZE 16 // 类别名最长字节数 #define OBJ_NUMB_MAX_SIZE 64 // 单帧最多输出检测框数量 #define OBJ_CLASS_NUM 80 // 类别总数(自训模型需修改) #define NMS_THRESH 0.45 // NMS IoU 阈值 #define BOX_THRESH 0.25 // 目标置信度阈值 #define PROP_BOX_SIZE (5 + OBJ_CLASS_NUM) // 每个 anchor 预测向量长度(xywh+obj+class)后处理入口post_process(...)接收conf_threshold(对应BOX_THRESH)与nms_threshold(对应NMS_THRESH)两个参数,在 main.cc 中从宏读取:
const float nms_threshold = NMS_THRESH; // 默认的NMS阈值 const float box_conf_threshold = BOX_THRESH; // 默认的置信度阈值2.4 依赖 librga.so
Demo 的图像缩放通过 RGA(Rockchip 2D 图形加速单元)完成,运行时依赖librga.so。RGA 相关头文件与库位于 rknpu2/examples/3rdparty/rga(含im2d.h、rga.h等头文件及 Linux/Android 预编译库)。若设备/lib或./lib目录下找不到librga.so,需自行定位其路径并追加到LD_LIBRARY_PATH(详见第 4.3 节)。
2.5 ReLU 激活函数与 CPU 后处理的取舍
由于 NPU 硬件资源限制,该 Demo默认将 YOLOv5 的后处理(anchor 解码、置信度筛选、NMS)移到 CPU 实现,不占用 NPU 资源。同时,Demo 附带的模型全部使用ReLU 作为激活函数(而非 YOLOv5 原版 SiLU),相比 SiLU 精度略有下降,但性能大幅提升。这一点在转换脚本中也有体现:convert_rknn_demo/yolov5/onnx_models/下存放的是yolov5s_relu.onnx而非原版 SiLU 模型。如需更高精度,可在 rknn_model_zoo 的 yolo 模型转换说明 基础上自行导出 SiLU 版本并相应修改后处理。
3. RKNN 模型导出:从 ONNX 到 .rknn
Demo 目录下的convert_rknn_demo/yolov5/提供了转换示例,核心脚本为 onnx2rknn.py。其完整流程如下:
from rknn.api import RKNN platform = 'rk3566' # 目标平台,如 rk3562/rk3566/rk3568/rk3576/rk3588/rv1126b MODEL_PATH = './onnx_models/yolov5s_relu.onnx' DATASET = './dataset.txt' # 量化校准数据集列表(每行一张图片路径) Width, Height = 640, 640 rknn = RKNN() rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform=platform) # 归一化参数:除以 255 ret = rknn.load_onnx(MODEL_PATH) # 加载 ONNX 模型 ret = rknn.build(do_quantization=True, dataset=DATASET) # 使用数据集做 INT8 量化并构建 ret = rknn.export_rknn(RKNN_MODEL_PATH) # 导出 .rknn 文件 rknn.release()要点说明:
mean_values/std_values使用[0,0,0]/[255,255,255],即输入归一化等价于将像素除以 255,与 C 端main.cc中inputs[0].type = RKNN_TENSOR_UINT8(直接送入 0–255 的 UINT8 数据)相匹配;dataset.txt用于 INT8 量化校准,内容为逐行的图片路径列表;target_platform决定编译目标 NPU 架构,导出后放入model/<对应平台目录>/下即可被 Demo 加载;- 若已有
.rknn文件,可跳过build直接rknn.load_rknn(RKNN_MODEL_PATH)加载(脚本中NEED_BUILD_MODEL = False分支)。
预编译模型方面,仓库已为RK3562、RK3566_RK3568、RK3576、RK3588、RV1126B五个平台各提供一个yolov5s-640-640.rknn,可直接进入下一步部署。
4. Android Demo:编译、推送与运行
4.1 编译
先导出 NDK 路径(建议 NDK r18 / r19 版本,其他版本可能导致编译失败),然后执行构建脚本:
export ANDROID_NDK_PATH=~/opts/ndk/android-ndk-r18b ./build-android.sh -t <target> -a <arch> [-b <build_type>] # 例如(目标 RK3568,arm64 架构,Release 构建): ./build-android.sh -t rk3568 -a arm64-v8a -b Release脚本 build-android.sh 的参数说明:
| 参数 | 含义 | 可选值 |
|---|---|---|
-t | 目标 SoC | rk3566/rk3568/rk3562/rk3576/rk3588(rk356x与rk3566_rk3568均映射为RK3566_RK3568) |
-a | 目标 ABI | arm64-v8a/armeabi-v7a |
-b | 构建类型 | Debug/Release(默认Release) |
脚本内部基于 NDK 的android.toolchain.cmake交叉编译,-DANDROID_PLATFORM=android-24、-DANDROID_STL=c++_static,编译产物安装到install/rknn_yolov5_demo/(含可执行文件、lib/librknnrt.so、lib/librga.so及model/目录,见 CMakeLists.txt 的install段)。
4.2 推送文件到板卡
通过 USB 连接板卡到 PC,将整个安装目录推到/data:
adb root adb remount adb push install/rknn_yolov5_demo /data/4.3 运行
adb shell cd /data/rknn_yolov5_demo/ export LD_LIBRARY_PATH=./lib ./rknn_yolov5_demo model/<TARGET_PLATFORM>/yolov5s-640-640.rknn model/bus.jpg运行后终端会依次打印:模型输入/输出数量、各张量属性(格式、量化类型、zp、scale)、单次推理耗时、平均推理耗时以及检测结果(类别名、框坐标、置信度),最终在./out.jpg生成带框标注图。
程序默认采用letterbox预处理;也支持resize与自定义输出路径(见 main.cc 的用法提示Usage: %s <rknn model> <input_image_path> <resize/letterbox> <output_image_path>)。
5. Aarch64 Linux Demo:编译、推送与运行
5.1 编译
先导出交叉编译器前缀(以 Linaro GCC 7.5.0 为例),再执行脚本:
export GCC_COMPILER=~/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu ./build-linux.sh -t <target> -a <arch> [-b <build_type>] # 例如: ./build-linux.sh -t rk3588 -a aarch64 -b Releasebuild-linux.sh 参数与 Android 版基本一致,但-a取值不同:
| 参数 | 含义 | 可选值 |
|---|---|---|
-t | 目标 SoC | rk3566/rk3568/rk3562/rk3576/rk3588/rv1126b |
-a | 目标架构 | aarch64/armhf |
-b | 构建类型 | Debug/Release(默认Release) |
脚本将CC=${GCC_COMPILER}-gcc、CXX=${GCC_COMPILER}-g++,并通过 CMake 指定-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=${TARGET_ARCH}完成交叉编译,产物输出到install/rknn_yolov5_demo_Linux/。
5.2 推送文件到板卡
将install/rknn_yolov5_demo_Linux拷贝到板卡/userdata/目录:
- 若使用 Rockchip EVB 板,可用 adb:
adb push install/rknn_yolov5_demo_Linux /userdata/- 其他板卡可使用
scp等方式将整个目录拷贝到/userdata。
5.3 运行
adb shell cd /userdata/rknn_yolov5_demo_Linux/ export LD_LIBRARY_PATH=./lib ./rknn_yolov5_demo model/<TARGET_PLATFORM>/yolov5s-640-640.rknn model/bus.jpg注意:如果
./lib下没有找到librga.so,请先定位系统内的librga.so并追加到动态库搜索路径:export LD_LIBRARY_PATH=./lib:<LOCATION_LIBRGA.SO>需要根据系统的 RGA 驱动版本选择对应的 librga 库,相关编译与使用方式参考仓库内 rknpu2/examples/3rdparty/rga/README.md。
6. 视频流 Demo:H.264 / H.265 / RTSP
视频推理使用rknn_yolov5_video_demo(由 CMakeLists.txt 在具备 MPP 库时额外构建),输入 640×640 模型,基于 MPP 硬解码、RGA 缩放、NPU 推理、MPP 硬编码输出out.h264。
6.1 H.264 本地文件
./rknn_yolov5_video_demo model/<TARGET_PLATFORM>/yolov5s-640-640.rknn xxx.h264 264若手头是 mp4,先用 ffmpeg 转成 H.264 裸流:
ffmpeg -i xxx.mp4 -vcodec h264 xxx.h2646.2 H.265 本地文件
./rknn_yolov5_video_demo model/<TARGET_PLATFORM>/yolov5s-640-640.rknn xxx.hevc 265对应的 ffmpeg 转换命令:
ffmpeg -i xxx.mp4 -vcodec hevc xxx.hevc6.3 RTSP 网络流
./rknn_yolov5_video_demo model/<TARGET_PLATFORM>/yolov5s-640-640.rknn <RTSP_URL> 2656.4 视频 Demo 的三大限制(官方明确说明)
- RK3562 仅支持 H.264 视频流(其 MPP 编码器不支持 HEVC,见 main_video.cc 中
enc_params.type = MPP_VIDEO_CodingAVC;的注释说明); - RTSP 视频流 Demo 仅在 Linux 系统上支持,Android 暂不支持(RTSP 拉流依赖 ZLMediaKit,构建时通过
-DBUILD_VIDEO_RTSP宏控制,Android 构建未链接libmk_api.so); - 输入 H.264 码流的文件名不能是
out.h264,因为它会被输出文件覆盖(输出路径硬编码为#define OUT_VIDEO_PATH "out.h264",见 main_video.cc)。
视频链路源码结构:mpp_decoder硬解码出 YUV420SP 帧后,回调mpp_decoder_frame_callback,经 RGAimresize缩放到模型输入尺寸并转为 RGB,推理后用draw_rectangle_yuv420sp直接在 YUV 帧上画框,最后交给mpp_encoder编码写入out.h264(RTSP 分支通过mk_player_play拉流,on_track_frame_out回调把每帧数据送入 decoder,见 main_video.cc)。
7. 源码级解析:CPU 后处理与推理主流程
7.1 推理主流程(main.cc)
main.cc 的核心调用链如下:
rknn_init:从文件加载.rknn模型数据并初始化上下文;rknn_query:依次查询RKNN_QUERY_SDK_VERSION、RKNN_QUERY_IN_OUT_NUM、输入/输出张量属性(RKNN_QUERY_INPUT_ATTR/RKNN_QUERY_OUTPUT_ATTR),并打印每个张量的 dims、格式(NCHW/NHWC)、量化类型、zp(zero point)与scale;- 根据输入属性判断模型输入布局:
RKNN_TENSOR_NCHW时channel=dims[1], height=dims[2], width=dims[3],否则按 NHWC 取dims[1..3]; - 图片预处理:OpenCV 读取图片 → BGR2RGB → 按
option选择resize(RGA 直接缩放)或letterbox(等比例缩放 + 灰边填充,pad 值记录到pads);预处理图会分别保存为resize_input.jpg/letterbox_input.jpg便于调试; - 推理:
rknn_inputs_set送入 UINT8 NHWC 输入 →rknn_run→rknn_outputs_get(want_float=0,直接取 INT8 量化输出)→ 统计单次耗时; - 后处理:取 3 个输出头(对应 stride 8/16/32)的 INT8 数据、zp、scale,调用
post_process得到检测结果; - 绘制:
rectangle/putText画框写类别与置信度,保存到输出图; - 性能测试:连续运行 10 次(
PERF_WITH_POST=1时含后处理)输出平均耗时,最后rknn_destroy释放资源。
7.2 CPU 后处理实现(postprocess.cc)
postprocess.cc 实现了完整的 YOLOv5 解码流程,理解它对自训模型适配至关重要:
a) 反量化(INT8 → float)
模型输出为 INT8 定点数据,需用各输出张量的zp与scale反量化:
static float deqnt_affine_to_f32(int8_t qnt, int32_t zp, float scale) { return ((float)qnt - (float)zp) * scale; }对应地,阈值也需先量化到 INT8 域再做比较(qnt_f32_to_affine),避免逐元素浮点比较的开销。
b) anchor 解码
对每个输出头按PROP_BOX_SIZE(= 5 + 类别数)解析,xywh使用 2×sigmoid 解码,宽高再乘以对应 anchor:
float box_x = (deqnt_affine_to_f32(*in_ptr, zp, scale)) * 2.0 - 0.5; float box_w = (deqnt_affine_to_f32(in_ptr[2 * grid_len], zp, scale)) * 2.0; box_w = box_w * box_w * (float)anchor[a * 2]; // 宽再乘 anchor 宽 box_x = (box_x + j) * (float)stride; // 加上网格偏移并按 stride 放大三个输出头分别按 stride 8 / 16 / 32 计算网格grid_h = model_in_h / stride,anchor 组对应anchor0/anchor1/anchor2。
c) 置信度筛选与类别判定
box_confidence >= thres_i8且maxClassProbs > thres_i8的候选进入列表,目标得分取obj × class_prob的乘积,同时记录类别 id。
d) NMS
先按得分降序(quick_sort_indice_inverse),再对每个类别独立执行 NMS(nms),IoU 高于NMS_THRESH(0.45)的框被抑制(置 -1)。
e) 坐标回映原图
由于推理是在 letterbox 后的 640×640 图(或 resize 图)上进行的,最终坐标需减去 padding 并按缩放比还原到原图:
float x1 = filterBoxes[n * 4 + 0] - pads.left; ... group->results[last_count].box.left = (int)(clamp(x1, 0, model_in_w) / scale_w);f) 类别名加载
类别名从./model/coco_80_labels_list.txt读取(LABEL_NALE_TXT_PATH宏),因此运行目录下必须存在model/coco_80_labels_list.txt(CMake 的 install 段会自动安装该文件与测试图片)。若自训模型类别不同,需同步替换该标签文件与OBJ_CLASS_NUM。
7.3 硬件加速预处理
- letterbox:等比例缩放并灰边填充(默认
cv::Scalar(128,128,128)),记录四个方向的 pad 值供后处理回映(见 include/preprocess.h); - RGA 缩放:
resize_rga通过im2d.h接口完成硬件加速 resize,避免 CPU 缩放开销(main_video.cc中直接对 MPP 解码帧调用imresize,零拷贝地把 YUV 帧转为模型输入 RGB 缓冲)。
8. 常见问题与调参速查表
| 场景 | 需要修改的位置 | 说明 |
|---|---|---|
| 自训模型 anchor 与官方不同 | postprocess.cc 中anchor0/1/2 | 三组 anchor 需与训练配置严格一致 |
| 自训模型类别数 ≠ 80 | include/postprocess.h 的OBJ_CLASS_NUM | 同时替换model/coco_80_labels_list.txt标签文件 |
| 检出框过少/误检 | include/postprocess.h 的NMS_THRESH/BOX_THRESH | 官方默认NMS_THRESH=0.45、BOX_THRESH=0.25 |
运行时找不到librga.so | LD_LIBRARY_PATH | 追加./lib:<LOCATION_LIBRGA.SO> |
| 输入分辨率不是 640×640 | onnx2rknn.py 的Width/Height | 重新导出对应分辨率的模型 |
| 运行目录缺少标签文件 | model/coco_80_labels_list.txt | 运行前确认与可执行文件同级存在model/目录 |
| RK3562 上跑 H.265 | 不支持 | 仅支持 H.264 视频流 |
| Android 上跑 RTSP | 不支持 | 仅 Linux 系统支持 RTSP 视频流 |
9. 总结
rknn_yolov5_demo提供了一条从模型转换(onnx2rknn.py)到端侧部署(Android / Aarch64 Linux 图片、视频、RTSP)的完整 YOLOv5 落地链路,其 CPU 后处理实现、RGA 硬件预处理与 MPP 视频编解码方案可直接作为二次开发的基线。复用该 Demo 时,最关键的是保持anchor、类别数、标签文件、归一化参数与后处理阈值在转换端和运行端的一致性;在此基础上,根据自训模型微调 include/postprocess.h 中的宏即可快速适配新任务。
- 人工智能
- 推理引擎
- 模型量化
- 模型优化
- 边缘计算
- 开发工具
【免费下载链接】rknn-toolkit2
相关推荐
Charon 转换系统详解:控制流重构与关联类型提升等 LLBC/ULLBC 输出优化
Charon 转换系统详解:控制流重构与关联类型提升等 LLBC/ULLBC 输出优化 Charon 是 Facebook Infer 仓库中内置的 Rust
人工智能推理引擎模型量化模型优化边缘计算开发工具RKNN-Toolkit2 实战:基于 RKNPU 的 Android APK 端 YOLOv5s 实时目标检测部署全解析(rknn_yolov5_android_apk_demo 源码级指南)
RKNN Toolkit2 实战:基于 RKNPU 的 Android APK 端 YOLOv5s 实时目标检测部署全解析(rknn_yolov5_androi
人工智能推理引擎模型量化模型优化边缘计算开发工具Airweave OpenAPI 规范生成与 Fern SDK / 文档管线全解析
Airweave OpenAPI 规范生成与 Fern SDK / 文档管线全解析 本篇技术指南以 Airweave 仓库中 fern/scripts/READ
人工智能推理引擎模型量化模型优化边缘计算开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考