【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
  • 运行环境(底座):板子目前安装了 rknnlite v2.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 部署的黄金三步体系

  1. PC/中心服务器训练:在拥有大排量集群算力(如 NVIDIA GPU)的电脑上训练出 YOLO 模型,导出全业界通用的 .onnx 格式(脚本下载的就是这个原料)。
  2. PC端编译量化(模型转换):由于 RK3588 的 NPU 芯片并不认识通用的 .onnx。且为了让 NPU 跑出 6TOPS 的极限速度,需要将模型内部臃肿的浮点运算极致压缩为 8 位整数(即 INT8 硬件量化)。这个庞大的重编译流程,要在开发者的电脑上使用沉重的 rknn-toolkit2 工具链来将其编译为瑞芯微特供的专属二进制文件 .rknn
  3. 嵌入式板端推理:在体积限制的真机开发板上(咱们的 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__': 往下,整个推理过程构成了经典深度学习推流框架:

  1. 环境读取与初始化 (setup_model):解析用户提供的 args.model_path。由于我们用的是修复过的 rknn_executor,它将通过底层的 librknnrt.so 直接占据板载 NPU 的 SRAM 内存池并加载网络结构。
  2. 图像前处理 (Pre-Process / letter_box):YOLO 是不能接受大小不一的长方形照片的。letter_box 就是把图片用黑边(pad_color=(0,0,0))强行垫成 640x640 的方形,防止原图通过暴力拉伸变形导致特征失真。
  3. 数据格式转化:由于 OpenCV 读取硬盘的图片永远是 (H, W, BGR) 结构,但模型必须要 (1, H, W, RGB) 结构。我们用 cv2.cvtColor 将颜色排布替换,再用 np.expand_dims 进行批量 (Batch) 升维以配合 NPU 多线程运算规格。
  4. 触发 NPU Core (model.run):此步无任何 CPU 运算密集型操作,将内存指针喂给瑞芯微专有算子指令,此时物理 NPU 满载瞬时爆发出推理结果 outputs。它包含多个维度特征图吐出。
  5. 后处理算子 (post_process) 退潮:NPU 算出的是上千个“可疑的小方块”。算法在其中首先调用 filter_boxes 用基础分数线一刀切;随后对于一团扎堆描述同一台公交车的小方块,运用 NMS(非极大值抑制) 计算面积交并比,强行剔除次优选框,仅留“置信首领”。
  6. 缩放与落墨 (draw):把在 640 层级被算出的数字边界框反向映射回图片原有的宽长比,最后调用极其经典的 OpenCV 画边框函数在彩色图上“留证”。

结尾

因为我现在只是想先完成一下最基础的内容,后续的模型转换,代码详细分析,如果有下一章,可能会继续做
如果没有就算了

AI时代,大家的效率都变快了,但是实际上,基本都是问AI,自动执行,复制执行,写这篇文章,也许也只是给AI提供一些数据罢了

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐