香橙派5基于RK3588 NPU的yolov5模型优化与部署实战
1. 环境准备:从零开始的软硬件配置
拿到香橙派5,看着这块小小的板子,你可能和我当初一样,既兴奋又有点无从下手。毕竟,它和树莓派这类通用开发板不太一样,核心的吸引力就在于那颗瑞芯微RK3588芯片里的NPU(神经网络处理单元)。这东西说白了,就是专门为AI计算设计的“加速引擎”,能让我们的yolov5模型跑得飞快。但要想用好它,第一步的环境搭建就得稳扎稳打,不然后面全是坑。
首先,你得把硬件凑齐。香橙派5主板是必须的,官方推荐搭配他们的散热外壳,实测下来,跑满NPU的时候芯片还是挺热的,有个好散热心里踏实。电源一定要用官方推荐的12V/3A那个,别用手机充电器凑合,供电不稳可能导致各种玄学问题。存储方面,我建议直接上64GB或以上的高速TF卡,系统运行和存放模型都更流畅。摄像头的话,普通的USB摄像头就行,我手头用的就是一个几十块钱的免驱摄像头,完全够用。显示器、HDMI线、键鼠这些基础外设就不多说了。
软件环境是重头戏,也是第一个容易迷糊的地方。整个流程涉及到三个系统环境:Windows(用于模型训练和初步转换)、Ubuntu Linux(用于核心的模型转换)、香橙派5自身的Debian系统(用于最终部署和推理)。很多朋友想偷懒,试图在Windows上一条龙搞定,但RKNN Toolkit2这个核心转换工具对Linux依赖很深,所以Ubuntu环境是绕不开的。如果你没有闲置电脑装Ubuntu,用VMware或VirtualBox装个虚拟机是最方便的选择,分配个4核CPU、8GB内存、50GB硬盘空间就足够了。我就是在Windows 11上用VMware装的Ubuntu 20.04,整个过程很顺利。
在香橙派5上,我们需要安装官方的Debian或Ubuntu系统。去香橙派官网下载最新的镜像,用BalenaEtcher这类工具烧录到TF卡里就行。第一次启动后,建议先做两件事:一是通过 sudo apt update && sudo apt upgrade -y 更新系统到最新状态;二是安装一些基础工具,比如 vim, git, python3-pip。网络配置也要检查好,确保能稳定 ping 通外网,因为后面需要在线安装一些Python包。
注意:香橙派5的NPU驱动和运行时库是预装在官方镜像里的,所以我们不需要单独安装NPU驱动,这省了不少事。但务必确认你下载的是较新的镜像(2023年3月之后的版本),早期的镜像可能对RKNN的支持不够完善。
2. 模型训练:为NPU准备“专属食材”
模型训练就像是为NPU这个“大厨”准备食材。如果你随便丢一个网上找的yolov5模型给它,它很可能“炒”不出来,或者“炒”得很难吃(性能差)。所以,我们必须按照NPU“大厨”的食谱(指定版本的代码)来准备食材(训练模型)。这是整个流程里最关键,也最容易出错的一步。
为什么非要指定版本?因为RKNN转换工具链对模型算子(即模型里的各种计算层,如卷积、激活函数等)的支持是固定的。yolov5官方主分支一直在快速迭代,会引入新的算子或算子实现方式,这些可能超出了当前RKNN工具链的识别范围。瑞芯微官方明确给出了一个他们测试通过的yolov5提交版本,其哈希值是 c5360f6e7009eb4d05f14d1cc9dae0963e949213。我们必须使用这个版本的代码来训练模型。
具体怎么操作呢?首先,打开yolov5的GitHub仓库。在仓库页面左上角,你会看到一个显示分支或提交哈希的按钮,点击它,在搜索框里粘贴上面那串长长的哈希值并回车。页面会自动跳转到那个特定的历史版本。这时候整个代码库的状态就定格在了瑞芯微测试通过的那个时刻。点击绿色的“Code”按钮,把整个代码库下载到你的Windows电脑上。接着,去同一个仓库的Releases页面,找到对应版本的预训练权重文件(比如 yolov5s.pt)并下载,把它放到刚才下载的代码目录里。这个预训练权重是我们训练的起点,能大大加快训练速度并提升效果。
接下来就是准备你自己的数据集了。你需要用LabelImg等工具标注图片,生成yolo格式的标签文件(每个图片对应一个.txt文件)。然后,在代码目录的 data 文件夹下,创建一个新的 .yaml 配置文件,比如叫 my_dataset.yaml。这个文件里要指明训练集、验证集图片的路径、类别数量和你类别的名字。结构大概长这样:
# my_dataset.yaml
train: ../datasets/images/train/
val: ../datasets/images/val/
nc: 2 # 你的目标类别数,比如我识别‘猫’和‘狗’,就是2
names: ['cat', 'dog'] # 类别名称,顺序很重要
配置好数据后,就可以开始训练了。在命令行进入代码目录,运行训练命令。但这里有几个我踩过的坑,你必须留意:
- 路径和引号问题:在
train.py里指定数据配置文件的路径时,一定要用单引号,比如--data ‘./data/my_dataset.yaml‘。如果用双引号或者不加引号,在Windows上很可能报ValueError: too many dimensions ‘str‘这种让人摸不着头脑的错误。 - 虚拟内存不足:训练yolov5,尤其是用
yolov5s.pt这种稍大的模型时,对内存要求不低。如果你的电脑内存不大,很可能会遇到OSError: [WinError 1455] 页面文件太小,无法完成操作。解决办法是增加Windows的虚拟内存:设置->系统->关于->高级系统设置->性能设置->高级->虚拟内存更改。我把虚拟内存设置在Anaconda所在的D盘,大小设了50GB。同时,把train.py里的--batch-size参数调小,我的轻薄本设成4才能跑起来。 - NumPy版本导致的类型错误:训练时可能会报错,提示
np.int相关问题。这是因为新版本NumPy弃用了np.int。解决办法很简单,用文本编辑器打开报错的那个.py文件(通常是utils/metrics.py或utils/loss.py),全局搜索np.int,全部替换成np.int_即可。 - 锚框(Anchor)计算错误:这是最隐蔽的一个坑。训练跑起来后,可能在计算损失(Loss)时崩溃,错误信息指向
loss.py。这是因为代码版本和模型结构有些不匹配。你需要修改loss.py文件里的两处代码。找到build_targets函数,将for i in range(self.nl):循环内的anchors = self.anchors[i]改为anchors, shape = self.anchors[i], p[i].shape。接着,在同一循环内找到indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1)))这一行,把它改为indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1)))。改完这两处,训练就能顺利进行了。
按照上面的步骤,你的模型就能开始训练了。训练完成后,会在 runs/train/exp/weights/ 目录下生成一个 best.pt 文件,这就是我们为NPU精心准备的“终极食材”。
3. 模型转换:从PyTorch到RKNN的“翻译”之旅
拿到 best.pt 文件,我们的工作只完成了三分之一。NPU不能直接理解PyTorch的模型,我们需要把它“翻译”成NPU能懂的格式——RKNN。这个过程需要两步“翻译”:先从PyTorch (.pt) 转到通用的ONNX格式,再从ONNX转到RKNN格式。这两步都是在为最终的部署做准备,任何一步出错,都会导致在香橙派5上跑不起来。
第一步:PT -> ONNX
这一步在Windows上完成就行。我们需要使用刚才训练用的那个特定版本的yolov5代码里的 export.py 脚本。但注意,不要直接运行!先打开 export.py 文件,找到里面的参数设置部分。瑞芯微的RKNN工具链对ONNX的算子集版本有要求,我们需要添加一个参数:--opset 11。这个 opset 参数指定了ONNX算子的版本,设为11能最大程度保证兼容性。
修改并保存后,在命令行运行转换命令。记得先把训练好的 best.pt 文件复制到 export.py 的同级目录下,或者用绝对路径指定它。
python export.py --weights best.pt --img 640 --batch 1 --include onnx --opset 11
这里解释一下参数:--img 640 指定了模型输入的图片尺寸,必须和训练时一致;--batch 1 表示我们导出单批次推理的模型,这对嵌入式部署是标准做法;--include onnx 告诉脚本我们只导出ONNX格式。
运行过程中,你可能会看到一些警告,比如“未使用的权重”之类的,只要不是关于 opset 的红色错误,一般可以忽略。转换成功后,你会得到一个 best.onnx 文件。这个文件就是模型的“中间语言”版本,它比PyTorch模型更标准化,能被各种推理框架识别。
第二步:ONNX -> RKNN(关键步骤,在Ubuntu中进行)
这是最核心、也最容易出问题的一步,必须在Ubuntu系统下进行。我们需要用到瑞芯微官方提供的 RKNN-Toolkit2 工具包。首先,在Ubuntu上安装它。建议使用Python虚拟环境来管理,避免包冲突。
# 创建并激活虚拟环境
python3 -m venv rknn_env
source rknn_env/bin/activate
# 安装RKNN-Toolkit2,具体wheel包名字和版本去瑞芯微官网下载页面找
pip install rknn_toolkit2-1.5.2+1fa95b5c-cp39-cp39-linux_x86_64.whl
安装完成后,我们需要写一个Python脚本来执行转换。这个脚本主要做三件事:1) 创建RKNN对象;2) 加载ONNX模型;3) 构建RKNN模型。下面是一个最基础的转换脚本示例:
from rknn.api import RKNN
if __name__ == '__main__':
# 1. 创建RKNN对象
rknn = RKNN(verbose=True)
# 2. 模型配置
print('--> Config model')
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588')
print('done')
# 3. 加载ONNX模型
print('--> Loading model')
ret = rknn.load_onnx(model='./best.onnx')
if ret != 0:
print('Load model failed!')
exit(ret)
print('done')
# 4. 构建RKNN模型
print('--> Building model')
ret = rknn.build(do_quantization=True, dataset='./dataset.txt')
if ret != 0:
print('Build model failed!')
exit(ret)
print('done')
# 5. 导出RKNN模型
print('--> Export rknn model')
ret = rknn.export_rknn('./best.rknn')
if ret != 0:
print('Export rknn model failed!')
exit(ret)
print('done')
# 6. 释放资源
rknn.release()
这里有几个关键点:
target_platform:必须设为'rk3588',告诉工具链我们是为香橙派5的芯片生成模型。do_quantization:强烈建议开启量化(设为True)。量化会将模型权重从浮点数(FP32)转换为整数(INT8),这能大幅减少模型体积、提升推理速度,并且对NPU的利用率更高。实测下来,量化后速度能有数倍提升,而精度损失在可接受范围内。dataset:量化需要一个小型校准数据集(大概100-200张图片)来统计激活值的分布。你需要准备一个文本文件(如dataset.txt),里面每行是校准图片的绝对路径。这些图片最好来自你的训练集或验证集,有代表性。
运行这个脚本,如果一切顺利,你就会得到最终的 best.rknn 文件。把这个文件拷贝到香橙派5上,我们的“翻译”工作就大功告成了。如果构建失败,通常会是ONNX模型中包含了不支持的算子,这时需要回到上一步,检查ONNX导出时是否有警告,或者尝试用Netron工具可视化ONNX模型,看看有没有奇怪的结构。
4. 香橙派5部署与实时推理实战
模型转换成功,拿到 best.rknn 文件,胜利就在眼前了。接下来就是在香橙派5上部署并跑起实时视频推理。这一步虽然代码量不大,但涉及到性能调优和资源管理,直接决定了最终是“幻灯片”还是“流畅直播”的效果。
首先,把 best.rknn 文件传到香橙派5上。然后在香橙派5上,我们同样需要安装RKNN的Python推理库,不过这个库通常已经包含在官方系统镜像里了。我们可以通过 pip list | grep rknn 来检查。如果没有,可以去瑞芯微官网下载对应平台(aarch64)的 rknn_toolkit_lite2 轮子文件进行安装。
核心的推理代码结构如下,我把它写成一个类,方便管理:
import cv2
import numpy as np
from rknnlite.api import RKNNLite
class YOLOv5RKNN:
def __init__(self, rknn_model_path):
# 初始化RKNN Lite
self.rknn_lite = RKNNLite()
print('--> Load RKNN model')
ret = self.rknn_lite.load_rknn(rknn_model_path)
if ret != 0:
print('Load RKNN model failed!')
exit(ret)
print('done')
# 初始化运行时环境,这里指定使用NPU核心
print('--> Init runtime environment')
# 使用RK3588的NPU核心0和1。RK3588有3个NPU核心,可以根据需要分配。
ret = self.rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1)
if ret != 0:
print('Init runtime environment failed!')
exit(ret)
print('done')
# 预设的输入输出尺寸
self.input_size = (640, 640)
self.conf_threshold = 0.5
self.nms_threshold = 0.6
def preprocess(self, img):
# 将OpenCV读取的BGR图像预处理为模型输入
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_resized = cv2.resize(img_rgb, self.input_size)
# 归一化并转换维度为 NCHW
input_data = np.expand_dims(img_resized / 255.0, axis=0).astype(np.float32)
return input_data
def infer(self, img):
input_data = self.preprocess(img)
# 执行推理
outputs = self.rknn_lite.inference(inputs=[input_data])
# outputs是一个列表,包含模型的所有输出层数据
# 需要根据你转换模型时的输出结构来解析
return self.postprocess(outputs, img.shape)
def postprocess(self, outputs, orig_shape):
# 这里是后处理逻辑,包括置信度过滤、非极大值抑制(NMS)和坐标还原
# 由于yolov5输出格式固定,我们可以根据输出维度来解析
# 注意:RKNN输出的数据是量化后的,可能需要反量化(如果模型是量化版)
# 瑞芯微的RKNN Lite库通常会自动处理反量化,我们拿到的是浮点数结果
# ... (具体的解析和NMS代码,因篇幅略去,可参考官方示例)
boxes, scores, class_ids = [], [], []
# 解析outputs,填充boxes, scores, class_ids
return boxes, scores, class_ids
def draw_detections(self, img, boxes, scores, class_ids):
# 在图像上绘制检测框和标签
for box, score, cls_id in zip(boxes, scores, class_ids):
x1, y1, x2, y2 = box.astype(int)
label = f'{self.classes[cls_id]}: {score:.2f}'
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
return img
# 主循环
if __name__ == '__main__':
detector = YOLOv5RKNN('./best.rknn')
detector.classes = ['cat', 'dog'] # 你的类别名
cap = cv2.VideoCapture(0) # 打开USB摄像头
# 或者 cap = cv2.VideoCapture('your_video.mp4')
while True:
ret, frame = cap.read()
if not ret:
break
# 执行推理
boxes, scores, class_ids = detector.infer(frame)
# 绘制结果
frame_with_det = detector.draw_detections(frame, boxes, scores, class_ids)
# 计算并显示FPS
# ... (FPS计算代码)
cv2.imshow('RK3588 NPU YOLOv5', frame_with_det)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
把这段代码保存成 inference.py,和 best.rknn 放在同一个目录,然后在终端运行 python3 inference.py,你应该就能看到摄像头的实时画面和检测框了。
但做到这里只是“能跑”,距离“跑得好”还有距离。下面是我实测中总结的几个性能优化技巧,能让你的帧率从十几帧飙升到上百帧:
- 输入分辨率是性能关键:模型输入尺寸(代码里的
self.input_size)直接决定了计算量。yolov5默认是640x640,如果你的应用场景对远处小目标不敏感,可以尝试降到480x480甚至320x320,速度会有巨大提升。当然,精度会有所下降,需要权衡。 - 善用多核NPU:RK3588的NPU有3个核心。在
init_runtime时,通过core_mask参数可以指定使用哪些核心。RKNNLite.NPU_CORE_0_1表示同时使用核心0和1。对于单模型推理,使用两个核心通常能比单核心快30%-50%。你可以尝试RKNNLite.NPU_CORE_0_1_2使用全部三个核心,但提升可能不再线性。 - 量化模型是必须的:再次强调,一定要使用量化后的INT8模型(
.rknn)。相比FP32或FP16模型,INT8模型在NPU上的速度优势是压倒性的,通常有3-5倍的提升,而模型体积则缩小为原来的1/4。 - OpenCV的瓶颈:在香橙派5上,图像读取、缩放、显示(
imshow)这些OpenCV操作会消耗不少CPU资源。当推理速度非常快(比如超过50FPS)时,这些预处理和后处理可能成为瓶颈。可以考虑用多线程,一个线程专门负责从摄像头抓帧和预处理,另一个线程负责推理和后处理,并用队列进行通信。 - 关闭调试信息:初始化RKNN Lite时,可以关闭详细日志输出。在
RKNNLite()初始化时不传verbose=True,或者在代码开头加import os; os.environ['RKNN_LOG_LEVEL'] = 'error'只显示错误信息,能减少一些开销。
我自己的实测结果是,使用量化后的yolov5s模型,输入尺寸640x640,在同时使用两个NPU核心的情况下,处理640x480的USB摄像头视频流,帧率可以稳定在70-90 FPS。如果把输入尺寸降到480x480,帧率能轻松突破120 FPS,完全满足绝大多数实时视频分析的需求。这个性能表现,相比在树莓派4B上靠CPU跑ncnn加速的yolov5(大概5-10 FPS),可以说是质的飞跃。整个部署过程虽然步骤不少,但一旦跑通,看到流畅的检测效果,那种成就感是非常足的。
更多推荐



所有评论(0)