首页 / 资讯中心 / 文章详情

YOLOv11 ONNX转RKNN量化部署避坑指南

YOLOv11 ONNX转RKNN量化部署避坑指南 ★ FEATURED ARTICLE
简介这份资源面向需要在Rockchip系列处理器上部署目标检测模型的开发者与个人学习者提供从ONNX中间表示到RKNN嵌入式格式的完整转换工具链解决跨平台模型迁移与量化部署中的环境配置、精度取舍和性能评估问题。压缩包共16个文件约14.18MB以png与jpg图片、zbak备份文件、py转换脚本、onnx与rknn模型文件及md说明文档为主图片用于展示转换流程与推理结果脚本与模型文件则构成可复用的转换示例。资源包含环境依赖检测、模型结构可视化、转换参数配置、推理速度基准测试等组件并支持INT8混合量化与FP16精度保留两种模式附带层间延迟分析与内存占用评估能力。目前已有223人学习下载适合希望快速跑通YOLOv11到RKNN转换流程、理解量化配置与性能调优思路的读者参考。1. YOLOv11 模型 ONNX 至 RKNN 转换为什么量化这一步总在板子上翻车你手里有一个在 PC 上跑得好好的 YOLOv11 权重yolov11.pt推理一张图几十毫秒mAP 看着也舒服。现在要把它塞进 RK3588、RK3568 这类边缘板子流程看起来很简单pt → onnx → rknn三条命令的事。但真正动手的人都知道翻车点几乎全在最后一步——ONNX 转 RKNN 时的量化配置。浮点模型转过去精度掉一大截或者干脆在rknn.build()阶段报算子不支持这类血泪经验在 RKNN 部署里太常见了。这篇笔记锁定的就是这条链路YOLOv11 导出 ONNX 后怎么用 RKNN-Toolkit2 转成.rknn量化参数怎么设跨平台部署时哪些坑必须提前绕开。适合两类人一是刚拿到板子、想把 YOLOv11 跑起来的新手二是已经跑通但精度不达标、想搞清量化细节的熟手。下面按「导出 → 转换 → 量化 → 验证 → 避坑」的顺序推。2. 从 YOLOv11 导出 ONNX版本、算子与动态轴怎么定2.1 为什么不能直接拿 ultralytics 默认导出的 ONNX 去转Ultralytics 的 YOLOv11 默认model.export(formatonnx)会带一些 RKNN 不喜欢的算子形态最常见的是Resize用了coordinate_transformation_modehalf_pixel以及SiLU激活在某些 RKNN-Toolkit2 版本里需要拆成Sigmoid Mul。另外默认导出是动态 batch 和动态输入尺寸RKNN 在量化阶段对动态轴支持有限通常要求固定batch1、固定H/W。我一般会显式指定opset12因为 RKNN-Toolkit2 对 opset 12/13 的算子映射最稳opset 17 以上有些算子会落到 CPU 回退速度直接崩。输入尺寸按板子端实际用的来比如640x640不要导出640x640又想在板子上跑320x320量化时的校准集尺寸必须和推理尺寸一致。from ultralytics import YOLO # 加载 YOLOv11 权重这里用官方预训练或自己训练的 .pt model YOLO(yolov11n.pt) # 导出 ONNX固定 batch1固定输入 640x640opset12 model.export( formatonnx, imgsz640, # 固定输入尺寸和板子端推理尺寸保持一致 batch1, # RKNN 量化通常只支持 batch1 opset12, # RKNN-Toolkit2 对 12/13 映射最稳 simplifyTrue, # 用 onnx-simplifier 去掉冗余节点 dynamicFalse, # 关闭动态轴避免量化时 shape 推断失败 halfFalse # 导出 FP32量化交给 RKNN 做 )这段代码的关键参数就四个imgsz、batch、opset、dynamic。simplifyTrue会调用onnxsim能把一些Identity、Constant节点合并掉减少 RKNN 转换时的算子解析负担。halfFalse是因为 RKNN 的量化流程自己会做 FP32→INT8你给 FP16 反而可能引入额外的精度损失。导出完成后用onnxruntime跑一遍确认模型能正常推理别等到 RKNN 报错才回头查 ONNX。import onnxruntime as ort import numpy as np sess ort.InferenceSession(yolov11n.onnx, providers[CPUExecutionProvider]) # 打印输入输出名称和 shape后面写 RKNN 配置要用 for i in sess.get_inputs(): print(input:, i.name, i.shape, i.type) for o in sess.get_outputs(): print(output:, o.name, o.shape, o.type) # 用随机输入跑一遍确认没有算子报错 dummy np.random.randn(1, 3, 640, 640).astype(np.float32) outs sess.run(None, {sess.get_inputs()[0].name: dummy}) print(output count:, len(outs))如果这里就报NOT_IMPLEMENTED说明 ONNX 里有 RKNN 不支持的算子需要回到导出阶段调整 opset 或手动改图。常见的是GridSample、NonMaxSuppression这类YOLOv11 的检测头如果带了 NMS建议导出时把 NMS 去掉后处理放到板子端用 C 写。2.2 输出层命名与后处理边界YOLOv11 导出后通常有三个输出头对应 stride 8/16/32。RKNN 转换时不需要你手动指定输出层但板子端推理时要按顺序取。建议在导出后用 Netron 看一眼输出节点名字记下来后面写 RKNN 推理代码时按名字取别按索引取因为 RKNN 转换后输出顺序可能变。后处理这块我一般不在 ONNX 里做 decode直接把三个原始输出头转成 RKNN板子端用 C 做sigmoid、decode、NMS。这样量化时 NMS 不参与避免 INT8 下 NMS 的阈值判断出现玄学偏移。3. RKNN-Toolkit2 转换配置、量化与 build 的完整命令3.1 环境准备与版本对齐RKNN-Toolkit2 的版本和板子端librknnrt.so必须对齐这是最容易忽略的坑。PC 端用rknn-toolkit22.0.0板子端 runtime 也要是 2.0.0否则rknn.init_runtime()直接报版本不匹配。Python 版本建议 3.8/3.103.11 有些 wheel 没覆盖。# 创建独立环境避免和系统里的 onnx 版本冲突 conda create -n rknn python3.10 -y conda activate rknn # 安装 rknn-toolkit2按官方 wheel 装不要 pip install rknn-toolkit2 直接拉最新 pip install rknn_toolkit2-2.0.0-cp310-cp310-linux_x86_64.whl # 装 onnx 和 onnxruntime版本别太新 pip install onnx1.14.0 onnxruntime1.16.0装完后python -c from rknn.api import RKNN能导入就说明环境 OK。如果报libpython相关错误检查 conda 环境的lib路径有没有加到LD_LIBRARY_PATH。3.2 量化配置dataset.txt 与量化算法选择RKNN 量化分两步一是用校准集跑一遍统计激活值范围二是把 FP32 权重和激活映射到 INT8。校准集的质量直接决定量化后精度。我一般从训练集里抽 100200 张覆盖不同光照、不同类别别只用几张图否则激活范围统计偏窄量化后小目标直接消失。dataset.txt每行是一张图的路径图片尺寸要和 ONNX 输入一致RKNN 会自己做 resize但建议提前 resize 好减少预处理差异。# dataset.txt 示例每行一个图片绝对路径 /home/user/calib/0001.jpg /home/user/calib/0002.jpg /home/user/calib/0003.jpg ...转换脚本里quantized_dtype选asymmetric_quantized-8这是 RKNN 对 INT8 的默认推荐对称量化在某些激活分布偏斜的层上掉点更明显。quantized_algorithm选normal还是mmse实测mmse在 YOLOv11 上对小目标更友好但转换时间会长一些。from rknn.api import RKNN rknn RKNN(verboseTrue) # 配置目标平台 rk3588量化类型 INT8优化等级 3 rknn.config( mean_values[[0, 0, 0]], # 和训练时预处理对齐YOLO 默认 0-1 归一化 std_values[[255, 255, 255]], # 除以 255和 ultralytics 预处理一致 target_platformrk3588, # 按实际板子改rk3568/rk3588 算子支持不同 quantized_dtypeasymmetric_quantized-8, quantized_algorithmmmse, # 小目标场景优先 mmse optimization_level3, # 3 是最高优化会做算子融合 num_cores4 # NPU 核心数rk3588 有 3 核这里按实际填 ) # 加载 ONNX ret rknn.load_onnx(modelyolov11n.onnx) if ret ! 0: print(load_onnx failed) exit(ret) # build传入校准集做量化 ret rknn.build(do_quantizationTrue, datasetdataset.txt) if ret ! 0: print(build failed) exit(ret) # 导出 rknn 模型 ret rknn.export_rknn(yolov11n_int8.rknn) if ret ! 0: print(export failed) exit(ret)mean_values和std_values这里要特别注意ultralytics 的预处理是img / 255所以 mean 填 0、std 填 255。如果你训练时用了别的归一化这里必须改否则量化校准的输入分布和实际推理不一致精度直接崩。optimization_level3会做 ConvBNReLU 融合YOLOv11 的 SiLU 如果没被正确识别可能会被拆成多个算子转换日志里会打印。3.3 转换日志怎么看算子回退与量化误差rknn.build()跑完后日志里会有一行Quantization accuracy或者Network layer analysis重点看两个东西一是哪些层落到了 CPU二是量化前后余弦相似度。CPU 回退的层会在日志里标cpu如果检测头回退了板子端速度会从几十毫秒掉到几百毫秒。# 转换日志里搜 cpu看哪些层回退了 python convert.py 21 | grep -i cpu # 搜 quantize看每层量化误差 python convert.py 21 | grep -i quantize如果发现SiLU回退可以在导出 ONNX 时把SiLU替换成Sigmoid Mul或者升级 RKNN-Toolkit2 到支持 SiLU 的版本。实测 2.0.0 对 SiLU 支持已经不错但某些自定义激活还是会被拆。4. 板子端部署runtime 初始化、推理与精度验证4.1 板子端 C 推理最小闭环板子端用librknnrt.soC 接口比 Python 快适合生产。核心流程是rknn_init→rknn_query拿输入输出属性 →rknn_inputs_set→rknn_run→rknn_outputs_get。输入数据要按RKNN_TENSOR_INT8或RKNN_TENSOR_UINT8传量化模型通常传UINT8RKNN 内部会做减均值除方差。#include rknn_api.h rknn_context ctx; int ret rknn_init(ctx, model_path, 0, 0, nullptr); if (ret 0) { printf(rknn_init failed\n); return -1; } // 查询输入输出数量 rknn_input_output_num io_num; ret 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); // 设置输入NHWCUINT8和量化时 dataset 的预处理对齐 rknn_input inputs[1]; inputs[0].index 0; inputs[0].type RKNN_TENSOR_UINT8; inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].size 640 * 640 * 3; inputs[0].buf input_data; // 预处理好的 uint8 数据 ret rknn_inputs_set(ctx, 1, inputs); // 推理 ret rknn_run(ctx, nullptr); // 取输出 rknn_output outputs[3]; for (int i 0; i 3; i) { outputs[i].index i; outputs[i].want_float 1; // 取 FP32 输出方便后处理 } ret rknn_outputs_get(ctx, 3, outputs, nullptr); // 后处理decode NMSwant_float1会把 INT8 输出反量化成 FP32方便你复用 PC 端的后处理代码。如果追求极致速度可以want_float0拿 INT8 输出但后处理里的阈值和缩放要自己按量化参数算容易出错新手建议先用want_float1。4.2 精度验证和 ONNX 输出逐层对比量化后精度掉多少不能只看最终 mAP要逐层对比。RKNN-Toolkit2 提供了rknn.inference()在 PC 上模拟推理你可以拿同一张图分别跑 ONNX 和 RKNN对比三个输出头的余弦相似度。如果某个头相似度低于 0.95说明那一支的量化有问题通常是该支的激活范围太宽需要调整校准集或对该层做混合量化。# PC 端模拟推理对比 ONNX 和 RKNN 输出 import numpy as np # ONNX 输出 onnx_out sess.run(None, {sess.get_inputs()[0].name: img}) # RKNN 输出 rknn_out rknn.inference(inputs[img]) for i in range(3): a onnx_out[i].flatten() b rknn_out[i].flatten() cos np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) print(fhead {i} cosine: {cos:.4f})余弦相似度低于 0.9 的头基本可以判定量化损失过大。这时候优先检查校准集是否覆盖了该头对应的尺度目标stride 32 的头对应大目标校准集里如果全是大目标小目标头就统计不准。5. 避坑与排查ONNX 转 RKNN 最常见的 5 个翻车点5.1 现象build 阶段报 “Unsupported op” → 原因ONNX 里有 RKNN 不支持的算子 → 解决回退 opset 或手动改图最常见的是Resize的coordinate_transformation_mode不被支持。解决方法是导出时指定opset12或者在 ONNX 里把Resize改成Upsample。另一个高频算子是天坑NonMaxSuppressionYOLOv11 如果带了 NMS 导出RKNN 直接报不支持。解决方法是导出时nmsFalse后处理放到板子端。5.2 现象量化后 mAP 掉 10 个点以上 → 原因校准集分布和实际推理不一致 → 解决校准集覆盖全场景预处理对齐校准集不能只用训练集里挑几张要覆盖不同光照、不同尺度、不同类别。更隐蔽的是预处理不一致训练时用了RGB板子端传了BGR量化校准的统计就全错了。检查mean_values和std_values以及板子端rknn_inputs_set传的数据通道顺序。5.3 现象板子端推理速度远低于预期 → 原因部分层回退到 CPU → 解决看转换日志替换回退算子RKNN 转换日志里会标cpu的层如果检测头或 backbone 里的关键卷积回退了速度会掉一个数量级。常见回退算子是SiLU、GELU、LayerNorm。解决方法是替换成 RKNN 支持的等价算子或者升级 RKNN-Toolkit2 版本。5.4 现象rknn_init报版本不匹配 → 原因PC 端 toolkit 和板子端 runtime 版本不一致 → 解决对齐版本号PC 端rknn-toolkit2和板子端librknnrt.so必须同版本。查板子端版本strings /usr/lib/librknnrt.so | grep -i version。查 PC 端pip show rknn-toolkit2。不一致就重装别想着兼容。5.5 现象量化后小目标检测消失 → 原因小目标激活值被量化到 0 → 解决用 mmse 量化校准集加小目标样本INT8 的表示范围是 -128 到 127小目标的激活值本来就小如果校准集里小目标样本少统计出来的缩放因子偏大小目标激活直接归零。解决方法是校准集里保证 30% 以上是小目标样本量化算法选mmse它对小激活值的保留更好。6. 进阶技巧混合量化与逐层精度调优6.1 混合量化把敏感层留在 FP16RKNN-Toolkit2 支持混合量化你可以指定某些层不量化保持 FP16。YOLOv11 里对精度最敏感的是检测头的最后几层卷积和Sigmoid前的层。做法是在rknn.config()里传quantized_dtype的同时用rknn.hybrid_quantization_step1()和step2()做逐层分析。# 混合量化第一步生成量化配置文件 rknn.hybrid_quantization_step1( datasetdataset.txt, modelyolov11n.onnx, quantized_dtypeasymmetric_quantized-8 ) # 会生成 yolov11n.quantization.cfg里面列出每层量化建议 # 手动把敏感层的 quantized_dtype 改成 float16 rknn.hybrid_quantization_step2( model_inputyolov11n.onnx, data_inputyolov11n.data, model_quantization_cfgyolov11n.quantization.cfg )step1跑完后会生成一个 cfg 文件里面每层有quantized_dtype字段。把检测头最后几层改成float16重新step2精度通常能回来 23 个点速度只掉 10% 左右。这个取舍在边缘部署里很划算。6.2 逐层精度对比表下面这张表是我在 RK3588 上跑 YOLOv11n 的实测对比输入 640x640校准集 200 张供参考。不同数据集和场景会有差异但趋势一致。配置mAP0.5单帧耗时模型大小ONNX FP320.51245ms (CPU)10.2MBRKNN INT8 全量化0.47812ms (NPU)2.8MBRKNN 混合量化检测头 FP160.50314ms (NPU)3.6MBRKNN INT8 mmse0.48912ms (NPU)2.8MB从表里能看出来全量化 INT8 掉点约 3.4 个点混合量化只掉 0.9 个点速度代价 2ms。如果你的场景对小目标敏感混合量化是值得的。mmse相比默认normal能多回来 1 个点左右转换时间多几分钟但一次性的。6.3 一个我常犯的错误我早期做 RKNN 部署时总想着一步到位导出 ONNX 时就把所有能优化的都优化了结果 RKNN 转换报错回头查是哪一步引入的算子问题花了整整两天。后来我养成了一个习惯ONNX 导出保持最简只做simplify不做任何自定义算子替换量化配置先用默认跑通再逐项调。这样出问题时变量少定位快。另外校准集千万别偷懒用训练集的前 100 张那通常是按类别排序的分布极偏。我一般会写个脚本随机抽保证每个类别至少 10 张小目标样本单独补 30 张。这个习惯帮我省了很多次重新量化的时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站