ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RK3568 部署实录:把 YOLO 搬上 NPU——从 0.7fps 到 21.4fps,和满屏“0.50 假框“的排查全过程

RK3568 部署实录:把 YOLO 搬上 NPU——从 0.7fps 到 21.4fps,和满屏“0.50 假框“的排查全过程 RK3568 部署实录把 YOLO 搬上 NPU——从 0.7fps 到 21.4fps和满屏0.50 假框的排查全过程系列第 3 篇。前情提要第 1 篇从开箱到第一帧图像第 2 篇刷机 Debian 与网络固化。本篇记录第 3 周战役YOLOv8n 从 CPU0.7~1.0 fps搬到 RK3568 的 NPU 上最终稳定21.4 fps。板子正点原子 RK35684G64GDebian 10内核 4.19模型YOLOv8n工具链RKNN-Toolkit2 2.3.2。〇、为什么要把 YOLO 从 CPU 搬到 NPU上一周我用 Python OpenCV YOLOv8n 在板子上跑通了实时人形检测摄像头画面里出现人就画绿框、计数、存截图。验收那天我对着摄像头挥手12 帧全中绿框严丝合缝。但有一个数字我一直不想直视0.7~1.0 fps。纯 CPU 跑深度学习就是这个样子——RK3568 的四核 Cortex-A55 是省电小核不是给神经网络准备的。而这块芯片真正的杀手锏是一颗 0.8 TOPS 的 NPU神经网络处理器。想让检测从演示能用变成实战能用路只有一条把模型搬上 NPU。搬家的工具是瑞芯微官方的RKNN工具链。这里先认识三个角色后文会反复出现角色装在哪干什么RKNN-Toolkit2x86 电脑虚拟机翻译官把通用模型ONNX翻译成 NPU 认的 .rknn.rknn 模型板子上翻译产物和工具链版本强绑定rknn-toolkit-lite2 librknnrt板子上执行者加载 .rknn驱动 NPU 跑推理一句话预告全程最大的两个坑“pip 装的库其实是壳”和**“满屏置信度 0.50 的假框”**。一、PC 侧装环境一装 ultralytics连踩三个版本坑模型翻译在 x86 虚拟机上做Toolkit2 只支持 x86 Linux板子只负责执行。虚拟机里我建好了 venvrknn-toolkit2 装得挺顺一个 import 就过了。真正的坑埋在下一步——装 ultralytics。我需要 ultralytics 不为别的就为了它自带的yolo export命令能把 yolov8n.pt 导成 ONNX。命令本身一条pipinstallultralytics-ihttps://pypi.tuna.tsinghua.edu.cn/simple装是装上了但 pip 在结尾处留了一行警告当时我没在意后面才知道每一行警告对应一个潜在的坑——因为 ultralytics 不给自己依赖的包锁版本上限而它带进来的一堆依赖里有三个正好踩了 toolkit2 的命门坑 1torch 被升级顶包。ultralytics 把 torch 拉到了最新的 2.14.0顺手把 toolkit2 依赖的 2.4.0 卸载了。pip 警告说得明白rknn-toolkit2 2.3.2 requires torch2.4.0, but you have torch 2.14.0。手动降回去pipinstalltorch2.4.0torchvision0.19.0-ihttps://pypi.tuna.tsinghua.edu.cn/simple坑 2setuptools 被卸载而且装新版也没用。紧接着跑转换脚本初始化就报No module named pkg_resources——装 ultralytics 时 pip 把旧 setuptools 当冲突依赖卸了。我理所应当地pip install setuptools装了最新版结果报错原封不动。查了才知道setuptools 从 81 版本开始正式移除了 pkg_resources 模块装最新版等于装了个没有零件的空壳。要装 81 以下的pipinstallsetuptools81-ihttps://pypi.tuna.tsinghua.edu.cn/simple引号必须带不然会被 shell 当成重定向符。坑 3onnx 太新删了老接口。setuptools 的坑刚填平转换器加载 ONNX 时又报AttributeError: module onnx has no attribute mapping——onnx 1.17 以上删除了 toolkit2 还在用的老接口onnx.mapping而 ultralytics 又顺手把 onnx 升到了 1.22。钉回兼容版本pipinstallonnx1.16.1-ihttps://pypi.tuna.tsinghua.edu.cn/simple三个坑填完环境才算真的好了。这段经历给我的沉淀是一条以后装环境的规矩同一个 venv 里先装挑剔的版本要求严的再装随和的装完随和的立刻回头检查挑剔的是否还活着。pip 那行 dependency conflict 警告不是废话是救生圈。二、模型翻译两道工序pt → onnx → rknn环境就绪翻译分两道工序。第一道工序导出 ONNX。ultralytics 一条命令yoloexportmodelyolov8n.ptformatonnxopset12imgsz320三个参数都有讲究formatonnx不用解释opset12是 ONNX 算子集版本——RKNN 对低版本算子支持最好这个细节先记住后文第四部分还会用到imgsz320是本篇提速决策的第一支柱模型输入尺寸在转换时就焊死YOLOv8n 默认是 640砍半到 320 后 NPU 算量降到四分之一代价是小目标检出率略降——对检人这种目标占画面比例不小的场景几乎无感。导出成功12.1 MB。第二道工序onnx → rknn。写个十几行的转换脚本流程四步每步检查返回值fromrknn.apiimportRKNN rknnRKNN(verboseTrue)# 归一化除255焊进模型mean0, std255板端运行时喂 uint8 原图即可rknn.config(mean_values[[0,0,0]],std_values[[255,255,255]],target_platformrk3568)rknn.load_onnx(modelyolov8n.onnx)rknn.build(do_quantizationFalse)# 先转 FP32 跑通量化是后话rknn.export_rknn(yolov8n_fp32.rknn)一次跑过产出 7.3 MB 的 yolov8n_fp32.rknn。日志里两个数字让我放心NPU 内部内存占用 4200KB、权重内存 6201KB——320 输入的模型在 RK3568 的 NPU 内存里绰绰有余。文件搬运走虚拟机 → Windows → 板子三跳虚拟机和板子不在同一网段Windows 是它们唯一的公共邻居原则是在文件所在的那一侧发起 scp。模型 7MB 放板子的/userdata/大数据分区——这个镜像的根分区只有 5.9G大文件一律进 /userdata这是刷机周定下的规矩。三、板侧攻坚战“Invalid RKNN model version 6”板端装执行环境一条 pippipinstallrknn-toolkit-lite22.3.2-ihttps://pypi.tuna.tsinghua.edu.cn/simple版本号和虚拟机的 toolkit2严格一致——模型格式和运行时库是强绑定的两边差一个小版本都可能打不开。然后写了个最小冒烟测试加载模型 → 初始化 NPU → 喂一张全零假图推理一次。本以为十拿九稳结果当场报错I RKNN Runtime Information: librknnrt version: 1.3.0 (9b36d4d742022-05-04) I RKNN Driver Information: version: 0.8.2 E Invalid RKNN model version 6 E rknn_init, load model failed!翻译一下这几行日志板子上真正干活的运行库librknnrt是1.3.02022 年 5 月的而我们的模型是用 2.3.2 的翻译官转出来的第 6 版格式——四岁的老库根本不认这个新生儿。这里有个反直觉的知识点也是最容易误导人的地方pip 装的 rknn-toolkit-lite2 只有 559KB它只是个壳Python 接口层真正做推理的动态链接库用的是系统/usr/lib/librknnrt.so——镜像出厂自带的那个 2022 年老库。你以为你跑的是 pip 里的新库其实系统里躺着个老的。解法是去瑞芯微官方仓库airockchip/rknn-toolkit2按和 toolkit2 一致的 v2.3.2 标签下载配套的 aarch64 运行库替换系统老库。操作三件套稳字当头cp/usr/lib/librknnrt.so /usr/lib/librknnrt.so.bak# 先备份可一键回滚cp/userdata/librknnrt.so /usr/lib/librknnrt.so# 新库覆盖旧库ldconfig# 刷新动态链接缓存两个悬着的心也放下了其一NPU驱动内核态0.8.2和运行库用户态是两回事换库动不了驱动而实测 0.8.2 的老驱动兼容 2.3.2 的新库不用动内核其二rknn_server 那个服务只有PC 连板调试才需要板上自推理压根不用装。换库重跑日志焕然一新librknnrt version: 2.3.2、model version: 6全部对上最后输出output shape: (1, 84, 2100)——和纸面推算分毫不差84 4 个框坐标 80 个 COCO 类别分数2100 320 输入下 40²20²10² 三个检测网格的锚点总数。NPU 链路打通。四、满屏 0.50 框一次完整的排障记录链路通了立刻把第 2 周的实时检测脚本移植过来取流、BT.709 色彩转换、旋转、画框全部复用只把 torch 推理换成 rknn.inference后处理自己写置信度过滤 NMS 画框。信心满满开跑——满屏都是框置信度还全是 0.50。检测确实在工作每帧稳定输出 92 个框框在画面上糊成一片绿。但注意看标签——置信度几乎清一色是0.50偶尔 0.40、0.60。这个整齐划一本身就是最大的线索。下面是完整的排查记录每一步只回答一个问题。第一步输入健康吗写探针脚本抓一帧打印像素统计——均值 144是正常亮度的画面不是黑图、不是花屏。取流和色彩转换排除。第二步输出是什么分布喂一帧真图推理打印全部 2100 个锚点的 person 分数全部落在 [0, 0.004] 区间。看到这一步0.50 的来源已经清楚了——sigmoid(0) 恰好等于 0.5分数全挤在 0 附近sigmoid 之后全变成 0.5自然全过 0.4 的阈值。分数全在零点附近说明网络实质上没算出来东西输出退化了。第三步排雷本步是反面教材。我怀疑是 opset 19 太新把网络转坏了网上确有高版本算子转换出错的先例于是降到 opset 12 重新导出、重新转换、重新测试——症状原封不动。白白花了一轮往返。教训很深没定位之前不要先动手改怀疑要有优先级先排除输入/输出这种一查便知的可能再动模型。第四步四路 A/B 输入探针。回到第二步的疑点网络输出退化常见原因是输入不对喂错尺度喂了黑图。到底运行时做不做归一化与其查文档猜不如一次实验同一张图分别以四种形式喂给 NPU——uint8 的 0~255、float 的 0~1、float 的 0~255、RGB 通道序——一次运行打印四路输出的分数统计。结果干净利落uint8 和 float 0~255 两路输出健康max 0.94float 0~1 那路全灭。结论转换配置里的 std255 归一化由运行时真实执行要喂原始像素这也回答了第二部分留下的问题归一化焊在模型里但焊的是运行时侧的预处理节点不是让我们喂 0~1。第五步对照实验真凶落网。输入对了、输出还是不像人检出的样子只剩最后一块拼图分数的含义。做了一组对照同一张真人照片分别用 torch 直接跑 yolov8n.pt、用 onnxruntime 跑导出的 yolov8n.onnx。torch 一无所获最高分没过默认阈值而 ONNX 的分数最大 0.19——矛盾出现了如果 0.19 是原始 logitsigmoid 之后是 0.55torch 不可能什么都检不出唯一自洽的解释是——ultralytics 导出的分数已经是概率了sigmoid 早就 baked 在图里。而我后处理又老老实实套了一遍 sigmoid真人概率 0.9 压成 0.71 看不出来毛病背景锚点概率 0.001 被抬成 0.5——全过阈值满屏假框。问题①就出在我多加的 sigmoid 上。问题②出在 NMS 的格式上去重用的cv2.dnn.NMSBoxes需要的是[x, y, w, h]格式我传了[x1, y1, x2, y2]坐标被错误解析92 个框一个都没去掉。修复就三处后处理删掉 sigmoid、NMS 手写二十行 numpy消灭格式歧义、输入按探针结论喂 uint8。重跑——frames 350, person-boxes 363, avg 9.70 fps350 帧 363 框稳态一帧一个人FP32 基线 9.7 fps比 torch 的 0.7~1.0 快了十倍。绿框精准落在人身上第 3 周上半场的目标完成。五、性能冲刺推理只占 23ms瓶颈却在别处9.7 fps 离 ≥20 的验收线还差一半INT8 量化是早就排好的下一刀把权重和激活从 32 位浮点压成 8 位整数NPU 算得更快、内存省一半代价是需要一组标定图片统计数值分布会有轻微精度损失。标定图怎么采有讲究标定数据必须和模型线上看到的输入同分布。我的采集脚本里每一帧都过和推理完全一致的预处理resize 320 BGR→RGB再存盘人在镜头前走动换姿势凑满 120 张转换脚本只改一行——build(do_quantizationTrue, datasetdataset.txt)转换器按清单逐张喂图跑标定。日志里 INT8 生效的直接证据是所有层类型齐刷刷变成INT8/INT32资源占用全面减半模型 7.3→4.1MBNPU 权重内存 6201→3152KB。INT8 模型上板推理脚本只改模型路径一行——lite2 运行时会自动把 INT8 输出反量化回 float32后处理零改动。开跑推理时间果然砍半55ms → 23ms。满心欢喜去看帧率——13.8 fps。推理明明够 20fps 了23ms 50ms帧率为什么上不去这次我学乖了不动模型先给循环加分段计时取流、预处理、推理、后处理每一段单独累积耗时让它自己交代。avg 15.00 fps, infer 23.4 ms | capture 33.5 prep 1.8 post 7.2 (ms/帧)耗时大户现形capture 33.5ms——取流加重色彩转换占了全程一半推理只占 23ms。回头看这 33.5ms 花在哪我手写的 BT.709 色彩转换是在 640×480 全分辨率上做 numpy 浮点逐像素运算Y、U、V 三个平面先各自 resize 对齐再五遍矩阵乘法扫过三十万个像素。关键观察是模型输入只要 320×320全分辨率的 BGR 只有存快照才用得上。于是把色彩转换整体搬到 1/4 分辨率Y 平面降到 320×240UV 本来就是半分辨率直接用709 矩阵在四分之一的像素上算旋转后得到 240×320 的小帧画框坐标按小帧映射快照每 5 帧写一次、放大回 480×640。BT.709 的系数一个没动只是算得粗了——色彩正确性零回归绝不能为了快换回 BT.601上一周已经实锤色偏是矩阵级错位增益类手段治不了这条弯路不能再走。改造前capture 33.5ms总 66ms → 15.0 fps 改造后capture 13.3ms总 47ms → 21.4 fps ✅验收达成。检测质量全程在线稳态一帧一人框准、色正。收尾时注意到一个有趣的小细节标签打出了person 1.46——置信度超过 1.0 了。这是 INT8 的量化噪声图里那个 sigmoid 的输出被量化为 8 位再反量化值略微越出 [0,1]。无害argmax 和阈值判断都不受影响但它是量化不是无损的最直观的物证比教科书上的任何解释都直观。六、最终数据、方法论与坑索引先看最终数据整个优化过程分四个阶段阶段单帧总耗时fps备注torch CPU第 2 周~1000ms0.7~1.0纯 CPU 的真实实力NPU FP32~66ms9.7模型上 NPUNPU INT8~46ms13.8推理 55→23ms瓶颈浮出INT8 预处理瘦身~47ms→含优化21.4capture 33.5→13.3ms验收达成累计提速21~30 倍模型体积 7.3→4.1MBNPU 内存占用减半。比数字更值钱的是这一路沉淀下来的两套方法论拆墙五步法第四部分现象离奇时不要慌——输入健康吗 → 输出什么分布 → 怀疑先排雷本步我走了弯路反面教材→ 做 A/B 探针实测运行时行为 → 对照实验定真凶。每一步只回答一个问题链条走完没有拆不掉的墙。分段计时找瓶颈第五部分性能优化的大忌是我以为瓶颈在模型。给循环每一段单独计时whale 自己现形然后按消费方需要什么分辨率倒推计算放在哪层而不是无脑全分辨率处理——模型只要 320色彩转换就不必在 640×480 上做。最后按惯例留一张坑索引方便对号入座坑一句话详见pip: command not foundvenv 没激活新开终端是常态一三个版本踩踏torch 降 2.4.0 / setuptools 钉 81 / onnx 钉 1.16.1一Invalid RKNN model version 6pip 的 lite2 是壳系统 librknnrt 太老换库三件套三满屏 0.50 假框导出分数已是概率重复 sigmoid NMS 格式传错四喂 float 0~1 全灭std255 归一化由运行时执行喂 uint8 原图四13.8 fps 上不去瓶颈在预处理不在推理分段计时定位后降分辨率做色彩转换五
返回列表