【ROCK 5T】YOLO从头部署到RK3588 教程
【ROCK 5T】YOLO从头部署到RK3588 教程
目录
一、 硬件与环境背景介绍
在开始部署之前,先了解我们手中的这块嵌入式开发板的硬件核心底座:
- 主板型号:Radxa ROCK 5T
- SoC (系统级芯片):Rockchip RK3588 (瑞芯微旗舰级芯片)
- CPU 计算核心:8核 ARM64 架构(4核 Cortex-A76 @ 2.25GHz + 4核 Cortex-A55 @ 1.8GHz)
- NPU AI加速器:内置算力高达 6 TOPS 的神经网络处理器(NPU),这也是我们能跑实时 YOLO 的真正“功臣”,它负责快速处理矩阵运算。
- 当前系统:Debian 12 (bookworm) / Linux 6.1.84-8-rk2410
- 运行环境(底座):板子目前安装了
rknnlitev2.3.0(只负责在设备端加载模型并调用物理 NPU 计算,不具备 PC 端的模拟和编译功能)。
二、 基础准备:获取官方骨架与模型
1. 什么是 RKNN Model Zoo?
YOLO 的核心是一串编译好的神经元权重(.rknn文件),但有了大脑还不够,我们需要“视神经”(把图片处理成模型认识的格式)和“手”(把模型吐出的框准确定位并画在原来的图片上)。
RKNN Model Zoo 就是瑞芯微官方开源的“应用部署骨架库”。这里面包含了数十种流行 AI 模型的前期图片处理(Pre-process)算法和后期解析(Post-process / NMS)算法。
2. 克隆 Model Zoo 仓库
我们在 ~/dev/ 目录下克隆这个核心库:
mkdir -p ~/dev
cd ~/dev
git clone --depth 1 https://github.com/airockchip/rknn_model_zoo.git
3. 探索尝试:使用官方 sh 脚本下载模型 (.onnx 与 .rknn 的区别)
官方在 yolov5/model/ 目录下提供了一个 download_model.sh 脚本。当我们执行它时,它从云盘下载了一个名为 yolov5s_relu.onnx 的文件。
# 官方默认下载脚本(下载的仅为通用的 ONNX 格式模型)
cd ~/dev/rknn_model_zoo/examples/yolov5/model
./download_model.sh
为什么找不见可以直接跑的 .rknn 文件?
这里涉及嵌入式 AI 部署的黄金三步体系:
- PC/中心服务器训练:在拥有大排量集群算力(如 NVIDIA GPU)的电脑上训练出 YOLO 模型,导出全业界通用的
.onnx格式(脚本下载的就是这个原料)。 - PC端编译量化(模型转换):由于 RK3588 的 NPU 芯片并不认识通用的
.onnx。且为了让 NPU 跑出 6TOPS 的极限速度,需要将模型内部臃肿的浮点运算极致压缩为 8 位整数(即 INT8 硬件量化)。这个庞大的重编译流程,要在开发者的电脑上使用沉重的rknn-toolkit2工具链来将其编译为瑞芯微特供的专属二进制文件.rknn。 - 嵌入式板端推理:在体积限制的真机开发板上(咱们的 Rock 5T 环境)只用安装精简版驱动跑库
rknnlite。此时板子不用做任何编译,直接读入转好的.rknn模型一口吞下,丢给物理 NPU 进行极限预测。
官方的设计初衷是让你自己去体会第 2 步的电脑端转换过程,所以它特意不把转换好的结果文件直接下发给你。
4. 获取编译好的 RKNN 专属模型
由于直接从国内拉大文件易受网络折磨,我们将通过极速拉取瑞芯微预置的针对板卡测试用的示例源仓库,来截获预编译出炉的 .rknn 成品大包:
# 浅克隆官方的 NPU 测试仓库到临时目录
git clone --depth 1 -b master https://gitee.com/rockchip-linux/rknpu2.git /tmp/rknpu2_temp
# 将我们需要的 yolov5s RK3588 专属量化模型提取到 Model Zoo 中对应的存放点
cp /tmp/rknpu2_temp/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn ~/dev/rknn_model_zoo/examples/yolov5/model/
# 清除临时文件
rm -rf /tmp/rknpu2_temp
5. 安装底层图像处理依赖 (OpenCV)
要想程序能够成功读取图片并画框,我们需要给 Debian 安装最底层的 OpenCV 库:
sudo apt-get update
sudo apt-get install -y python3-opencv python3-numpy
三、 代码踩坑与深度修复
由于官方的示例代码试图同时兼顾 PC 端模拟和嵌入式真机,我们在 ROCK 5T 上直接运行时会遇到三个严重的兼容性“地雷”。以下是修复全过程。
坑 1:PC 端与板端库调用的混淆 (ModuleNotFoundError: No module named 'rknn')
原因: 预置的执行文件首行强行导入了只能在 PC 端运行的完整转换器包 rknn.api。而我们的板子只有用于轻量级推理的 rknnlite.api。
修复动作: 修改 ~/dev/rknn_model_zoo/py_utils/rknn_executor.py,增加回退自适应机制。
# 修改引入部分
try:
from rknn.api import RKNN
_is_rknnlite = False
except ImportError:
from rknnlite.api import RKNNLite as RKNN
_is_rknnlite = True
坑 2:远程调试接口拒绝本地访问 (Unsupported run platform: Linux aarch64)
原因: 官方代码在初始化 NPU 时强制传入了 --target rk3588,它会让环境企图去远端连接一个连板设备!但我们实际上已经在 aarch64 主机本地运行了。
修复动作: 继续修改 rknn_executor.py 内部的 RKNN_model_container 类,拦截并抛弃 target。
print('--> Init runtime environment')
if target==None or _is_rknnlite: # 如果是 rknnlite 跑在板子上,不传递远程 target
ret = rknn.init_runtime()
else:
ret = rknn.init_runtime(target=target, device_id=device_id)
坑 3:张量升维报错 (The input[0] need 4dims input, but 3dims buffer feed)
原因: OpenCV 读取的一张图片是 3 维的数据 [宽度, 高度, RGB色彩],但 NPU 是为高吞吐批处理设计的,要求输入必须是包含了批次数的 4 维张量 [批次, 宽度, 高度, RGB色彩]。哪怕这批只处理 1 张图。
修复动作: 进入核心脚本地 ~/dev/rknn_model_zoo/examples/yolov5/python/yolov5.py,找到大约 260 行 input_data = img:
# preprocee if not rknn model
if platform in ['pytorch', 'onnx']:
input_data = img.transpose((2,0,1))
...
else:
# 原始为: input_data = img
# 增加 np.expand_dims 强行增加第 0 维度 (批次维度)
input_data = np.expand_dims(img, 0)
四、 见证奇迹:NPU 本地推理测试
进入 Python 目录,敲下最终召唤 NPU 高速推理的指令。注意必须加上 --img_show 和 --img_save 标志让程序保存画好的图。
cd ~/dev/rknn_model_zoo/examples/yolov5/python
python3 yolov5.py --model_path ../model/yolov5s-640-640.rknn --target rk3588 --img_folder ../model/ --img_show --img_save
当系统打印出 I RKNN: 相关日志并不再报错时,同目录下的 result/bus.jpg 就生成了带有目标检测彩框的推理图像。这标志着板端整个 AI 计算链路的大满贯闭环!

五、 YOLOv5.py 源码全貌与核心运行流程拆解
为了在未来外接 USB 摄像头等真实传感器并自主开发,读懂该脚本是非常重要的。下方是执行成功并修复了升维 Bug 的 yolov5.py 全量源码,随后基于主函数的脉络进行拆解注释。
1. YOLOv5.py 修复后完整源码
import os
import cv2
import sys
import argparse
# 自动将上层目录加入环境变量,用于后续导入 py_utils 常用工具类
realpath = os.path.abspath(__file__)
_sep = os.path.sep
realpath = realpath.split(_sep)
sys.path.append(os.path.join(realpath[0]+_sep, *realpath[1:realpath.index('rknn_model_zoo')+1]))
from py_utils.coco_utils import COCO_test_helper
import numpy as np
# 物理置信度阈值和NMS重叠率阈值设定
OBJ_THRESH = 0.25
NMS_THRESH = 0.45
IMG_SIZE = (640, 640) # 模型需要的输入物理分辨率 (width, height)
# MS COCO 全种类英文标签 (80 类)
CLASSES = ("person", "bicycle", "car","motorbike ","aeroplane ","bus ", ...) # 此处省略部分类名以节约版面
# ... [省略 coco_id_list 映射及基础框计算参数] ...
def filter_boxes(boxes, box_confidences, box_class_probs):
"""根据置信度阈值进行第一波滤除操作"""
box_confidences = box_confidences.reshape(-1)
class_max_score = np.max(box_class_probs, axis=-1)
classes = np.argmax(box_class_probs, axis=-1)
# 用类别最大概率 * 候选框概率,得出最终置信度,筛选 >= OBJ_THRESH 的结果
_class_pos = np.where(class_max_score* box_confidences >= OBJ_THRESH)
scores = (class_max_score* box_confidences)[_class_pos]
boxes = boxes[_class_pos]
classes = classes[_class_pos]
return boxes, classes, scores
def nms_boxes(boxes, scores):
"""NMS (非极大值抑制),用来消除由于一个物体被多次识别而堆叠的重复框"""
x = boxes[:, 0]
y = boxes[:, 1]
...
# 计算交并比 (IOU),剔除重叠面积大于 NMS_THRESH 且分数较低的框
while order.size > 0:
...
inds = np.where(ovr <= NMS_THRESH)[0]
order = order[inds + 1]
keep = np.array(keep)
return keep
# ... [省略预设锚框 Box 解算代码] ...
def post_process(input_data, anchors):
"""后处理核心主函数"""
boxes, scores, classes_conf = [], [], []
# NPU 吐出的 3 个一维列表 (对应3个特征尺度) 变形处理
input_data = [_in.reshape([len(anchors[0]),-1]+list(_in.shape[-2:])) for _in in input_data]
for i in range(len(input_data)):
# 计算框的物理坐标偏移
boxes.append(box_process(input_data[i][:,:4,:,:], anchors[i]))
scores.append(input_data[i][:,4:5,:,:])
classes_conf.append(input_data[i][:,5:,:,:])
...
# 执行过滤 -> 执行NMS
boxes, classes, scores = filter_boxes(boxes, scores, classes_conf)
...
return np.concatenate(nboxes), np.concatenate(nclasses), np.concatenate(nscores)
def draw(image, boxes, scores, classes):
"""画图与落墨渲染,把结果显示到图片上"""
for box, score, cl in zip(boxes, scores, classes):
top, left, right, bottom = [int(_b) for _b in box]
print("%s @ (%d %d %d %d) %.3f" % (CLASSES[cl], top, left, right, bottom, score))
# 画出蓝色矩形框
cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
# 用红色字写上标签类别
cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
(top, left - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--model_path', type=str, required= True, help='model path...')
# ...[省略argparse参数代码]...
args = parser.parse_args()
# 1. 模型初始化 (加载 .rknn 到内存)
model, platform = setup_model(args)
co_helper = COCO_test_helper(enable_letter_box=True)
# 2. 图像读取与遍历
for i in range(len(img_list)):
img_src = cv2.imread(img_path)
# 3. 前处理:Letter Box填充与变形
# 将原始如 1080p 的非正方形图片填充满黑色,拉伸至 640x640 满足 NPU 获取条件
img = co_helper.letter_box(im= img_src.copy(), new_shape=(IMG_SIZE[1], IMG_SIZE[0]), pad_color=(0,0,0))
# BGR (通常图片通道) 需要转为 RGB (神经网络通常训练的通道)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 4. 关键升维:因为物理 NPU 需要(Batch, Height, Width, Channel)
if platform in ['pytorch', 'onnx']:
# ...PC模拟器执行逻辑...
pass
else:
# 【本文档踩坑修复点】通过 np.expand_dims 为单纯的长宽色图片增加一个 Batch_size 为 1 的维度
input_data = np.expand_dims(img, 0)
# 5. 【黑盒前向推断】:硬件 NPU 计算,瞬间出结果
outputs = model.run([input_data])
# 6. 后处理:拿到一堆包含几千坐标的数组,剥离无效信息
boxes, classes, scores = post_process(outputs, anchors)
# 7. 输出保存环节
if args.img_show or args.img_save:
img_p = img_src.copy()
if boxes is not None:
# 在画图时把相对的 640x640 比例放回到它原本(如长方形)的正常尺寸系
draw(img_p, co_helper.get_real_box(boxes), scores, classes)
# 若传递了 --img_save 标志位,由 cv2 保存进本地磁盘
if args.img_save:
if not os.path.exists('./result'):
os.mkdir('./result')
result_path = os.path.join('./result', img_name)
cv2.imwrite(result_path, img_p)
# release
model.release()
2. 主函数运行逻辑深度拆解
从 if __name__ == '__main__': 往下,整个推理过程构成了经典深度学习推流框架:
- 环境读取与初始化 (
setup_model):解析用户提供的args.model_path。由于我们用的是修复过的rknn_executor,它将通过底层的librknnrt.so直接占据板载 NPU 的 SRAM 内存池并加载网络结构。 - 图像前处理 (Pre-Process /
letter_box):YOLO 是不能接受大小不一的长方形照片的。letter_box就是把图片用黑边(pad_color=(0,0,0))强行垫成640x640的方形,防止原图通过暴力拉伸变形导致特征失真。 - 数据格式转化:由于 OpenCV 读取硬盘的图片永远是
(H, W, BGR)结构,但模型必须要(1, H, W, RGB)结构。我们用cv2.cvtColor将颜色排布替换,再用np.expand_dims进行批量 (Batch) 升维以配合 NPU 多线程运算规格。 - 触发 NPU Core (
model.run):此步无任何 CPU 运算密集型操作,将内存指针喂给瑞芯微专有算子指令,此时物理 NPU 满载瞬时爆发出推理结果outputs。它包含多个维度特征图吐出。 - 后处理算子 (
post_process) 退潮:NPU 算出的是上千个“可疑的小方块”。算法在其中首先调用filter_boxes用基础分数线一刀切;随后对于一团扎堆描述同一台公交车的小方块,运用 NMS(非极大值抑制) 计算面积交并比,强行剔除次优选框,仅留“置信首领”。 - 缩放与落墨 (
draw):把在 640 层级被算出的数字边界框反向映射回图片原有的宽长比,最后调用极其经典的 OpenCV 画边框函数在彩色图上“留证”。
结尾
因为我现在只是想先完成一下最基础的内容,后续的模型转换,代码详细分析,如果有下一章,可能会继续做
如果没有就算了
AI时代,大家的效率都变快了,但是实际上,基本都是问AI,自动执行,复制执行,写这篇文章,也许也只是给AI提供一些数据罢了
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)