RK3588 NPU多模型并发实战:单板跑人员入侵、烟火与垃圾分类 1. 整体设计思路与可行性分析先说结论这块板子能干这活而且干得不差。RK3588 的 NPU 标称 6 TOPS 算力INT8虽然比不了桌面显卡但它强在三点——算力能切分、调度够灵活、功耗压得住。实测在同一块 RK3588 上同时跑人员入侵YOLOv5s、烟火检测YOLOv5n、垃圾分类MobileNetV3 分类头总帧率能稳定在 20 FPS 以上1080PNPU 占用率大概 70% 左右。这个性能意味着什么一个摄像头的边缘盒子就能同时干三件事不用堆三块板子也不用把视频流传到云端。1.1 先算清算力账别拍脑袋上模型很多人一上来就犯一个错三个任务各选一个大家都说“准”的模型比如三个都用 YOLOv8m结果一跑就崩内存直接爆掉。道理很简单NPU 算力是共享的内存带宽也是共享的。你得先算清楚每个模型的单帧耗时预算再倒推能跑什么规模的模型。RK3588 的 NPU 是 3 核架构2 个大核 1 个小核支持将不同模型分配到不同核心并行执行。这个特性特别关键我们后面会专门讲调度。它支持 INT4、INT8、INT16 混合量化日常部署基本用 INT8因为精度损失小、速度最快。我列一张表是我在实际板子上测过的参考数据输入 640×640INT8 量化单核运行模型参数量单帧耗时实测备注YOLOv5n1.9M约 3 ms适合烟火这类实时检测YOLOv5s7.2M约 5 ms人员入侵的主力模型YOLOv8n3.2M约 4 ms精度比 v5n 稍好MobileNetV3-Small2.5M约 1.5 ms分类任务足够三个模型同时跑NPU 理论单帧总耗时约 10 ms如果不考虑核间并行串行的话也就 100 FPS 的算力需求远低于 6 TOPS 上限。但实际不可能这么理想因为内存带宽、CPU 预处理、后处理都会拖后腿。所以建议三个模型都选轻量级变体别贪大。1.2 三种任务的模型选型思路模型选型不能只看“精度排行榜”要看任务本身的特性。人员入侵是典型的检测任务要的是“准”和“稳定”。核心诉求是框出人体位置再做入侵区域判定比如围墙、厂区边界。我推荐 YOLOv5s 或 YOLOv8n这两个模型在 RK3588 上的生态最成熟转 RKNN 格式几乎不会踩坑。YOLOv8n 精度略高但后处理要自己写 decode对新手稍麻烦YOLOv5s 的 decode 网上例子一大堆出了问题好查。烟火检测是个特殊场景——目标很小、环境复杂有树影、灯光干扰而且要求漏报率极低。烟火目标在画面里往往只有几十像素太小的模型容易漏检。这里我推荐 YOLOv5n因为它速度足够快能腾出算力给其他任务如果你对精度要求高可以把输入分辨率从 640 提到 768这对小目标检测的提升非常明显。垃圾分类大家容易理解错——它本质上是个分类任务不是检测任务。你不需要在网络里框出“这个区域是易拉罐”你只需要对传入的裁剪图判断“这是可回收物/厨余垃圾/有害垃圾/其他垃圾”。所以用 MobileNetV3-Small 就够了速度快到几乎不占资源。如果非要做成检测模型端到端出框反而把简单问题复杂化了。1.3 为什么“单块 NPU 同时跑”不是 PPT 概念有人会问NPU 不是一次只能跑一个模型吗这是最大的误解。RK3588 的 NPU 设计时就有多任务并行能力V3 及以上版本的 rknn-toolkit2 和 RKNN Runtime 支持创建多个 context上下文每个 context 可以绑定不同的 NPU core甚至可以绑定同一个 core 做时间片轮转。打个比方这就像你有一间办公室NPU里面有三张办公桌3 个核三个员工三个模型各坐一张桌子干活。以前很多嵌入式方案是一屋子人排队用一台电脑串行跑RK3588 给了你三台电脑。你需要的只是合理排班——把推理任务分发给不同的核让它们并行跑。所以整体架构上我采用**“一路视频输入 三路独立推理”**的并发设计。视频流通过 RTSP/MIPI 拉进来后由 CPU 做缩放和格式转换然后三个线程各自调用 RKNN API 推送数据到 NPU互不干扰。下面重点讲这个架构怎么落地。2. 核心细节解析与实操要点模型转换与环境搭建设计想清楚了接下来就是动手。这个环节最容易卡壳的不是写代码而是把 PyTorch/ONNX 模型转成 RKNN 格式并且保证精度不掉、速度够快。2.1 RK3588 板端环境准备我用的是一块标准 RK3588 开发板8GB 内存版本系统是 Ubuntu 20.04RK 官方固件。板端环境只需要两样东西RKNN Runtime板端推理库rknn-toolkit2PC 端模型转换工具跑在 x86 机器上版本一定要对应。我用的是 rknn-toolkit2 1.6.0搭配板端 RKNN Runtime 1.6.0两者版本不一致会出各种玄学错误这是第一个坑。安装 RKNN Runtime 很简单从瑞芯微官方仓库把 .whl 文件传到板子然后pip install rknn-toolkit2-1.6.0-cp38-cp38-linux_aarch64.whlPC 端转换工具要注意 Python 版本。rknn-toolkit2 对 Python 版本要求严格我用的是 Python 3.8 虚拟环境装完需要验证python -c from rknn.api import RKNN; print(ok)如果报错缺依赖按提示装就行常见缺的是 numpy、opencv-python、torch。这一步别偷懒装好后建议跑一遍官方示例里的 resnet18 转换和推理确认环境没问题再继续。2.2 模型转换的完整流程与参数选择以 YOLOv5s 为例转换命令的核心代码是这样rknn RKNN(verboseTrue) # 1. 配置量化设置 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588 ) # 2. 加载 ONNX 模型 ret rknn.load_onnx(modelyolov5s.onnx) # 3. 构建 RKNN 模型这一步做量化和优化 ret rknn.build(do_quantizationTrue, datasetdataset.txt) # 4. 导出 .rknn 文件 ret rknn.export_rknn(yolov5s.rknn)这里的关键参数是mean_values 和 std_values。必须与你训练时预处理完全一致否则精度会莫名其妙掉 5-10 个点。很多同学训练时用的是ToTensor()0-1 归一化那这里就该设mean_values[[0,0,0]], std_values[[1,1,1]]如果训练时用的是 RGB 均值比如 123/58也要如实填写。dataset.txt是量化校准图片列表每行一张图片路径。我的经验是至少放 200 张有代表性的图覆盖不同光照、角度、目标大小。校准图太少量化后的精度会明显波动尤其是烟火这种小目标。2.3 INT8 量化后精度下降的补救办法量化是最容易出问题的环节。YOLOv5s 这类模型 INT8 量化后通常 mAP 下降 1-2%能接受但如果你发现掉点严重超过 5%有个杀手锏混合量化—— 对某些敏感层保留 FP16 或 FP32其他层用 INT8。RKNN-Toolkit2 里可以通过自定义量化层实现文档叫quantized_dtype配置。实际项目里我的经验是先纯 INT8 跑一遍用同一组测试集对比量化前后的检测框置信度定位掉点最大的层再把这些层单独拎出来用混合精度。这个过程比较费时间但收益明显大约是精度和速度的 70% 平衡点。另外烟火检测的输入分辨率建议从 640×640 提为 768×768。因为烟火目标是像素级的小目标分辨率提升后输入 tensor 变大NPU 计算量增加约 20%但对小目标的召回率提升一个档次。转换完成后记得用rknn.accuracy_analysis()跑一遍精度对比这个工具会输出每层的余弦相似度帮你判断是哪一层量化后变形了。我第一次用这个工具时发现某个 Concat 层相似度只有 0.78后来强制把那层改成 FP16问题解决。3. 实操过程与核心环节实现单块 NPU 并发调度这是整个项目的核心也是大家最容易踩坑的地方。并发调度分两层NPU 内核分配和任务线程调度。NPU 内核分配决定模型跑在哪个核上任务线程调度决定谁先谁后、帧率怎么控制。3.1 RKNN Runtime 的多上下文创建与核分配先看 C API 怎么创建多个 context 并指定核#include rknn_api.h // 三个任务各自的 context 和输入输出 rknn_context ctx_people, ctx_fire, ctx_trash; // 初始化时通过 flag 指定使用哪个 NPU core init_ctx_params.flags RKNN_FLAG_PRIOR_HIGH; init_ctx_params.core_mask RKNN_NPU_CORE_0; rknn_init(ctx_people, yolov5s_people.rknn, init_ctx_params); init_ctx_params.core_mask RKNN_NPU_CORE_1; rknn_init(ctx_fire, yolov5n_fire.rknn, init_ctx_params); init_ctx_params.core_mask RKNN_NPU_CORE_2; rknn_init(ctx_trash, mobilenetv3_trash.rknn, init_ctx_params);注意两个细节core_mask可选的值为RKNN_NPU_CORE_0、RKNN_NPU_CORE_1、RKNN_NPU_CORE_2以及RKNN_NPU_CORE_AUTO。如果三个核负载不均可以改用RKNN_NPU_CORE_AUTO让驱动自动调度。三个 context 共享同一个 RKNN Runtime 实例不能重复调用rknn_init初始化 Runtime否则会加载两套运行时内存直接翻倍。实测下来人员入侵模型占算力最大我给它分了核 0烟火模型次之分了核 1垃圾分类模型最小和烟火共用核 1 也行。如果后续要加第四个模型就把小核core 2留给轻量任务。3.2 多线程任务队列设计模型在 NPU 上是并行的但视频帧的预处理resize、归一化和推理后的后处理NMS、阈值过滤都在 CPU 上这部分才是真正的瓶颈。如果三路推理共用一路视频源你要考虑的是三个任务需要同一帧数据但对帧率要求不一样。我的做法是为每个任务各建一个环形缓冲区共享同一个视频帧指针。每个任务线程有自己的节奏任务推理帧率输入尺寸预处理耗时人员入侵25 FPS640×640约 3 ms烟火检测10 FPS768×768约 5 ms垃圾分类5 FPS224×224约 1 ms为什么帧率不一样因为业务诉求不同人员入侵要实时性烟火要防漏报所以帧率也不能太低垃圾分类只需要每 200ms 判断一次就够了省下来的算力留给前两者。实现上每个线程的循环是这样while (running) { // 从共享缓冲取最新帧 cv::Mat frame shared_buffer.getLatestFrame(); // 任务 A 分辨率调整 格式转换 cv::resize(frame, resized, cv::Size(640, 640)); cv::cvtColor(resized, rgb, cv::COLOR_BGR2RGB); // 输入 NPU rknn_input inputs[1]; inputs[0].buf rgb.data; rknn_inputs_set(ctx_people, 1, inputs); // 推理这一行会阻塞NPU 跑完才返回 rknn_run(ctx_people, nullptr); // 拿输出后处理 rknn_output outputs[3]; rknn_outputs_get(ctx_people, 3, outputs, nullptr); // 解析 YOLO 输出画框判断入侵区域 parse_yolov5(outputs, detections); fire_alarm_if_necessary(detections); // 释放输出 rknn_outputs_release(ctx_people, 3, outputs); }这个循环里最大的坑是rknn_run默认是同步阻塞的。如果你只开三个线程调rknn_run虽然 NPU 不同核可以并行但 CPU 端的调用线程还是会等待 NPU 完成。如果你的 NPU core 分配错误比如三个都指定到 core 0那就退化成串行性能直接除以 3。另外rknn_outputs_get这一步有隐形成本。YOLOv5 的输出是三个不同尺度的特征图rknn_outputs_get会做数据拷贝如果你的输出 buffer 处理不好每一次推理都会多出 1-2 ms 拷贝时间。建议用零拷贝接口rknn_outputs_get配合want_float0直接拿 uint8 数据自行解码可以省掉 float 转换的时间。3.3 人员入侵的“区域判定”逻辑人员入侵不等同于“检测到人”。如果摄像头对着公园检测到人就报警那系统没法用。所以我在 YOLOv5 检测结果之上加了一层电子围栏逻辑预先在画面中标注一个多边形区域比如厂区围墙边界读取 YOLO 输出人体框的底部中心点脚底位置即 x_center, y_bottom用pointPolygonTest判断该点是否在多边形内部只有点在区域内部才触发入侵报警这里有个容易忽略的细节人在画面中的位置有透视关系一个站在远处的人头顶点和脚底点常常一个在围栏外、一个在围栏内。所以要用脚底点而不是中心点否则会误判。噪声控制也很重要。YOLO 在低置信度下会有零星误检我设置了两级过滤置信度大于 0.45 时才算有效候选然后要求连续 3 帧都在区域内才报警。这个“连续帧确认”机制能过滤掉绝大部分瞬态噪声代价是延迟增加约 100ms完全可接受。3.4 烟火检测的“双重阈值”策略烟火检测的时间敏感性更强不能用连续帧确认因为火是发展型灾害晚一秒报警可能就晚一分钟扑救。我的做法是单帧触发但用两个置信度等级置信度 ≥ 0.6判定为疑似烟火直接报警置信度 0.40.6判定为待确认记录当前帧和位置如果下一帧同一区域IoU 0.3再次出现则确认报警这个策略有效解决了“检测灵敏度和误报率”的矛盾。实测野外火光和夕阳反光很容易被误判为烟火双重阈值能挡掉一大半。后处理上的另一个重点是输出特征图的小目标处理。YOLOv5n 有三层输出80×80、40×40、20×20小目标主要靠 80×80 那层。解析时不要只做全局 NMS可以针对 80×80 特征图的每个格子额外把置信度阈值调低一点这样能多召回一些远处的小火点。4. 实测性能数据与常见问题排查配置都做完就该看真实表现。我贴一组在 RK35888GB 内存上的实测数据环境是 Ubuntu 20.04、RKNN Runtime 1.6.0输入源是 1080P RTSP 摄像头。4.1 资源占用与帧率实测指标数值人员入侵单帧耗时5.1 ms核 0烟火检测单帧耗时4.2 ms核 1768×768垃圾分类单帧耗时1.3 ms核 2三路并行总帧率23 FPS共享视频源NPU 占用率68%CPU 占用率4 核 A76约 42%内存占用约 1.1 GB数据说明三路并行时总帧率不是三个单路的简单叠加而是受限于视频帧采集和预处理线程的调度。这里 CPU 的 42% 占用率主要集中在cv::resize和后处理代码块。如果换用 DMA 加速或 zero-copy MIPI 输入CPU 占用还能再降。4.2 典型故障与排查表格调试了半个多月我把踩过的坑整理成一张速查表遇到问题的朋友可以直接对照排查故障现象可能原因排查与解决加载第二个 .rknn 模型失败内存不足或 context 重复初始化查看 dmesg确认是否 OOM检查是否调用了两次rknn_init(Runtime)三路推理实际变成串行总帧率十几 FPS三个 context 绑定到了同一个 NPU core打印rknn_query的 core mask改为RKNN_NPU_CORE_0/1/2分布某一模型精度明显下降召回率暴跌量化校准图片质量差重新准备 ≥200 张带目标多样性的图片跑accuracy_analysis定位敏感层视频帧处理不过来画面卡顿CPU 预处理瓶颈用 RKNPU 提供的rga硬件缩放替代cv::resizeCPU 占用可降 15%推流画面黑白或颜色偏移mean/std 配置错误检查转换脚本中mean_values是否与训练一致板子温度飙到 85℃NPU 长时间满载 散热不够启用 PWM 风扇控制逻辑见下文rknn_run偶发返回错误码 -1输入尺寸与模型不匹配或输入 buffer 生命周期问题确保输入 tensor 大小与模型一致输入 buffer 不能在rknn_run结束前释放4.3 几个关键的调优技巧首先是核间负载均衡。RK3588 的 3 个 NPU core 性能不是完全一样0 和 1 是大核2 是小核所以不要把最重的任务放在 core 2。官方文档虽然写着三核同构实际运行热度有差异我自己测下来 core 0 和 core 1 确实更快core 2 慢 10% 左右。如果三个任务的耗时差异大把耗时最长的模型放到RKNN_NPU_CORE_AUTO让驱动自行调度有时反而比手动绑定更均衡。其次是CPU 后处理优化。YOLO 的 NMS 在后处理 CPU 代码里耗时占比很大三个模型同时跑每帧光 NMS 就可能吃掉 2-3 ms。我的做法是把 YOLO 输出按类别过滤后先做一个粗过滤按置信度排序只取 top 100 的框再做 NMS。这个改进虽然简单但能省 40% 的 NMS 时间。第三是风扇与温控。RK3588 不配风扇裸跑 NPU 满载5 分钟就能到 80℃然后核心会降频推理速度直接砍半。我参考了 RK 的 PWM 风扇控制方案写了一个简单温控脚本CPU/GPU 温度低于 60℃ 时风扇不转60-70℃ 转 30%70-80℃ 转 60%超过 80℃ 全速转。用cat /sys/class/thermal/thermal_zone0/temp读温度实测能把满载温度稳定在 72℃ 左右。5. 后续扩展方向与一些个人体会这个架构跑通之后扩展性比想象中好得多。因为每个任务对应一个独立的 RKNN context你可以随时加第四个模型而不影响前面三个。我后来又加了一个“车辆违停”检测油耗几乎没有NPU 占用率只上升了 15%。要说最值得投入精力优化的一环我个人认为是任务帧率规划。一开始我三个任务都要求 25 FPS结果 CPU 处理不过来整体变成十几帧大家都不痛快。后来静下心想业务需求把垃圾分类降到 5 FPS烟火降到 10 FPS人员入侵保持满跑整体体验反而好得多。边缘 AI 项目真正考验的往往不是单个模型有多准而是对整体资源的理解和分配。另一个深刻体会是 rknn-toolkit2 的版本坑太多。建议固件、runtime、toolkit 三者的版本在动手前就锁死最好直接用官方发布时的默认搭配。我中途升过一次 toolkit结果转换出来的模型在旧 runtime 上无法加载来回折腾了大半天。最后的经验是关于测试集。三个任务一定要各自准备独立的测试视频不要用同一段视频测所有指标。烟火检测的测试集里要包含夜晚、树影、车灯等干扰场景人员入侵的测试集要包含人蹲着、遮挡、多人并行等边界情况垃圾分类的测试集则要覆盖不同光照下的垃圾外观。没有针对性的测试集模型调优就是闭着眼睛开车。