香橙派5实战:YOLO11从训练到RKNN NPU部署全链路指南
1. 为什么要在香橙派5上折腾YOLO11全链路香橙派5这块板子RK3588的芯片6TOPS的NPU算力价格却只有同级别开发板的一半不到。我第一次拿到手的时候第一反应就是——这玩意儿不拿来跑视觉模型简直浪费。但真正开始折腾才发现从YOLO11训练到RKNN部署中间要跨过的坑比想象中多得多。这篇文章要聊的就是怎么在香橙派5上把YOLO11从零训练出来再通过RKNN工具链部署到NPU上跑起来。整个过程涉及模型训练、ONNX导出、RKNN转换、板端推理四个大环节每个环节都有各自的脾气。适合谁看如果你手头正好有一块RK3588的板子想跑自己的检测模型或者你正在评估RK3588能不能满足项目的视觉需求那这篇内容应该能帮你省下不少查文档和试错的时间。我用的环境是Ubuntu 22.04的PC做训练和转换香橙派5官方Ubuntu镜像做部署。YOLO11选的是Ultralytics的11n版本轻量、速度快适合边缘端。RKNN Toolkit2的版本是2.0.0b0这个版本对YOLO11的支持已经比较完善了。下面按实际操作的顺序把每个环节拆开讲。2. 环境搭建与工具链选型2.1 训练端环境配置的取舍训练端我建议直接用带NVIDIA显卡的机器别想着在香橙派5上训练。RK3588的CPU性能虽然不错但训练YOLO11这种模型没有CUDA加速的话一个epoch可能要跑几个小时完全不现实。我用的是一台RTX 3060的台式机12GB显存跑YOLO11n的640x640输入batch size设16显存占用大概在8GB左右刚好够用。Python环境用conda建一个独立环境Python版本选3.10。为什么不用3.11或3.12因为RKNN Toolkit2在3.10上的兼容性最好官方文档也是基于3.10写的。PyTorch选2.1.0版本对应CUDA 11.8的cu118版本。Ultralytics直接pip install ultralytics就行但要注意版本我用的8.3.0这个版本对YOLO11的支持是完整的。conda create -n yolo11 python3.10 conda activate yolo11 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics8.3.0 pip install onnx1.15.0 onnxruntime1.17.0ONNX的版本要特别注意1.15.0是经过验证能和RKNN Toolkit2配合的版本。我试过1.16和1.17转换的时候会报一些奇怪的算子不支持错误换回1.15就正常了。onnxruntime装CPU版本就行主要用来验证导出的ONNX模型推理结果是否正确。2.2 RKNN Toolkit2的安装与验证RKNN Toolkit2是瑞芯微提供的模型转换和推理工具包跑在PC端做模型转换板端用RKNN Runtime做推理。安装方式有两种pip直接装或者从GitHub拉源码装。我建议用pip装省事。pip install rknn-toolkit22.0.0b0装完之后验证一下from rknn.api import RKNN rknn RKNN() print(rknn.version())如果输出版本号就说明装好了。这里有个坑要注意RKNN Toolkit2对numpy的版本有要求必须是1.26.4以下如果numpy版本太高import的时候会报错。我一开始用numpy 2.0直接崩了降回1.26.4就好了。板端这边香橙派5的官方Ubuntu镜像里已经预装了RKNN Runtime的库但版本可能比较老。我建议去瑞芯微的GitHub仓库下载最新的rknpu2运行时库替换掉系统里的旧版本。具体操作是把librknnrt.so拷贝到/usr/lib/目录下然后ldconfig刷新一下。注意板端RKNN Runtime的版本必须和PC端RKNN Toolkit2的版本匹配否则转换出来的模型在板端加载会失败。我用的组合是PC端2.0.0b0 板端2.0.0b0这个组合是验证过的。2.3 香橙派5系统准备与NPU驱动检查香橙派5的Ubuntu镜像刷好之后第一件事是检查NPU驱动是否正常。运行cat /sys/kernel/debug/rknpu/version如果输出类似“RKNPU driver version: 0.9.6”这样的信息说明NPU驱动已经加载了。如果没有这个文件可能需要更新内核或者手动加载驱动模块。另一个要检查的是RGARaster Graphic Acceleration和MPPMedia Process Platform这两个硬件加速模块。RGA负责图像缩放和格式转换MPP负责视频编解码。YOLO11推理的时候图像预处理用RGA来做比CPU快很多。ls /dev/rga /dev/mpp_service这两个设备节点存在就说明驱动正常。如果不存在需要重新编译内核或者找官方提供的驱动包安装。3. YOLO11模型训练与导出细节3.1 数据集准备与训练参数设置数据集用YOLO格式目录结构是这样的dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml里配置好路径和类别数。我这次用的自建数据集5个类别大概3000张图。训练参数方面YOLO11n的默认配置其实已经调得不错了但有几个参数我习惯改一下from ultralytics import YOLO model YOLO(yolo11n.pt) model.train( datadata.yaml, epochs100, imgsz640, batch16, device0, workers8, optimizerAdamW, lr00.001, patience20, saveTrue, pretrainedTrue )imgsz设640是因为RK3588的NPU对640x640的输入支持最好再大推理时间会明显增加。batch size根据显存来12GB显存跑16没问题。patience设20意思是20个epoch验证集指标不提升就早停避免过拟合。训练完之后用验证集跑一下mAPmetrics model.val() print(metrics.box.map) # mAP50-95 print(metrics.box.map50) # mAP50我这次训练的结果是mAP50大概0.89mAP50-95大概0.67对于5个类别的检测任务来说够用了。3.2 导出ONNX模型的正确姿势训练完的.pt模型不能直接转RKNN必须先导出成ONNX。Ultralytics提供了export接口model YOLO(runs/detect/train/weights/best.pt) model.export( formatonnx, imgsz640, batch1, simplifyTrue, opset12, dynamicFalse )这里有几个关键点。opset选12不要选太高的版本RKNN Toolkit2对opset 12的支持最稳定。simplifyTrue会用onnx-simplifier对模型做简化去掉一些冗余算子转换的时候成功率更高。dynamicFalse表示固定输入尺寸RKNN转换的时候不需要动态shape固定尺寸性能更好。导出之后用onnxruntime验证一下ONNX模型的输出import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name dummy np.random.randn(1, 3, 640, 640).astype(np.float32) outputs sess.run(None, {input_name: dummy}) print(outputs[0].shape)YOLO11的ONNX输出shape是(1, 84, 8400)84是4个坐标加80个类别分数COCO数据集8400是预测框数量。如果是自定义数据集类别数不同这个维度会变。实操心得导出ONNX的时候如果遇到“Unsupported operator”的错误大概率是opset版本太高。降到12或者11试试。另外如果模型里有自定义算子需要先在PyTorch里替换成标准算子再导出。3.3 ONNX模型结构分析与RKNN适配性评估在转RKNN之前最好先看一下ONNX模型的结构确认没有RKNN不支持的算子。用netron打开ONNX文件或者用代码打印节点信息import onnx model onnx.load(best.onnx) for node in model.graph.node: print(node.op_type)YOLO11主要用到的算子有Conv、Concat、Resize、Sigmoid、Mul、Add、Transpose、Softmax等这些都是RKNN支持的。需要特别注意的是SiLU激活函数YOLO11用的是SiLURKNN Toolkit2从1.6版本开始就支持SiLU了所以没问题。如果模型里有GridSample、NonMaxSuppression这类算子RKNN可能不支持需要把NMS后处理从模型里拿出来放到CPU上做。YOLO11的导出默认是不带NMS的所以这一步可以跳过。4. RKNN模型转换与量化调优4.1 RKNN转换脚本编写与参数解读RKNN转换的核心是写一个转换脚本配置好模型路径、目标平台、量化方式等参数。下面是我用的脚本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.onnx) if ret ! 0: print(Load ONNX failed) exit(ret) # 构建RKNN模型 ret rknn.build(do_quantizationTrue, datasetdataset.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)mean_values和std_values是预处理参数。YOLO11训练的时候输入是0-1之间的浮点数而RKNN推理时输入是0-255的uint8所以需要做归一化。mean设0std设255就是把输入除以255映射到0-1范围。target_platform必须设成rk3588不能设成rk3588s或者其他否则转换出来的模型在板端加载会报错。quantized_dtype选asymmetric_quantized-8这是8位非对称量化精度和速度的平衡最好。optimization_level设3表示最高级别的图优化。4.2 量化数据集准备与精度调优do_quantizationTrue的时候需要提供一个量化数据集也就是dataset.txt文件。这个文件里列出了一些图片的路径RKNN会用这些图片来做量化校准。./quant_images/001.jpg ./quant_images/002.jpg ./quant_images/003.jpg ...量化图片的数量建议在100-200张之间太少会导致量化精度下降太多会拖慢转换速度。图片要覆盖各种场景最好从训练集里随机抽不要只用一类图片。我这次用了150张量化图片转换出来的RKNN模型在板端跑mAP50从0.89降到了0.86降了3个点可以接受。如果精度降得太多可以试试混合量化把某些层保持浮点rknn.config( ..., quantized_dtypeasymmetric_quantized-8, quantized_algorithmnormal, quantized_methodchannel )quantized_algorithm选normalquantized_method选channel这两个组合对YOLO系列模型的量化精度最好。我试过用kl_divergence算法精度反而更差。注意量化数据集里的图片尺寸不需要和模型输入尺寸一致RKNN会自动做resize。但图片的格式必须是RGB如果是BGR需要提前转换。4.3 转换结果验证与常见报错处理转换完成后RKNN Toolkit2提供了仿真推理功能可以在PC上验证RKNN模型的输出rknn.load_rknn(best.rknn) rknn.init_runtime(targetrk3588) outputs rknn.inference(inputs[dummy_input]) print(outputs[0].shape)如果仿真推理的结果和ONNX推理的结果差距不大说明转换成功。如果差距很大可能是量化参数没设对或者量化数据集有问题。常见的报错和处理方法报错信息原因解决方法E init_runtime: Invalid RKNN model模型版本不匹配检查PC端和板端RKNN版本是否一致E build: Unsupport op: GridSample算子不支持替换算子或把该部分放到CPUE load_onnx: Invalid ONNX modelONNX版本问题降级onnx到1.15.0W quantize: Quantize dataset is too small量化图片太少增加到100张以上5. 板端部署与NPU推理实战5.1 板端环境配置与模型传输板端这边先把RKNN模型传到香橙派5上。用scp或者adb都行scp best.rknn orangepi192.168.1.100:/home/orangepi/models/然后确认板端的RKNN Runtime库版本strings /usr/lib/librknnrt.so | grep -i version如果版本和PC端不一致去瑞芯微GitHub下载对应的rknpu2运行时库替换掉系统里的。板端推理的Python脚本大概长这样from rknnlite.api import RKNNLite import cv2 import numpy as np rknn RKNNLite() ret rknn.load_rknn(best.rknn) ret rknn.init_runtime(core_maskRKNNLite.NPU_CORE_0_1_2) img cv2.imread(test.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img np.expand_dims(img, axis0) outputs rknn.inference(inputs[img])core_mask设成NPU_CORE_0_1_2表示用三个NPU核心一起跑RK3588有三个NPU核心可以并行推理。如果模型比较小用一个核心就够了设成NPU_CORE_0。5.2 推理性能实测与优化技巧我实测下来YOLO11n在RK3588上的推理时间大概是25-30ms也就是30-40 FPS。这个速度对于大多数边缘视觉应用来说够用了。如果开三个NPU核心推理时间可以降到15ms左右但功耗会上去。优化技巧方面有几个点可以关注第一图像预处理用RGA来做。OpenCV的resize是CPU做的640x640的图大概要5ms用RGA可以降到1ms以内。RGA的Python接口在rknpu2的示例代码里有可以直接拿来用。第二后处理用C写。Python做NMS后处理大概要10ms用C可以降到2ms以内。如果对帧率要求高建议把后处理用C实现通过pybind11暴露给Python调用。第三模型输入尺寸不要盲目加大。640x640已经能覆盖大多数场景如果换成1280x1280推理时间会翻倍但精度提升有限。5.3 完整推理Pipeline搭建一个完整的推理Pipeline包括图像采集、预处理、NPU推理、后处理、结果输出。我用的是USB摄像头通过OpenCV的VideoCapture采集cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: break # 预处理 img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img np.expand_dims(img, axis0) # NPU推理 outputs rknn.inference(inputs[img]) # 后处理 boxes, scores, classes post_process(outputs[0]) # 画框 for box, score, cls in zip(boxes, scores, classes): x1, y1, x2, y2 box cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{cls}:{score:.2f}, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow(result, frame) if cv2.waitKey(1) 0xFF ord(q): break后处理函数需要根据YOLO11的输出格式来写。YOLO11的输出是(1, 84, 8400)84是4个坐标加80个类别分数。需要先转置成(8400, 84)然后过滤掉置信度低的框再做NMS。def post_process(output, conf_thres0.25, iou_thres0.45): output output[0].transpose(1, 0) # (8400, 84) boxes output[:, :4] scores output[:, 4:] class_ids np.argmax(scores, axis1) confidences np.max(scores, axis1) mask confidences conf_thres boxes boxes[mask] confidences confidences[mask] class_ids class_ids[mask] # NMS indices cv2.dnn.NMSBoxes( boxes.tolist(), confidences.tolist(), conf_thres, iou_thres ) return boxes[indices], confidences[indices], class_ids[indices]实操心得YOLO11的输出坐标是cxcywh格式需要转换成x1y1x2y2才能画框。转换公式是x1cx-w/2, y1cy-h/2, x2cxw/2, y2cyh/2。别忘了乘以原图的缩放比例。6. 踩坑记录与性能调优经验6.1 常见问题速查与排查思路整个流程走下来我遇到的最典型的问题有这么几个第一个是RKNN模型加载失败报“Invalid RKNN model”。这个问题90%的情况是PC端和板端的RKNN版本不一致。解决方法很简单统一版本就行。我现在的做法是PC端装什么版本板端就换什么版本的librknnrt.so。第二个是推理结果全乱框的位置完全不对。这个问题一般是预处理参数没设对。检查mean_values和std_values确认和训练时的归一化方式一致。YOLO11训练时输入是0-1所以mean0, std255。如果训练时用了其他归一化方式这里要对应修改。第三个是量化后精度掉太多mAP降了10个点以上。这种情况一般是量化数据集的问题。量化图片要覆盖各种场景数量要够最好从训练集里随机抽。另外可以试试把quantized_algorithm改成normalquantized_method改成channel。第四个是推理速度慢只有几FPS。检查core_mask有没有设对RK3588有三个NPU核心设成NPU_CORE_0_1_2可以并行推理。另外检查图像预处理是不是用CPU做的换成RGA可以省不少时间。6.2 NPU核心调度与多模型并行RK3588的三个NPU核心可以独立调度也可以组合使用。如果只有一个模型设成NPU_CORE_0_1_2可以让三个核心一起跑一个模型速度最快。如果有多个模型需要同时跑可以每个模型分配一个核心# 模型A用核心0 rknn_a.init_runtime(core_maskRKNNLite.NPU_CORE_0) # 模型B用核心1 rknn_b.init_runtime(core_maskRKNNLite.NPU_CORE_1) # 模型C用核心2 rknn_c.init_runtime(core_maskRKNNLite.NPU_CORE_2)这种调度方式适合多模型并行的场景比如同时跑检测和分类。但要注意三个核心共享内存带宽如果模型都很大并行跑可能会互相拖慢。6.3 从YOLO11到其他模型的迁移经验这套流程不只适用于YOLO11YOLOv8、YOLOv5、甚至RT-DETR都可以用类似的方式部署。区别主要在导出ONNX的步骤和后处理逻辑。YOLOv8的导出方式和YOLO11基本一样后处理也类似。RT-DETR的输出格式不同后处理需要单独写。如果要把DINOv3这类Transformer模型转到RKNN需要注意几点Transformer里的MultiHeadAttention算子RKNN支持但需要确认opset版本。另外Transformer模型对量化更敏感建议用混合量化把注意力层保持浮点。迁移的时候核心思路是一样的先导出ONNX确认算子支持然后写RKNN转换脚本准备量化数据集最后板端部署。区别只是模型结构不同需要调整的地方主要在预处理和后处理。最后分享一个小技巧RKNN Toolkit2的仿真推理功能可以在PC上验证模型转换的正确性不用每次都传到板子上试。仿真推理的结果和板端推理的结果基本一致可以省很多时间。但要注意仿真推理不支持RGA和MPP这些硬件加速模块所以性能数据只能参考不能作为最终依据。