资讯中心

RK3588 部署 YOLOv8 全流程:从 PyTorch 到 RKNN 量化与板端推理

📅 2026/9/29 1:53:48
RK3588 部署 YOLOv8 全流程:从 PyTorch 到 RKNN 量化与板端推理
1. 为什么选择 RK3588 跑 YOLOv8算力账与落地场景RK3588 这颗芯片在边缘视觉圈子里火起来不是没有道理的。它内置的 NPU 标称 6 TOPS 算力支持 INT8 量化推理配合三核 Cortex-A76 加五核 Cortex-A55 的 CPU 架构跑 YOLOv8n 这种轻量级检测模型时单帧推理能压到 20ms 以内。我实测过一块 8GB 内存的 RK3588 开发板YOLOv8n 输入 640x640INT8 量化后单核 NPU 推理稳定在 18-25ms 区间三核并行还能再往下压。这个性能放在智能安防、工业质检、车载辅助这些场景里已经完全够用了。很多人第一次接触这个流程时最大的困惑不是能不能跑而是从哪开始。PyTorch 训练出来的权重是浮点模型RK3588 的 NPU 只认 RKNN 格式中间要经过 ONNX 导出、量化校准、模型转换、板端部署这一整条链路。每一步都有坑而且坑和坑之间是串联的——前面一步没做对后面直接报错排查起来非常痛苦。这篇文章面向的是已经有一定深度学习基础、手里有 RK3588 开发板、想把 YOLOv8 真正跑起来的工程师。我会把从训练到部署的完整链路拆开讲重点放在那些官方文档里不会写、但实际调试中一定会遇到的问题上。整个流程涉及的核心工具链包括PyTorch 训练框架、ONNX 作为中间格式、RKNN-Toolkit2 做模型转换和量化、板端 RKNPU2 运行时做推理。先给一个全局的链路图景让你心里有数阶段输入输出核心工具模型训练自定义数据集PyTorch 权重 .ptultralytics格式导出.pt.onnxtorch.onnx.export模型转换.onnx.rknnRKNN-Toolkit2板端部署.rknn推理结果RKNPU2 Runtime这条链路上训练和导出在 PC 上完成转换可以在 PC 或板端完成部署一定在板端。量化校准需要一批有代表性的图片这一步直接决定最终精度掉多少。提示RKNN-Toolkit2 的版本和板端 RKNPU2 驱动的版本必须匹配否则会出现模型加载失败或推理结果异常。建议在开始之前先确认板端固件版本再选择对应版本的 Toolkit。2. 训练环境的搭建与 YOLOv8 模型训练要点2.1 环境配置的版本陷阱YOLOv8 的训练环境看起来简单pip install ultralytics就能跑但真正要导出 ONNX 并保证后续转换顺利版本控制非常关键。我踩过最深的坑是 PyTorch 版本和 ONNX opset 的兼容性问题。ultralytics 在不同版本里对 ONNX 导出的默认 opset 设置不一样opset 太高 RKNN-Toolkit2 不支持太低又会导致某些算子导出失败。我目前验证过比较稳的组合是Python 3.8 或 3.10PyTorch 2.0.1 或 2.1.0ultralytics 8.0.x 系列onnx 1.14.0onnxruntime 1.15.1安装命令大致如下conda create -n yolov8_rknn python3.10 conda activate yolov8_rknn pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics8.0.200 pip install onnx1.14.0 onnxruntime1.15.1 pip install onnxsimonnxsim这个工具后面会用到它的作用是对导出的 ONNX 模型做图优化把一些冗余的算子合并掉能显著减少 RKNN 转换时的算子兼容问题。2.2 自定义数据集训练的关键参数YOLOv8 训练自己的数据集数据组织格式是 YOLO 标准的 txt 标注每行是class_id x_center y_center width height坐标都归一化到 0-1。目录结构长这样dataset/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml里配置好路径和类别名。训练命令本身不复杂yolo detect train datadataset/data.yaml modelyolov8n.pt epochs200 imgsz640 batch16但有几个参数对后续部署影响很大必须提前想清楚。第一个是imgsz训练时的输入尺寸最好和部署时保持一致RK3588 上常用 640x640如果你训练用 640 部署用 320精度会掉得莫名其妙。第二个是rect参数默认训练会做矩形推理但导出 ONNX 时如果输入是动态尺寸RKNN 转换会麻烦很多建议训练时就固定成正方形输入。关于freeze参数如果你想在预训练权重基础上微调可以冻结 backbone 的前若干层减少训练时间。但要注意冻结层数太多会导致模型对自定义数据集的适应能力下降我一般只冻结前 5 层左右。训练完成后损失函数曲线可以用 ultralytics 自带的工具画出来观察是否过拟合。如果验证集 loss 在后期开始上升说明该早停了。这些细节看起来和部署无关但模型质量直接决定量化后的精度底线。2.3 训练完成后的模型自检训练结束后别急着导出先在 PC 上用验证集跑一遍确认 mAP 正常。然后拿几张实际场景的图片做推理测试看看检测框是否合理。我遇到过训练指标很好看但实际推理一塌糊涂的情况原因是标注数据里有一批图片的标注框偏移了训练时被平均掉了验证集又恰好没覆盖到。yolo detect val modelruns/detect/train/weights/best.pt datadataset/data.yaml yolo detect predict modelruns/detect/train/weights/best.pt sourcetest_images/确认无误后best.pt就是后续流程的起点。3. 从 PyTorch 到 ONNX导出环节的隐藏细节3.1 导出命令与 opset 选择ultralytics 提供了直接的导出接口yolo export modelbest.pt formatonnx opset12 simplifyTrue这里opset12是我反复验证后比较稳妥的选择。RKNN-Toolkit2 对 opset 11 到 13 的支持最好opset 12 在算子覆盖和兼容性之间平衡得不错。simplifyTrue会调用 onnxsim 做图优化这一步能去掉很多无用的 Identity 节点和常量折叠。导出完成后你会得到一个best.onnx文件。但别以为这就完事了直接拿这个 ONNX 去转 RKNN大概率会遇到算子不支持或者输出节点不对的问题。3.2 检查 ONNX 模型的输入输出用 Netron 打开 ONNX 文件或者用代码打印输入输出信息import onnx model onnx.load(best.onnx) for inp in model.graph.input: print(Input:, inp.name, [d.dim_value for d in inp.type.tensor_type.shape.dim]) for out in model.graph.output: print(Output:, out.name, [d.dim_value for d in out.type.tensor_type.shape.dim])YOLOv8 导出的 ONNX 通常有多个输出头对应不同尺度的检测结果。RKNN 转换时需要明确指定输出节点如果输出节点选错了板端推理出来的结果维度对不上后处理直接崩。3.3 动态维度改成静态YOLOv8 默认导出的 ONNX 输入是动态 batch 和动态尺寸这对 RKNN 转换很不友好。RKNN 更偏好固定输入尺寸。可以在导出时指定yolo export modelbest.pt formatonnx opset12 simplifyTrue imgsz640 batch1这样导出的 ONNX 输入就是固定的1x3x640x640。如果已经导出了动态模型也可以用 onnx 的 API 手动改import onnx from onnx import shape_inference model onnx.load(best.onnx) model shape_inference.infer_shapes(model) # 手动设置输入维度为固定值 model.graph.input[0].type.tensor_type.shape.dim[0].dim_value 1 model.graph.input[0].type.tensor_type.shape.dim[2].dim_value 640 model.graph.input[0].type.tensor_type.shape.dim[3].dim_value 640 onnx.save(model, best_fixed.onnx)3.4 一个容易被忽略的坑输出节点命名YOLOv8 导出的 ONNX 输出节点名字通常是output0这种自动生成的。RKNN 转换时如果不指定输出节点它会自己推断有时候推断出来的输出顺序和你想的不一样。我建议在导出后手动确认输出节点名称并在 RKNN 转换配置里显式指定。注意ONNX 模型导出后务必用 onnxruntime 在 PC 上跑一遍推理确认输出结果和 PyTorch 一致。这一步是后面所有工作的基准如果这里就不对后面全是白费功夫。4. RKNN 模型转换与 INT8 量化实战4.1 RKNN-Toolkit2 环境搭建RKNN-Toolkit2 是瑞芯微官方提供的模型转换工具运行在 PC 上x86 Linux 环境。安装方式是从官方仓库下载 whl 包pip install rknn_toolkit2-1.5.2-cp310-cp310-linux_x86_64.whl版本选择要和板端 RKNPU2 驱动匹配。我用的板端固件是 1.5.0 版本Toolkit 用 1.5.2兼容性没问题。如果你用的是更新的固件Toolkit 也要相应升级。安装完成后可以验证from rknn.api import RKNN print(RKNN Toolkit2 loaded)4.2 转换脚本的完整写法RKNN 转换的核心是一个 Python 脚本配置模型路径、目标平台、量化方式等。下面是一个我实际使用的完整脚本from rknn.api import RKNN rknn RKNN(verboseTrue) # 配置模型预处理 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, quantized_dtypeasymmetric_quantized-8, optimization_level3 ) # 加载 ONNX 模型 ret rknn.load_onnx(modelbest_fixed.onnx) if ret ! 0: print(Load ONNX failed) exit(ret) # 构建 RKNN 模型指定量化校准数据集 ret rknn.build(do_quantizationTrue, datasetcalibration.txt) if ret ! 0: print(Build RKNN failed) exit(ret) # 导出 RKNN 模型 ret rknn.export_rknn(best.rknn) if ret ! 0: print(Export RKNN failed) exit(ret) rknn.release()这里有几个关键点需要展开说。mean_values和std_values是预处理参数。YOLOv8 训练时输入是 0-1 归一化的 RGB 图像所以这里 mean 设为 0std 设为 255表示把 0-255 的输入除以 255。如果你在板端前处理里已经做了归一化这里就要相应调整否则会出现重复归一化导致精度暴跌。quantized_dtype选asymmetric_quantized-8是 INT8 非对称量化这是 RK3588 NPU 支持最好的量化方式。对称量化在某些层上会有精度损失。optimization_level3是最高优化级别会做一些算子融合和内存优化但偶尔也会引入问题。如果转换后精度异常可以降到 2 试试。4.3 量化校准数据集的选择calibration.txt是一个文本文件每行是一张校准图片的路径。这些图片必须来自你的实际应用场景数量在 100 到 500 张之间比较合适。太少会导致量化参数估计不准太多会拖慢转换速度。我一般从训练集里随机抽 200 张再额外加 50 张验证集里的图片确保覆盖各种光照和场景。校准图片的预处理方式必须和板端推理时完全一致包括 resize、归一化、通道顺序。# calibration.txt 示例 ./calib/img_001.jpg ./calib/img_002.jpg ./calib/img_003.jpg4.4 量化精度损失的定位方法INT8 量化后精度下降是正常的一般 mAP 掉 1-3 个点是可接受的。如果掉超过 5 个点就要排查原因。常见的排查路径现象可能原因排查方法精度暴跌预处理参数不匹配检查 mean/std 和板端前处理部分类别失效校准集不均衡增加该类别图片到校准集输出全乱输出节点选错用 Netron 确认输出节点转换报错算子不支持查看 verbose 日志定位算子RKNN-Toolkit2 提供了仿真推理功能可以在 PC 上模拟板端推理结果rknn.load_rknn(best.rknn) rknn.init_runtime(targetrk3588) outputs rknn.inference(inputs[test_img])把仿真结果和 ONNX 推理结果对比就能定位是量化问题还是后处理问题。提示如果某些算子 RKNN 不支持可以尝试在 ONNX 导出时用simplify做算子替换或者手动修改模型结构把不支持的算子换掉。YOLOv8 里常见的 SiLU 激活函数在旧版 Toolkit 里支持不好升级到 1.5.x 后基本没问题。5. 板端部署RKNPU2 运行时与推理代码5.1 板端环境确认RK3588 板端需要确认 RKNPU2 驱动和运行时库已经安装。一般官方固件里都带了可以用以下命令检查cat /sys/kernel/debug/rknpu/version如果输出类似RKNPU driver version: 0.9.2就说明驱动正常。运行时库是librknnrt.so通常在/usr/lib/目录下。Python 端需要安装rknn_toolkit_lite2这个包是专门给板端用的轻量级推理接口pip install rknn_toolkit_lite2-1.5.2-cp310-cp310-linux_aarch64.whl5.2 板端推理代码的完整实现板端推理的核心流程是加载 RKNN 模型、初始化运行时、前处理、推理、后处理。下面是一个完整的 Python 示例import numpy as np import cv2 from rknnlite.api import RKNNLite class YOLOv8RKNN: def __init__(self, model_path): self.rknn RKNNLite() ret self.rknn.load_rknn(model_path) if ret ! 0: raise RuntimeError(Load RKNN failed) ret self.rknn.init_runtime(core_maskRKNNLite.NPU_CORE_0_1_2) if ret ! 0: raise RuntimeError(Init runtime failed) self.input_size (640, 640) def preprocess(self, img): img_resized cv2.resize(img, self.input_size) img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) img_input np.expand_dims(img_rgb, axis0) return img_input def inference(self, img): input_data self.preprocess(img) outputs self.rknn.inference(inputs[input_data]) return outputs def postprocess(self, outputs, conf_thres0.25, iou_thres0.45): # YOLOv8 输出解码 predictions np.squeeze(outputs[0]).T scores np.max(predictions[:, 4:], axis1) predictions predictions[scores conf_thres, :] scores scores[scores conf_thres] if len(scores) 0: return [] class_ids np.argmax(predictions[:, 4:], axis1) boxes predictions[:, :4] # 转换 xywh 到 xyxy boxes_xyxy np.zeros_like(boxes) boxes_xyxy[:, 0] boxes[:, 0] - boxes[:, 2] / 2 boxes_xyxy[:, 1] boxes[:, 1] - boxes[:, 3] / 2 boxes_xyxy[:, 2] boxes[:, 0] boxes[:, 2] / 2 boxes_xyxy[:, 3] boxes[:, 1] boxes[:, 3] / 2 # NMS indices cv2.dnn.NMSBoxes( boxes_xyxy.tolist(), scores.tolist(), conf_thres, iou_thres ) results [] for i in indices: results.append({ box: boxes_xyxy[i], score: scores[i], class_id: class_ids[i] }) return results def release(self): self.rknn.release()core_maskRKNNLite.NPU_CORE_0_1_2表示使用三个 NPU 核心并行推理这是 RK3588 的独有优势。如果你的模型比较小单核就够用可以改成NPU_CORE_0减少功耗。5.3 后处理中的坐标还原YOLOv8 的输出是相对于输入尺寸 640x640 的归一化坐标需要还原到原图尺寸。这一步很容易出错尤其是图片做了 letterbox 填充的情况下。如果前处理用了 letterbox后处理必须做对应的逆变换否则检测框会偏移。我一般在前处理时记录缩放比例和填充偏移量def letterbox(self, img, new_shape(640, 640), color(114, 114, 114)): shape img.shape[:2] r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad int(round(shape[1] * r)), int(round(shape[0] * r)) dw, dh new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] dw / 2 dh / 2 img_resized cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) img_padded cv2.copyMakeBorder( img_resized, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor ) return img_padded, r, (left, top)后处理时用r和(left, top)把坐标还原回去。这个细节不做检测框会整体偏移而且偏移量随图片长宽比变化非常隐蔽。5.4 性能实测与优化在 RK3588 上跑 YOLOv8n我实测的数据如下配置单帧耗时帧率单核 NPU28ms35 FPS三核 NPU18ms55 FPS三核 零拷贝15ms66 FPS零拷贝是指用 RKNN 的inference接口直接传入 numpy 数组避免内存拷贝。如果追求极致性能可以用 C 接口配合 DMA 缓冲区还能再快一点。CPU 占用方面前处理和后处理是瓶颈。如果帧率要求高可以把 resize 和归一化用 RGA 硬件加速RK3588 有独立的 RGA 模块专门做图像缩放和格式转换。6. 调试过程中最容易卡住的几个问题6.1 模型加载失败版本不匹配最常见的报错是load_rknn failed或者init_runtime failed。九成以上是 Toolkit 版本和板端驱动版本不匹配。排查方法是分别打印两边的版本号# 板端 cat /sys/kernel/debug/rknpu/version # PC 端 python -c from rknn.api import RKNN; print(RKNN().version)两个版本号的主版本号必须一致次版本号可以有小差异。如果差太多要么升级板端固件要么降级 Toolkit。6.2 推理结果全为零或全为同一类别这种情况通常是量化校准出了问题。检查校准集图片是否和推理时的输入分布一致。我遇到过一次校准集用的是白天场景实际推理是夜间红外图像量化参数完全不对输出全是背景。解决办法是把实际场景的图片加入校准集重新转换。校准集要覆盖各种光照、角度、目标尺度。6.3 检测框偏移或尺寸不对前面提到的 letterbox 逆变换问题。还有一种可能是 RKNN 输出的坐标格式和预期不一致。RKNN 转换后输出可能是[1, 84, 8400]或者[1, 8400, 84]取决于转换时的配置。用rknn.inference打印输出 shape 确认一下然后在后处理里做对应转置。6.4 多线程推理时的资源竞争如果你在板端开了多个线程同时推理要注意 RKNN 运行时不是线程安全的。每个线程需要独立的 RKNNLite 实例或者用锁串行化。我一般用单线程推理加队列的方式避免资源竞争。注意RKNN 模型加载后init_runtime只需要调用一次。重复调用会导致内存泄漏长时间运行后板子会卡死。7. 从能跑到好用几个实战优化技巧7.1 模型剪枝与通道裁剪如果 YOLOv8n 还是太慢可以考虑对模型做剪枝。用 ultralytics 训练时加prune参数或者在 ONNX 层面用工具做通道裁剪。剪枝后的模型需要重新微调精度会掉一些但推理速度能提升 20%-30%。7.2 输入分辨率的选择640x640 是精度和速度的平衡点。如果场景里目标比较大可以降到 416x416速度能提升近一倍精度掉 2-3 个点。如果目标很小比如远距离检测反而要升到 800x800 以上但 RK3588 的 NPU 对非 640 尺寸的支持需要额外验证。7.3 多模型并行RK3588 有三个 NPU 核心可以同时跑三个模型。比如一个跑检测一个跑分类一个跑 OCR。用core_mask分别指定核心互不干扰。这个特性在复杂视觉系统里非常有用。7.4 温度与功耗管理RK3588 满负荷跑 NPU 时发热不小长时间运行需要加散热片。如果板子温度超过 80 度NPU 会降频推理速度明显下降。可以在代码里监控温度cat /sys/class/thermal/thermal_zone0/temp返回值除以 1000 就是摄氏度。超过 75 度就要考虑加风扇或者降低推理频率。7.5 模型加密与授权如果部署到商业产品里RKNN 模型可以加密。RKNN-Toolkit2 支持在导出时设置加密密钥板端加载时需要提供相同密钥。这个功能在防止模型被逆向时有用但会增加一点加载时间。整个流程走下来从训练到部署顺利的话两三天能跑通不顺利的话在量化精度和板端调试上卡一两周也正常。关键是要有耐心每一步都做验证不要跳步。ONNX 导出后在 PC 上验证RKNN 转换后用仿真验证板端部署后先用单张图片验证确认无误再上视频流。这个逐级验证的习惯能帮你省下大量排查时间。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案