拓冰建站拓冰建站
首页 / 资讯中心 / 正文

RK3588 NPU并发调度实战:单板同时跑通入侵检测、烟火识别与垃圾分类

拿到一块 RK3588很多人第一件事就是跑通一个 YOLO然后发个朋友圈庆祝。但我这个项目没这么轻松一块 RK3588 的 NPU 上要同时支撑人员入侵检测、烟火检测、垃圾分类三个 AI 任务。这三个任务听起来是三个模块实际上就是三套模型、三条推理管线、三份后处理逻辑而且全挤在同一个 6 TOPS 的 NPU 上。这篇文章把我从选模型、转 rknn、写多线程推理、调帧率到踩坑排错的全过程都写出来准备做边缘智能盒、工地园区监控一体机或者想把 RK3588 NPU 性能榨干的朋友可以直接拿这份方案去改。先说结论单块 RK3588 完全能同时跑这三个任务但绝对不是“三个模型分别推理”这么简单。真正的难点在 NPU 核心怎么分配、多线程推理怎么调度、视频帧怎么同步以及后处理怎么做到既快又稳。下面我会把整个项目的设计思路、模型转换、并发实现、帧调度、性能实测和常见问题全部拆开讲里面有不少是我实测踩坑换来的经验常规文档里不会写这么细。1. 项目概述与整体设计思路1.1 这个需求到底是谁提出来的这类需求在智能安防、智慧园区里非常常见。一个典型场景是这样的一台 RK3588 边缘计算盒子上接到一路或者几路网络摄像头。摄像机 A 对着围栏区域要检测有没有人翻越闯入这叫人员入侵摄像机 B 对着仓库角落或者杂物堆要实时识别有没有火光和烟雾这叫烟火检测还有一个摄像头或者一个触发信号对着垃圾桶投放口人要扔垃圾的时候拍一张照片识别这袋垃圾属于可回收、厨余、有害还是其他这叫垃圾分类。三个任务如果分开每个都单独部署一台设备成本翻三倍客户肯定不接受。于是全部压到一块 RK3588 上。RK3588 这块芯片本身就是为了边缘 AI 设计的内置的 NPU 理论算力 6 TOPSINT8三核设计比很多同价位的开发板强不少这也是它能扛住三个模型并发的重要原因。1.2 硬件平台选型与系统环境我用的是一块常见的 RK3588 评估板8GB 内存版本带散热风扇和 12V/3A 电源。系统是官方的 Debian 11内核 5.10NPU 侧用的是 Rockchip 的 rknpu2 驱动和配套的 librknnrt.so 运行时。这里有个比较关键的点RKNN-Toolkit2 的 PC 端版本和板端运行时版本要匹配否则模型在板子上加载会直接失败。我自己踩过多版本混用的坑建议直接用当时官网配套发布的最新版本并且在板子上用 rknn-toolkit2-lite 自带的 demo 先跑通一遍确认环境没问题再开始做项目。摄像头接入我用的是支持硬件解码的 GStreamer 管线RK3588 自带的 MPP 硬解能非常有效地把 H.264/H.265 视频流的解码压力从 CPU 上卸下来。软件框架是 C 写的推理部分用 Rockchip 的 C API没有用板端 Python因为 Python 在频繁调用 NPU 时解释器开销和内存抖动比较明显生产环境还是 C 更可控。1.3 一切困难都出在“同时”两个字上如果只是顺序跑三个模型事情非常简单加载三个 rknn 文件一帧一帧依次跑一遍就行。但实际项目里视频源是 25 帧每秒如果三个模型串行处理每一帧一帧的总延迟可能超过 100 毫秒帧率直接掉到不到 10 FPS而且后面模型处理的是前面模型延迟之后的旧画面报警联动会慢半拍。“同时”两个字背后包含三个维度的挑战算力分配NPU 只有 3 个核心3 个模型同时跑是轮流占用全核还是各自绑定一个核直接决定吞吐量和单模型延迟。内存管理每个模型要独立加载权重、输入输出缓冲区三个模型同时常驻要避免内存碎片和反复申请释放。帧同步一路视频流要喂给多个推理线程如果处理不过来是排队还是丢帧逻辑上必须想清楚。这三个问题就是我们后面所有设计和调优的主线下面逐个展开。2. 模型选择、训练与 RKNN 转换2.1 人员入侵检测选 YOLOv5s 还是 YOLOv8s人员检测是目标检测里最成熟的方向之一所以我不推荐自己从头训练一个检测网络直接用开源 YOLO 系列预训练模型然后用目标场景的数据做微调。我这里对比过 YOLOv5s、YOLOv6s、YOLOv8s 三个常见系列的 RKNN 部署效果。实际部署下来YOLOv5s 在 RK3588 上对算子兼容性最好导出 ONNX 后基本不用改图就能转 rknnYOLOv8s 检测精度更好尤其是对小目标的召回率但模型结构里 DFL 模块在导出时要注意某些 RKNN 版本会报不支持。如果你对精度要求更高可以用 YOLOv8s但要先在 PC 端把完整导出流程跑通再上板验证。人员入侵检测的输入分辨率我建议固定 640×640。有人为了提速降到 416×416但入侵场景通常要求尽可能早地发现人小分辨率会让较远处的人检测不稳定反而误报漏报变多。用 YOLOv5s 640×640 INT8 量化后的 rknn 模型在 RK3588 上跑一次推理大概 20 到 30 毫秒三核全用这个速度完全够 25 FPS 实时处理。2.2 烟火检测小目标和误报是最大的坑烟火检测和人员入侵不一样。人员入侵检测只要框出“人”就行但烟火检测要同时区分“火”“烟”和大量看起来很像的干扰物红色车灯、晚霞、白色水汽、工地扬尘、喷雾都可能被模型误判成火或者烟。我的做法是用 YOLOv5s 作为基础结构但训练数据里除了公开的火灾烟雾数据集外额外加入了大量负样本和难样本——比如工地常见的水泥扬尘、雾天的背景、红色的机械设备。类别就两类fire 和 smoke。训练时用 mosaic 和 mixup 增强把火焰的形态、烟雾的半透明特性尽量学进去。部署时的输入分辨率仍然保持 640×640这比把图缩小更重要因为烟火目标往往不大。后处理阶段我会做“时序确认”一个报警必须连续 3 帧以上都能检测到才触发单帧偶发的高置信度直接忽略。这个策略非常有效实测能把误报率降低一半以上而且对真实的火灾烟火影响不大因为真火和真烟在视频里一般会持续多帧出现。2.3 垃圾分类不要用目标检测网络用轻量分类器垃圾分类这个任务和前面两个不同它本质上是一个图像分类问题不是在画面里找物体位置。一开始有人想用 YOLO 直接检测垃圾然后按类别框出来但这个做法性价比太低了。我们场景是垃圾桶投放口人在投放时画面里主体就是那袋垃圾直接裁出来丢给分类网络就行。我选的是 MobileNetV3-Small输入分辨率 224×224权重只有几 MB在 RK3588 NPU 上推理单核只要几毫秒。分类类别按实际项目需求定为四类可回收垃圾、厨余垃圾、有害垃圾、其他垃圾。如果你项目的类别更细比如要区分塑料瓶、纸箱、易拉罐、电池那就收集对应类别的数据网络结构不用变只改最后一层分类头。垃圾分类模型的训练数据并不难弄公开的垃圾分类数据集有不少自己针对投放口场景补拍 200 到 500 张每类图片就足够做微调。这个任务里最容易被忽略的是光线问题投放口环境光照不稳定白天晚上差别很大训练时一定要做亮度扰动和模糊扰动否则在晚上识别率会掉得很明显。2.4 ONNX 转 RKNN 完整流程与量化经验RK3588 的 NPU 不能直接跑 PyTorch 或 TensorFlow 模型需要把模型转成 rknn 格式转换工具是 PC 端的 rknn-toolkit2。整个流程我在 PC 上完成转换脚本核心代码如下from rknn.api import RKNN rknn RKNN(verboseTrue) rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, quantized_dtypew8a8, optimize_level3 ) ret rknn.load_onnx(modelyolov5s.onnx) assert ret 0, load onnx failed ret rknn.build(do_quantizationTrue, datasetcalib.txt) assert ret 0, build failed ret rknn.export_rknn(yolov5s.rknn) assert ret 0, export failed这里最需要注意的就是 mean_values 和 std_values。YOLOv5/YOLOv8 训练时把输入图像归一化到 0 到 1所以这里 mean 用 0std 用 255让 NPU 端直接对 0 到 255 的原始像素做归一化跟模型训练时保持一致。垃圾分类如果用 ImageNet 预训练的分类网络mean 和 std 要用 ImageNet 那组常见统计值比如 mean[123.675, 116.28, 103.53]std[58.395, 57.12, 57.375]。这个值如果不对分类结果会很差而且是那种“看不清但想不通为什么”的坑。量化这一步需要准备一个 calib.txt 文件里面列出几百张有代表性的图片路径用来做 INT8 量化校准。图片要尽量覆盖真实场景的光线、角度、目标大小。烟火检测如果对 INT8 精度不满意可以试一下混合量化把某些对精度影响大的层保留 FP16这个路径在 rknn-toolkit2 里叫 hybrid_quantization能明显改善小目标的检测精度代价是模型变大、推理变慢建议作为精度兜底方案而不是默认方案。提示导出 ONNX 时一定要去掉模型里的 NMS 层rknn 是不带 NMS 推理的NMS 要在板端自己用 CPU 写。YOLO 官方仓库的 export.py 一般有 no-nms 参数网上各种坑基本都是这一步没做到位。3. NPU 并发调度单卡跑三个模型的实现方案3.1 先搞懂 RK3588 NPU 的并发能力要谈并发得先知道 NPU 内部是怎么工作的。RK3588 的 NPU 有三个核心每个核心内部都是一大堆 MAC 阵列也就是乘累加单元组成的计算网格。卷积运算在这里被拆成大量并行的乘加操作MAC 阵列的数量决定了理论算力。三个核心合起来是 6 TOPS INT8 算力平均每个核心约 2 TOPS。在软件层面Rockchip 的 rknpu2 运行时支持同时存在多个 rknn_context每个 context 对应一个模型。多个 context 可以分别设置核心绑定关系这就是我们做并发的底层基础。另一个重要接口是 rknn_set_core_mask它允许你在推理时动态指定当前模型用哪个核或者哪几个核。核绑定不是一次性设置而是每次推理前都可以改这就给了我们非常灵活的调度空间。3.2 三种并发策略对比我在项目里实际对比了三种方案第一种是全局串行也就是三个模型每次都分别占用全部三个核心依次推理。这样每个模型单次推理最快但处理完一帧三个任务的总耗时是三项相加帧率上不去适合对单帧延迟要求低、对吞吐量要求不高的场景。第二种是核心绑定并行每个模型固定绑一个核三个模型真正同时跑。这个方案下每个模型的单次推理变慢因为算力只有原来的三分之一但三个任务能同时进行整体吞吐是三个模型并行叠加。我们实测 YOLOv5s 640 输入单核推理大约 55 到 60 毫秒也就是说每个模型大约能跑到 16 到 18 FPS对安防场景完全够用。第三种是动态组合把重模型绑两个核、轻模型绑一个核两个核的模型和单核的模型并行。这个方案适合模型权重不平衡的情况比如人员检测和烟火检测都很重但垃圾分类很轻可以让垃圾分类和另外两个模型共用时间片或者单独占用一个核。三种方案的对比我整理成了一张表并发策略单模型延迟整体吞吐实现复杂度适用场景全核串行最低最低最简单三个任务不同步触发核绑核并行变高最高中等三个任务都要持续实时动态组合可调中等偏高较复杂模型权重差异大我最终选的是第二种人员入侵绑 core 0烟火检测绑 core 1垃圾分类绑 core 2。这样每个模型都有独立算力不会互相干扰逻辑也最清晰。实际部署时我会在每个推理线程启动后加一个 0 到 3 毫秒的随机初始延迟避免三个线程同时调用 rknn_run 撞车虽然新版驱动已经优化了不少但这个习惯能减少启动瞬间的 CPU 争抢。3.3 关键代码多线程推理框架整个推理框架可以抽象成一个独立的模型类每个模型一个线程。核心结构如下#include rknn_api.h #include thread #include mutex #include atomic #include opencv2/opencv.hpp struct ModelCtx { rknn_context ctx; rknn_input_output_num io_num; vectorrknn_tensor_attr in_attrs; vectorrknn_tensor_attr out_attrs; int core_id; // 0/1/2 };加载模型时每个模型单独初始化int load_rknn(ModelCtx mc, const char *path) { FILE *fp fopen(path, rb); fseek(fp, 0, SEEK_END); size_t size ftell(fp); fseek(fp, 0, SEEK_SET); void *model_data malloc(size); fread(model_data, 1, size, fp); fclose(fp); int ret rknn_init(mc.ctx, model_data, size, 0, NULL); free(model_data); if (ret 0) return ret; rknn_query(mc.ctx, RKNN_QUERY_IN_OUT_NUM, mc.io_num, sizeof(mc.io_num)); mc.in_attrs.resize(mc.io_num.n_input); mc.out_attrs.resize(mc.io_num.n_output); for (uint32_t i 0; i mc.io_num.n_input; i) { mc.in_attrs[i].index i; rknn_query(mc.ctx, RKNN_QUERY_INPUT_ATTR, mc.in_attrs[i], sizeof(rknn_tensor_attr)); } for (uint32_t i 0; i mc.io_num.n_output; i) { mc.out_attrs[i].index i; rknn_query(mc.ctx, RKNN_QUERY_OUTPUT_ATTR, mc.out_attrs[i], sizeof(rknn_tensor_attr)); } return 0; }推理线程的主循环长这样每次从帧池里拿最新帧做完预处理后设置核心绑定再执行推理void infer_loop(ModelCtx mc, std::mutex frame_lock, cv::Mat latest, std::atomicbool running) { while (running) { cv::Mat frame; { std::lock_guardstd::mutex lk(frame_lock); if (latest.empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } frame latest.clone(); } cv::Mat resized, blob; letterbox(frame, resized, 640, 640); cv::cvtColor(resized, blob, cv::COLOR_BGR2RGB); rknn_set_core_mask(mc.ctx, core_mask_from_id(mc.core_id)); rknn_input inputs[1] {0}; inputs[0].index 0; inputs[0].type RKNN_TENSOR_U8; inputs[0].size blob.total() * blob.elemSize(); inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].buf blob.data; rknn_inputs_set(mc.ctx, 1, inputs); rknn_run(mc.ctx, nullptr); vectorrknn_output outputs(mc.io_num.n_output); for (uint32_t i 0; i mc.io_num.n_output; i) { outputs[i].want_float 1; outputs[i].index i; } rknn_outputs_get(mc.ctx, mc.io_num.n_output, outputs.data(), nullptr); // 在这里解析输出做对应任务的后处理 run_post_process(mc, frame, outputs.data()); rknn_outputs_release(mc.ctx, mc.io_num.n_output, outputs.data()); } }这个框架非常简单但已经很接近生产级。要注意的是 rknn_outputs_get 拿到的输出如果不及时解析并调用 rknn_outputs_release 释放跑一段时间会内存泄漏这个问题在性能监控里非常容易被当成“板子内存不够”来排查实际罪魁祸首就是漏释放。3.4 内存与零拷贝优化三个模型同时常驻内存占用大概是两个 YOLOv5s INT8 模型每个权重加中间计算缓冲约 100MB 左右MobileNetV3-Small 更小几十 MB加上系统、OpenCV、视频解码缓冲8GB 内存版本完全没压力。如果你用的是 4GB 版本也能跑但要小心别让 OpenCV 的调试窗口和日志把内存吃满。真正的优化重点在数据拷贝。默认输入路径会把 cv::Mat 的数据拷贝给 NPU对 640×640 的图来说一次拷几千字节不是问题但每秒几十次调用累积起来 CPU 占用并不小。追求极致性能的话可以用 rknn_create_mem 分配 NPU 侧输入输出内存配合 rknn_set_io_mem 做零拷贝推理。这个方案代码复杂度高一些而且多个模型同时用零拷贝内存对齐和生命周期管理要格外小心。我建议前期先用普通输入路径把流程跑通性能不够再用零拷贝优化不要一上来就追求最复杂方案。预处理上还有一个省 CPU 的技巧三个模型输入分辨率不一样人员检测和烟火检测都是 640×640垃圾分类是 224×224。可以让视频帧先统一缩小到 960×540 左右然后每个模型各自再做 letterbox 缩放。虽然看上去多做了一次 resize但避免了三个模型各自从 1920×1080 原始分辨率做缩放实际总 CPU 开销更小。4. 视频流接入与帧调度实战4.1 摄像头接入RTSP 和本地摄像头怎么选安防场景里最常用的是 RTSP 网络流。RK3588 接入 RTSP 一定不要用 CPU 软解要用 GStreamer 加 mpph264dec 硬解管线。我在代码里用 OpenCV 的 CAP_GSTREAMER 后端管线这样写rtspsrc locationrtsp://192.168.1.64:554/stream1 latency0 ! rtph264depay ! h264parse ! mpph264dec ! videoconvert ! video/x-raw,formatBGR ! appsinklatency 设为 0 非常关键。默认的 RTSP 缓冲会给视频流增加几百毫秒延迟对于人员入侵这种需要快速报警的场景延迟越少越好。实际使用中如果网络环境差可以把协议改成 TCP也就是在 rtspsrc 里加 protocolstcp画面会更稳定但代价是有线网络延迟会稍高一点。如果是 USB 摄像头或 MIPI 摄像头走 V4L2 接入就行注意把摄像头分辨率设成 1920×1080 或 1280×720 这类标准分辨率不要用不常见的分辨率否则 mpp 硬解和后续 letterbox 的兼容性都可能出问题。4.2 帧同步与跳帧策略多个推理线程共享一路视频流时最忌讳的是用队列把所有帧都保存下来。假设摄像头是 25 FPS但你的推理速度只有 17 FPS队列会越积越长报警延迟越来越大。我的做法是共享“最新帧”缓冲区每个推理线程每次直接从共享变量里拿当前最新帧的拷贝处理完再拿下一帧。如果推理期间来了好几帧新画面直接跳过永远处理最新帧。这种策略的本质是“宁可跳帧不可积压”。对实时视觉任务来说处理 0.5 秒前的画面比处理 0.1 秒前但卡住很久的画面更有价值。丢帧其实没所谓因为视频本来就是连续的你的算法每秒钟能处理十几帧已经能完整覆盖实际场景里的运动变化。垃圾分类这个任务比较特殊它的触发不是每帧都需要而是投放动作发生时拍一张照。实际操作时我用了一个触发信号比如 GPIO 检测到投放口门打开或者简单一点用人员检测的结果当检测到人员框出现在垃圾桶投放口区域时抓当前帧做裁剪和分类。这样垃圾分类线程大部分时间闲置几乎不占 NPU 资源把核心 2 留给其他突发任务或者做多路视频轮询都可以。4.3 后处理入侵判定、烟火判定、分类结果联动后处理是整个系统里最体现工程经验的部分。YOLO 模型输出的只是原始的预测框数据要变成业务报警还有很长一段路。人员入侵的判定我是这样做的先用 YOLO 检测出人的框取框底部中心点作为人的落脚点然后判断这个点是否进入了预先设定的虚拟围栏区域。虚拟围栏用多边形表示可以在配置界面里人工画。判断点是否在多边形内部用经典的射线法代码很短bool point_in_polygon(float x, float y, const vectorcv::Point2f poly) { bool inside false; int n poly.size(); for (int i 0, j n - 1; i n; j i) { float xi poly[i].x, yi poly[i].y; float xj poly[j].x, yj poly[j].y; if (((yi y) ! (yj y)) (x (xj - xi) * (y - yi) / (yj - yi) xi)) inside !inside; } return inside; }选定落脚点而不是人的中心点是为了让人跨过围栏线时能精确判断“进来了”还是“还没进来”。如果用中心点人一半身子探入围栏区域时中心点还在外面报警会慢半拍。烟火检测的后处理核心是抑制误报。单帧检测到火或者烟还不能立刻触发报警我用了一个五帧滑窗五帧里有三帧以上检测到才真正报警。这个策略能过滤掉大量单帧噪声。另外烟和火的置信度阈值我分开设火一般设 0.4烟设 0.35因为烟的视觉特征更模糊阈值太高容易漏报小烟头。垃圾分类的结果直接和业务联动。分类置信度大于 0.7 时直接输出类别置信度低于 0.5 时输出“未识别”提示用户重新投放中间段可以配一个“不确定”状态让用户在屏幕上确认。报警和识别结果通过 MQTT 上报上级平台同时联动 RK3588 的 MPP 硬编码模块对报警前后各 10 秒的视频进行本地录像。这里我特意用硬件编码而不是软件编码就是为了在推理高负载时不让 CPU 编码抢走太多资源。5. 性能实测与优化方向5.1 实测数据与资源占用整个系统调通后我在 1080p 的 RTSP 视频源上做了连续 12 小时的稳定性测试用 top 和 /sys/kernel/debug/rknpu 下面的节点观察资源占用。核心数据如下任务模型输入尺寸绑定核心单次推理延迟实际帧率人员入侵YOLOv5s INT8640×640core 0约 58ms约 16 FPS烟火检测YOLOv5s INT8640×640core 1约 57ms约 16 FPS垃圾分类MobileNetV3-Small INT8224×224core 2约 8ms触发式系统整体 CPU 占用大约 60% 到 80%主要消耗在 OpenCV 的 resize、letterbox 以及 YOLO 后处理的 NMS 上NPU 占用接近满载。内存稳定在 1.5GB 上下没有持续增长。这个数据说明三个任务在单块 RK3588 上完全跑得动而且还有一定余量。如果你的业务要求更高帧率比如人员入侵要跑到 25 FPS可以考虑把人员检测模型换成 YOLOv5n单核延迟能压到 35 毫秒左右精度下降有限但对小目标确实不如 v5s。这是一个经典的精度和速度取舍得看具体场景。我自己在正式项目里宁可保持 16 FPS 也要用 v5s因为人员漏报比帧率低更致命。5.2 瓶颈分析与优化手段从实测数据看NPU 推理本身不是唯一瓶颈CPU 端的预处理和后处理经常被忽略。我测过一次完整的 YOLO 推理里NPU 部分大约 58 毫秒但预处理加后处理也要接近 15 到 20 毫秒。如果 CPU 被系统其他任务抢占这个数字会更难看。优化手段集中在三条线。第一是让 OpenCV 用上 NEON 优化RK3588 是 ARM 平台编译 OpenCV 时开启 NEON 和 VFPV3resize 和 cvtColor 能快 20% 到 30%。第二是把 NMS 做大核上的多线程版本或者用更高效的非极大值抑制实现YOLOv5 自带的 NMS 在单线程上处理 640×640 的输出有一定开销改成多线程后整体延迟能少 3 到 5 毫秒。第三是用输入输出的双缓冲在当前帧做 NPU 推理的同时用 CPU 并行准备下一帧的输入数据和解析上一帧的输出这个重叠能显著提高流水线吞吐。这里还有一个很多人不注意的点电源和散热。RK3588 满载跑三个模型时 NPU 功耗不低如果用劣质电源或者散热片没贴好芯片温度超过 80 度后会自动降频帧率会突然从 16 掉到 10 甚至更低而且这个现象非常难排查因为它不是固定的性能瓶颈而是间歇性的。所以一定要用正规电源最好带锁固的插座别用那种几块钱的劣质充电头。5.3 散热与 PWM 风扇控制既然说到散热就顺便把 RK3588 的风扇控制一起讲了。RK3588 的开发板大部分带一个 PWM 风扇默认由内核 thermal 管理温度高了自动加速。你可以通过 /sys/class/thermal/cooling_device*/cur_state 查看当前风扇档位。如果你想让风扇在特定温度就全速跑可以修改设备树里的 fan 节点或者直接用用户态程序写 /sys/class/hwmon/hwmon0/pwm1。读取风扇转速也很简单如果板子把测速线接出来了通常在 /sys/class/hwmon/hwmon0/fan1_input 里直接读到 RPM 值。有些板子没暴露这个节点可以用 pwm 占空比反推大概转速。这里提醒一点如果你改了 dts 里的 fan 参数导致开机异常别慌RK3588 有 Maskrom 模式按住 recovery/maskrom 键用 USB Type-C 数据线连电脑重新烧录镜像就能救回来不用换板子。6. 常见问题与排查技巧6.1 模型转换与加载报错我最常被问到的是“模型转 rknn 失败”和“rknn_init 返回负数”。转换失败十有八九是模型里有 rknn 不支持的算子。解决办法不是硬怼而是先看 rknn-toolkit2 的日志它会明确指出第一个不支持的算子名称。NMS 是最常见的第二种是某些激活函数或上采样方式比如双线性插值在某些版本上支持不完整。YOLO 系列模型基本都能顺利转换小众的 Transformer 模型则要谨慎评估。rknn_init 返回负数先检查三件事rknn 文件是否在 PC 端和目标平台上用同一个版本的运行时导出板端 librknnrt.so 是否被替换成了不兼容的版本模型文件是否损坏。还有一个很隐蔽的问题如果板端内存不足rknn_init 也可能返回错误可以用 free -m 看看剩余内存。6.2 推理结果异常结果不对首先要区分是“模型部署问题”还是“业务逻辑问题”。如果检测框位置偏得出奇那大概率是 letterbox 的坐标映射没处理好。YOLO 推理是在 640×640 的 letterbox 图上做的但业务显示和报警判断在原图上需要记录缩放比例和 pad 偏移把框坐标换算回原图。我一开始漏掉 pad 偏移导致围栏判定错位排查了半天才发现是坐标映射问题。如果输出全是零或者一个目标都检测不到先检查输入数据的格式。rknn 的输入通道顺序默认是 NHWC也就是 RGBRGB 这样排布如果你的图像数据是 BGR要先 cvtColor 转成 RGB。另外输入预处理必须和训练时保持一致YOLOv5 用的归一化是 0 到 1mean 和 std 一定要配对正确否则模型识别率会降到没法用。垃圾分类如果识别结果总是同一类或者置信度很低十有八九是分类网络的 mean/std 和实际预处理不一致。ImageNet 预训练模型对归一化非常敏感差一点整个特征分布就偏了。建议在 PC 端先用一张固定图片测试原始 ONNX 和转换后 rknn 的输出差异确保一致再拿到业务里联调。6.3 系统级与多路流的坑系统层面还有一个我踩过比较久的坑RK3588 有时候在开机时打印 “cant find suitable delayline” 的报错。这个报错跟 AI 推理本身没关系是 DRM 显示子系统在初始化 HDMI 或 MIPI 屏时找不到合适的时序参数。如果你的盒子是无头模式也就是不接显示器这个报错可以忽略不影响 NPU 和视频处理。但如果你确实需要接屏就得检查内核设备树里的 display timing 配置或者换一个更标准的屏幕分辨率。多路视频流接入时千万不要每个摄像头都单独开一个解码管线RK3588 的 MPP 硬解能力再强也架不住滥用。多个摄像头共享一路 RTSP 流的话可以在应用层做一次解码、多次分发让三个推理线程从同一个最新帧缓冲区取数据。这样不仅省了解码资源还保证了三个模型看到的是同一时刻的画面对人员入侵和烟火检测联动非常有意义。另外我要特别提醒一个看起来很低级但非常常见的问题反复调用 rknn_outputs_get 却忘记 rknn_outputs_release。这种内存泄漏的成长速度很慢可能要连续跑几个小时之后内存才逐渐上涨但它会最终导致整个进程 OOM 崩溃。我在做稳定性测试时抓到过一次进程重启之前内存已经涨到 6GB。所以每次推理循环里拿到输出、解析完、一定要立即 release。我做完整套系统之后最大的感受是RK3588 这块 NPU 的性能比很多人想象中强但真正拉开项目差距的不是芯片而是工程细节。核绑定怎么配、帧缓冲怎么设计、后处理怎么抑制误报、输出内存怎么释放这些细节决定了一台设备到底是“能跑 demo”还是“能上线”。如果你正在调类似的项目可以先从最简单的串行版本跑通三个模型再逐步加多线程和核心绑定千万不要一上来就追求最复杂的方案。等整个链路稳定了你会发现单块 RK3588 同时处理这三个任务其实还留了不少余量给接下来的扩展。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门