RK3566与RKNN实战:MobileNetV3图像分类模型端侧部署全流程
发布时间:2026/9/27 23:25:00 作者:尧图编辑部 阅读量:1,286

最近帮团队做了个小项目用立创泰山派这块RK3566开发板加上RKNN工具链部署了一个 MobileNetV3 图像分类模型做成一台完整的智能摄像头。整条链路从硬件选型、模型转换、板端推理到性能调优都走了一遍踩了不少坑也沉淀下来一套可以直接复用的流程。这篇文章就是想把这套实战过程完整记录下来适合手里有 RK3566 系列板子、想把神经网络模型真正跑在NPU上的朋友参考。我自己是搞嵌入式Linux出身的之前也用过树莓派和Jetson Nano跑视觉模型但实际做到量产级小批量项目时RK3566 这个性价比区间确实很能打。泰山派这块板子的好处是接口齐全、资料开放而且RKNN工具的兼容性做得不错只要你按流程走基本不会卡在“模型死活跑不起来”这种玄学问题上。1. 项目背景与技术选型思路这一章先把为什么选这套方案讲透。很多初学者一上来就照着教程敲命令但不知道每一步背后的选型逻辑遇到问题就懵了。我尽量把我在做选型时考虑的点都展开说一下。1.1 为什么选 RK3566 而不是树莓派或者其他板子智能摄像头这种场景核心诉求是本地跑模型、实时输出结果、成本和功耗可控。树莓派4B/5确实能跑TensorFlow Lite但CPU推理的帧率很有限跑个MobileNetV3分类模型可能也就5~8帧稍微加点预处理逻辑帧率更低。而RK3566内置了1TOPS算力的NPU虽然数字不高但跑MobileNetV3这种轻量级模型绰绰有余实测单核NPU跑INT8量化后的模型推理一次只要10~20毫秒差距非常明显。Jetson Nano算力更高但价格贵、货源不稳定、板卡体积也偏大做小批量产品不太合适。全志、晶晨的芯片虽然也有NPU但工具链成熟度和社区资料差了不少。RK3566背后的RKNN工具链经过几代迭代对 PyTorch/ONNX/TensorFlow 模型的支持已经比较完善遇到算子问题也有路人缘不错的社区包括开源项目和各种问答帖可以求助。最后选中立创泰山派主要是看中这几点接口丰富双MIPI CSI、HDMI、USB3.0、以太网口都有直接省了转接板的钱、原理图和数据手册全部公开、有配套的SDK和底板设计参考。它不像一些商业开发板那样封闭非常适合产品原型验证。1.2 为什么是 MobileNetV3轻量模型的端侧优势选 MobileNetV3 而不是 ResNet 或 YOLO需要结合硬件和场景来考虑。智能摄像头如果只做固定场景的图像分类比如识别工位上有没有人、垃圾分类、质检缺陷分类等MobileNetV3-Small/Large 是完全够用的。它的优势有三点结构上用了深度可分离卷积和 SESqueeze-and-Excitation注意力机制参数量和计算量大幅下降在RK3566上跑INT8量化后精度损失很小。RKNN 工具链对 MobileNetV3 的算子支持非常成熟几乎不需要手工改图导出ONNX后直接转换就能过。部署后内存占用极低模型文件也就5~10MB运行时代理占用几十MB非常适合摄像头这种资源受限的设备。当然如果你的目标是检测多个目标或者需要毫秒级跟踪那建议直接用 YOLO 系列比如 YOLOv5s、YOLOv6甚至现在比较热的 YOLO26 也有转 RKNN 的教程。不过分类模型的部署流程是所有视觉任务的基础先把这条链路跑通后面切换到检测模型只是换模型和改后处理的事。1.3 整体系统架构与数据流这个项目的完整数据流是这样的摄像头通过 MIPI CSI 或 USB 接口采集图像送到 RK3566 的 ISP 或 V4L2 驱动处理应用层拿到 YUV/RGB 帧后做 Resize和归一化再传给 NPU 进行推理NPU 输出的是分类概率向量经过 softmax 和 argmax 得到类别ID和置信度最后把结果叠加到视频帧上通过 HDMI 显示或 RTSP 推流出去。我在实现时把整个流程分了三层采集层负责V4L2取流、分辨率对齐、帧率控制推理层封装 RKNN 模型加载和推理采用单例模式避免重复初始化应用层把推理结果和业务逻辑结合比如识别到特定类别后就触发继电器或者发送MQTT消息。这种分层的好处是后续如果要从分类模型换成检测模型只需要替换推理层和后处理部分采集层和应用层基本不用动。2. 硬件准备与开发环境搭建这部分我把从开箱到板子能跑模型的全过程记录下来。不仅仅是接线和烧录还包括很多第一次接触这块板子时容易忽略的细节。2.1 泰山派核心板与底板接线要点泰山派板卡界面上的接口还是比较好认的。做智能摄像头至少需要准备这些外设一块泰山派核心板 对应底板的电源建议12V/2A DC 电源不要只靠Type-C供电带摄像头和屏幕时电流不稳容易掉盘MIPI CSI 摄像头模块推荐使用官方配套的 OV5648 / IMX219 模组免驱且设备树已有预设带 HDMI 接口的显示器或者用配套的 MIPI DSI 屏幕用于查看实时画面一张质量靠谱的 TF 卡或者 SSD 硬盘建议 SSD 通过 USB3.0 转接开发效率比TF卡高一截。接线时特别注意MIPI排线的方向金属触点要面对板子上的丝印说明我一开始装反了导致摄像头一直没枚举出来排查了半小时才发现是排线方向问题。还有一点如果需要用到 GPIO 控制外设先查一下泰山派底板的引脚定义避免把3.3V和5V接错烧掉模块。2.2 SDK 下载、编译与固件烧录泰山的SDK是基于Rockchip Linux SDK裁剪的BSP层和其他Rockchip方案是兼容的。整体流程如下在立创开源硬件平台找到泰山派资料页下载SDK压缩包建议选完整包而不是补丁包省去很多依赖匹配的麻烦。SDK解压后先跑一遍环境检查脚本确保安装了必要的交叉编译工具链和库。官方文档推荐在Ubuntu 18.04/20.04 x64环境下开发我自己用了Ubuntu 20.04验证过一遍没有问题。如果要定制设备树编辑 kernel/arch/arm64/boot/dts/rockchip/rk3566-泰山派相关的 dts 文件比如打开或关闭某个 I2C、MIPI CSI 节点。编译命令大概是./build.sh lunch # 选择对应的板级配置 ./build.sh烧录用 RKDevToolWindows版或 upgrade_toolLinux版把板子切换到Loader模式按住 recovery 键上电然后把编译生成的镜像按分区写入。如果你的板子已经预烧了系统可以先不重新编译SDK直接用官方的 Ubuntu 镜像烧录后面只拷贝编译好的模型推理程序上去。第一次玩的话我建议先跑官方镜像确认摄像头、网络都正常再折腾SDK编译。2.3 摄像头与屏幕的接入验证系统烧好后先验证摄像头是否被正确识别。通过串口或SSH登录板子执行v4l2-ctl --list-devices正常情况会输出类似rkisp_vir0 (platform: rkisp): /dev/video0 /dev/video1如果什么设备都没出现检查设备树里CSI节点是否打开、排线是否接好。也可以插一个USB摄像头通常会自动识别为 UVC 设备设备节点是/dev/video0或其他编号。屏幕接入相对简单HDMI 一般是即插即用。如果要接 MIPI DSI 屏幕需要确认内核里对应的 panel 驱动有没有编进去并且 dts 里的 backlight 和 reset gpio 引脚号要和屏幕规格书一致否则屏幕点亮后可能出现“背光亮了但黑屏”的问题。2.4 开发机与板卡联调的基础配置开发机和板卡之间通常走两种连接方式一是串口二是网络。调试阶段串口非常关键因为内核打印和系统崩溃日志都只能从串口看。我习惯用 CH340 这种USB转串口模块接上板子的调试串口波特率一般 1500000瑞芯微默认比较特殊不是普通的115200如果乱码就改几个常用波特率试一下。网络联调建议用静态IP在板子上执行nmcli con mod eth0 ipv4.addresses 192.168.1.100/24 nmcli con mod eth0 ipv4.method manual nmcli con up eth0然后用SSH连接ssh root192.168.1.100。接下来开发机上的代码、模型文件都用 scp 传过去避免频繁插拔TF卡。3. 模型训练与 RKNN 转换流程这是整个项目中最核心、也最容易出问题的一环。模型本身不复杂但“PyTorch训练好的模型 - ONNX - RKNN”这条链路里每一步都有它自己的规矩。我按实际操作的顺序来写。3.1 用 PyTorch 训练/微调 MobileNetV3我用的模型是torchvision.models.mobilenet_v3_small在大规模数据集上预训练好的权重基础上针对自己的分类任务做迁移学习只训练最后的全连接层。这样做的原因是端侧算力有限从零训练一个深度模型周期太长而且数据量不够容易过拟合。训练脚本关键点如下import torch import torchvision.models as models model models.mobilenet_v3_small(pretrainedTrue) num_classes 10 # 根据实际业务调整 model.classifier[3] torch.nn.Linear(model.classifier[3].in_features, num_classes) # 冻结特征层只训练分类头 for param in model.features.parameters(): param.requires_grad False # 训练循环省略重点输入图像尺寸统一为 224x224归一化参数 # mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]这里有一个容易忽略的坑MobileNetV3 的classifier结构是Sequential(Linear, Hardswish, Dropout, Linear)所以最后分类头是classifier[3]而不是classifier[-1]里的某个层改层的时候别改错了。训练完成后保存模型权重torch.save(model.state_dict(), mobilenet_v3_custom.pth)3.2 PyTorch 导出 ONNX 的避坑指南RKNN 工具链不能直接吃 PyTorch 模型需要先转成 ONNX 或者 TFLite。我建议优先用 ONNX因为后续调试算子映射问题更直观。导出脚本import torch model.load_state_dict(torch.load(mobilenet_v3_custom.pth)) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, mobilenet_v3_custom.onnx, input_names[input], output_names[output], opset_version11, do_constant_foldingTrue, )导出时务必注意三个问题。第一模型一定要切到 eval 模式否则 BatchNorm 和 Dropout 的参数会变导出后的模型推理结果不对。第二opset_version 不要乱用最新的RKNN 工具链对 opset 11 的支持最稳定太高反而可能在转换时报不支持的算子。第三导出完成后用 ONNX Runtime 验证一遍输出确认导出的 ONNX 和 PyTorch 原模型结果一致这一步能把问题提前拦截而不是等到板子上再排查。3.3 用 RKNN Toolkit 将 ONNX 转换为 RKNN这一步是在 PC 上完成的。瑞芯微提供了 RKNN-Toolkit2建议创建独立虚拟环境安装git clone https://github.com/airockchip/rknn-toolkit2.git cd rknn-toolkit2 pip install packages/rknn_toolkit2-*.whl转换脚本from rknn.api import RKNN rknn RKNN() ret rknn.config(mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], target_platformrk3566) # 这里 mean/std 对应 ImageNet 的通道均值和方差乘255 ret rknn.load_onnx(modelmobilenet_v3_custom.onnx) ret rknn.build(do_quantizationTrue, datasetdataset.txt) # dataset.txt 里是若干张校准图片的路径每行一个 ret rknn.export_rknn(mobilenet_v3_custom.rknn)mean_values和std_values一定要和训练时一致。我在训练时用的是 PyTorch 默认的[0.485, 0.456, 0.406]但标准化的实际数值是除以标准差所以配置里要填的是std * 255而不是原始的0.229。量化校准集 dataset.txt 里的图片建议选100~200张覆盖各种光照、角度不用太多但要有代表性。如果校准图太单一量化后的模型在真实场景下精度会明显下降。3.4 模型评估与精度对比转换完不能直接就上板先在 PC 上用模拟器评估一下量化模型的精度。RKNN Toolkit 支持在 x86 上模拟 NPU 运行虽然速度慢但验证精度很实用。rknn.init_runtime(targetNone) # 模拟模式 outputs rknn.inference(inputs[img])把量化 RKNN 模型和原始 ONNX 的输出对比记录每一类的 softmax 概率。经验值是量化后 top-1 准确率下降不超过 2% 属于正常如果超过 5%优先检查校准集质量和预处理参数。我实际测过一个有意思的现象INT8 量化后某些原本概率低的类会变得特别激进、置信度虚高。解决办法是提高校准集的多样性或者给均值标准差配置做微调。不要一上来就怀疑量化精度问题大多数时候是前面的某一个环节没对齐。4. 板端推理程序实现模型转换好后剩下的主要工作就是把推理程序跑起来。我分别写了 Python 和 C 的版本Python 适合快速验证C 适合性能敏感场景。4.1 Python 推理代码RKNNLite 简洁高效板端推理推荐使用 RKNNLite 接口比 RKNN 接口轻量不需要调用模拟器专为部署设计。核心代码如下from rknnlite.api import RKNNLite rknn RKNNLite() ret rknn.load_rknn(mobilenet_v3_custom.rknn) ret rknn.init_runtime(core_maskRKNNLite.NPU_CORE_0) # 推理 outputs rknn.inference(inputs[img])core_mask参数可以指定用哪个NPU核。RK3566 有3个NPU核像 MobileNetV3 这样的小模型跑单核就够了没必要都占用留出资源给图像采集和编码更合理。摄像头采集用 OpenCV 或者 V4L2。OpenCV 简单但延迟略高V4L2 写起来复杂但更可控。我的建议是先用 OpenCV 把整个链路跑通证明模型没问题再优化采集那部分。4.2 图像采集与预处理对齐预处理是最容易被忽视的环节。模型训练时的预处理是“读取图像 - Resize到224x224 - 转Tensor - Normalize”但摄像头采集到的是 BGR 或者 NV12 数据需要保证链路一致import cv2 import numpy as np cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) ret, frame cap.read() rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) resized cv2.resize(rgb, (224, 224)) # 转成 HWC 连续内存RKNN 输入是 NCHW需要指定 data_format # 但 rknn.inference 会自己处理 transpose所以传 HWC 即可需要注意rknn.inference接口默认输入格式是NHWCdata_format参数可以调整。如果你传的是CHW必须显式声明data_formatnchw否则结果完全不对而且很不容易察觉——因为模型不会报错只会给出乱七八糟的分类结果。4.3 推理结果解析与叠加显示RKNN 输出的原始内容是一个展平的 NumPy 数组因为模型没有在内部做 softmax所以需要自己处理outputs rknn.inference(inputs[resized]) probs np.squeeze(outputs) probs np.exp(probs - np.max(probs)) probs probs / np.sum(probs) class_id int(np.argmax(probs)) confidence float(probs[class_id])拿到类别和置信度后叠加到视频帧上label fclass_{class_id}: {confidence:.2f} cv2.putText(frame, label, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(smart_camera, frame)如果推到网络可以用 RTSP 或者直接把帧通过 MQTT 发送到服务端。板上跑一个mediamtx原 rtsp-simple-server就能快速搭建 RTSP 流服务具体用法网上资料已经很多。4.4 C 接口与性能优化方向Python 版验证完成后如果觉得帧率还不够可以按性能瓶颈逐一优化。RKNN 提供了完整的 C/C API初始化流程和 Python 大同小异#include rknn_api.h rknn_context ctx; rknn_init(ctx, model_path, 0, 0, nullptr); rknn_input inputs[1]; rknn_output outputs[1]; // 设置输入输出格式 inputs[0].type RKNN_TENSOR_UINT8; inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].size 224 * 224 * 3; inputs[0].buf frame_data; rknn_run(ctx, nullptr); rknn_outputs_get(ctx, 1, outputs, nullptr);优化方向按收益排序采集和推理并行化用双缓冲采集线程往 buffer A 写入推理线程读 buffer B交替使用避免阻塞关闭不必要的调试打印RKNN 在初始化时会输出很多日志release 编译去掉这些输出能省时间固定 NPU 核心单核跑模型避免多核调度切换带来的额外开销减少拷贝能从 V4L2 直接拿到连续内存就不要再经过 OpenCV Mat 的深拷贝。实测优化后MobileNetV3-Small INT8 模型在 224x224 输入下单次推理稳定在 12~15ms加上采集和显示整体帧率能到 30FPS完全满足实时监控需求。5. 常见问题与排错技巧实录这部分是整篇文章里最有价值的内容基本上都是我在项目里真实踩过的坑。我按照现象、原因、解决方式整理成速查表方便你出了问题直接对照。5.1 常见报错速查表现象可能原因解决方式E RKNNAPI: rknn_init failed模型文件不存在、驱动未加载检查 rknn_server 和 librknnrt.so 是否匹配重装 runtime推理结果始终是同一个类别预处理参数不对核对 mean/std 是否与训练一致检查输入数据格式帧率只有个位数模型未量化或采集阻塞确认 rknn 文件是 INT8 量化版使用异步采集摄像头无法枚举排线方向接反或设备树未配置检查 MIPI 排线方向dmesg屏幕点不亮背光正常但黑屏检查 panel reset 引脚和 dts 中的时序参数系统启动反复重启电源功率不足换 12V/2A 以上电源避免用劣质 DC 线adb 找不到设备USB 线问题或未开 adb换数据线确认主机驱动安装正常还有一些问题是因为 RKNN runtime 和 Toolkit 版本不匹配。PC 端用 Toolkit2 转换模型板端 runtime 必须是对应版本否则会报version mismatch。检查方式# 板端 strings /usr/lib/librknnrt.so | grep version如果报错去官方资料页下载对应版本的 runtime 包重新安装即可。5.2 精度下降严重时的排查流程如果你发现量化后的模型精度掉得很厉害按这个顺序排查先确认 FP32 的 ONNX 在板端用 CPU 模式跑精度正常排除硬件问题看量化校准集的质量不要用网上随便下载的图片尽量用你自己业务场景中的真实数据检查dataset.txt的图片路径是不是相对路径RKNN 在读取时可能因为找不到图片而静默跳过导致校准集实际只有几张图实验一下不同的quantized_dtype有时候asymmetric_quantized-u8比w8a8更适合你的数据分布。我遇到过一个特殊情况一张全黑图片分类模型输出各类概率几乎都是 0.02argmax 结果频繁跳变。原因是校准集里没有低光照样本量化时把低灰度区间的映射压缩得太厉害。加了一组昏暗场景的图片重新量化后问题就消失了。这块的经验就是校准集要还原真实场景的“分布”而不是追求图片数量多。5.3 救砖与系统恢复RK3566 这块芯片在烧录时碰到断电、镜写错分区很容易变砖。不过好在瑞芯微的 SoC 都有 MaskROM 模式可以强制进入烧写模式拔掉所有电源和USB线按住板子上的 recovery 键如果有 maskrom 键就短接对应的测试点不松手接上 USB 线到电脑再上电使用 RKDevTool 的 MaskROM 模式选择完整的升级固件含 loader 和所有分区执行“升级”。只要不是硬件损坏这个方法基本都能救回来。别问我为什么这么熟练问就是曾经在熬夜调设备树的时候把参数写错把触摸屏驱动编成了默认加载从而导致内核崩溃靠这个方法恢复过两次。5.4 从分类到检测与更多模型扩展这个项目跑通后你可以很自然地扩展到目标检测、人脸识别、缺陷检测等任务。RKNN 工具链的通用流程是一样的训练/获取模型 - 导出 ONNX - 转 RKNN量化 - 板端推理。目标检测的差异主要在输出层解析YOLO 系列的输出是(1, 3*(5num_classes), grid_h, grid_w)这种格式需要解码框坐标和置信度如果你用官方 RKNN 模型库里的 YOLO 示例后处理代码可以直接参考近年来很火的 DINOv3 这类视觉Transformer模型其实也能转 RKNN但算子复杂度和模型尺寸都大很多初学者建议先把 CNN 模型吃透再碰 Transformer。我自己目前就在尝试把轻量级检测模型跑上板子目标是同一个 NPU 核上同时做分类和检测。按现在的性能余量来看只要不做多任务同时推理单核 NPU 跑一个 YOLOv5s 量化的检测模型应该很轻松。6. 性能调优实操从30FPS到更高如果你不满足于“能跑”还希望把性能压榨到极限这一章应该能帮到你。优化的核心思路是先定位瓶颈再针对性地改。6.1 先定位瓶颈NPU还是CPU使用top命令可以快速看到 CPU 占用率同时查看 RKNN 日志中的推理耗时。推理耗时主要由三部分组成预处理时间 NPU 计算时间 后处理时间。我实际测试中OpenCV 的 BGR2RGB Resize 操作在 720p 分辨率下大概要 12msNPU 推理只要 15ms后处理基本忽略不计。也就是说预处理和推理几乎各占一半如果只优化 NPU 侧收益很有限。所以优化的重点应该放在减少预处理开销上。6.2 用 OpenCV 的加速指令优化预处理OpenCV 默认编译已经开了很多加速但如果你用的是 ARM 板确保 OpenCV 是 ARM 版本且开启了 NEON 优化。我自己会用cv2.setNumThreads(4)来调整线程数因为采集线程和主线程如果同时抢 CPU反而会互相拖慢。更激进的做法是直接在 V4L2 的格式设置里让摄像头输出 RGB 格式如果支持或者减少分辨率从源头减少数据量。对于分类任务720p 采集、224x224 推理即可中间只需要一次缩放拷贝不必整帧转换色彩空间。6.3 多线程流水线架构我最终采用的方案是三个线程流水线采集线程不断从摄像头读帧写入帧队列 A推理线程从帧队列 A 取帧preprocess rknn.run postprocess结果写入队列 B显示线程从队列 B 取结果叠加信息并显示。用两个队列做缓冲采集和推理互不阻塞。实际运行后帧率稳定提升而且 CPU 占用率还下降了因为线程切换减少了。如果你需要更极致的帧率还可以考虑使用 DMA-BUF 零拷贝直接让 NPU 读取摄像头输出的内存省掉内存拷贝。但这是比较进阶的优化手段先把基础的流水线版本跑稳定再考虑它。6.4 功耗与发热控制RK3566 满载运行时发热不小长时间跑摄像头推理建议给芯片贴散热片或者加个小风扇。之前有一次调试时只用了散热片跑了一个小时温度到了80度虽然芯片没死机但NPU频率已经降到比较低的档位帧率明显下降。加了主动散热后运行稳定帧率也恢复到了正常水平。如果你做的是电池供电的设备还可以用cpufreq-set调整 CPU 的频率策略或者只让一个 NPU 核工作功耗能降不少。7. 项目总结与个人经验分享这里不打算做什么项目成果展示就聊聊我在整个过程中最深的三点体会希望能帮你少走弯路。第一RKNN 这条链路的坑90% 出在“预处理不一致”上。PC 上 PyTorch 用的归一化参数、ONNX 导出时的输入尺寸、RKNN 转换阶段的 mean/std、板端代码里的图像格式这四个环节只要有一个不一致最终结果就是模型在 PC 上跑 99% 准确率在板上一跑就“随机猜测”。所以每次转换完模型我都强制自己在板端跑同一张测试图和 PyTorch 输出做数值对比确保误差在合理范围内才开始做业务逻辑。第二开发板调试一定要善用串口日志。很多人习惯只看 SSH 终端但系统早期启动阶段、内核崩溃、驱动加载失败的信息只有串口能看到。把串口线接好波特率调到 1500000可以省掉一半的排查时间。第三不要被“模型多大、算力多强”这些参数迷惑实际跑起来才知道瓶颈在哪里。MobileNetV3 在 PC 上推理只要几毫秒但在板端如果把预处理做得粗糙整个链路照样会被卡在 10FPS。优化性能的时候先用 perf 或者简单的计时函数把每一段耗时测出来再决定往哪使劲。这个项目做完后我手上那套代码和模型转换脚本已经沉淀成了团队内部的新项目模板。后续不管是换成检测模型还是接上屏幕做本地交互在这个底座上扩展都很方便。如果你也准备在 RK3566 上做视觉项目建议直接从“跑通最小系统”开始先让模型在板上输出正确结果再慢慢抠细节这样正反馈会强很多也不容易半途而废。