1. 项目概述与核心需求解析
1.1 单板跑三任务到底难在哪
先说结论:单块 RK3588 在 6 TOPS 的 NPU 算力下,同时跑人员入侵、烟火检测、垃圾分类三个视觉任务,完全可行,但前提是你得把算力、内存、推理框架调度这三件事安排明白。否则就会出现典型的“一跑全一起卡成 PPT,单独跑哪个都流畅”的窘境。
很多人拿到 RK3588 的第一反应是“这芯片挺强”,毕竟 8 核 CPU、Mali-G610 GPU、6 TOPS NPU 的配置在嵌入式板卡里确实能打。但你真把三个模型塞进去同时跑,问题立刻暴露出来:NPU 的 MAC 阵列就那么多算力,三个模型交替抢占执行,编解码还要分走一部分资源,内存带宽也有上限。这里面的核心矛盾不是“能不能跑”,而是“同时跑的时候每个任务的延迟和帧率还能不能达到业务要求”。
人员入侵讲究的是实时性无延迟,烟火检测要的是准确率不错检漏检,垃圾分类则是个典型的分类任务,对帧率要求不高但对精度敏感。三个任务的性质完全不同,调度策略就不可能一刀切。
1.2 我的软硬件基线和预期目标
我这边用的是 RK3588 标准开发板,8GB 内存版本,系统是 Ubuntu 20.04,NPU 驱动和 runtime 用的是 RKNN-Toolkit2 配套的 1.6.0 版本。摄像头输入走的是 RTSP 拉流,三路视频流分别对应三个场景:周界摄像头给人员入侵检测,仓库摄像头给烟火检测,垃圾投放点摄像头给垃圾分类。
预期目标我定得比较务实:
- 人员入侵:1080P 分辨率下不低于 15 FPS,检测到人后框体延迟不超过 500ms
- 烟火检测:720P 分辨率下不低于 10 FPS,重点是少漏检
- 垃圾分类:640x640 输入下每 2 秒判一帧即可,但 Top-1 准确率不能低于 90%
这三个目标数据不是拍脑袋定的,而是根据场景业务需求倒推出来的。入侵检测 15 FPS 是底线,再低就容易漏掉快速移动的目标;烟火检测可以稍微牺牲帧率换取精度;垃圾分类因为是定点投放场景,人不会快速移动,2 秒一帧完全够用。后面所有方案选型都要服务和满足这些指标。
2. 内容整体设计与思路拆解
2.1 为什么不能三个模型同时并行跑
先说一个新手最容易踩的坑:以为 NPU 有 3 个核心就三个模型各占一个核并行跑。这个想法看似合理,实际根本走不通。
RK3588 的 NPU 确实是三核架构,但它的算力调度远没有“一核一任务”那么简单。实际调用的时候,NPU 驱动会根据当前负载把任务分配到不同的核心上,但你没法在应用层直接指定“这个模型必须跑在 0 号核上”。而且三个模型同时推理时,内存带宽会成为新的瓶颈——每个模型都要频繁读写权重和中间特征图,三路同时访问 DDR,带宽一打满,延迟直接翻倍。
我自己实测过:三个 YOLOv5s 模型同时喂帧,NPU 利用率显示只有 60% 左右,但单帧推理耗时从原来的 35ms 涨到了接近 90ms,整体吞吐反而下降了。这就是典型的内存带宽瓶颈,不是算力不够,是数据搬运不过来。
2.2 更合理的方案:分时复用加错峰调度
真正可行的方案是让三个模型分时复用 NPU,但在调度策略上做错峰:优先级高的任务先跑,低优先级的任务利用高优先级任务的空闲间隙执行。
具体来说,我用的是单线程推理加任务队列的方案。主线程负责从三个视频流分别取帧,取到帧之后按任务优先级投递到推理队列里,推理线程按优先级从队列里拿任务去跑 NPU。人员入侵检测优先级最高,烟火检测次之,垃圾分类最低。
这样做的好处是 NPU 在任何一个时间点都只跑一个模型,不会出现多模型同时抢占内存带宽的问题。坏处是如果高优先级任务长期占满算力,低优先级任务可能会被饿死。所以还需要配合帧率限制——人员入侵检测控制最大 15 FPS,超过这个频率直接丢帧,把 NPU 时间让给后面的任务。
2.3 备选方案对比:为什么不用单模型多任务
另外一个思路是训练一个多任务模型,一个网络同时输出人员检测、烟火检测和垃圾分类结果。这个方案理想状态下算力占用最小,但实现难度非常高。
首先,人员入侵和烟火检测都是检测任务,可以共享骨干网络做多任务输出,但垃圾分类是纯分类任务,输入图像类别差异大(垃圾图像特征与安防场景完全不一样),强行共用特征提取层会导致特征相互干扰——我实际试过把垃圾分类头接在检测模型上,分类准确率掉了 7 个百分点。其次,多任务模型需要同时准备三种标注数据,训练调参的复杂度远超单任务模型。
所以最终我选了三模型分时复用的方案。虽然看起来“笨”,但胜在稳定可控,每个模型可以单独优化迭代,出问题也好定位。对于 RK3588 这种嵌入式平台,工程上的稳定性比理论上的最优要重要得多。
3. 核心细节解析与实操要点
3.1 RK3588 NPU 硬件特性梳理
RK3588 的 NPU 算力标称 6 TOPS,INT8 精度。这个算力在嵌入式平台里属于中上水平,但和桌面级 GPU 完全不是一个量级。跑 YOLOv5s 这种规模的检测模型,单帧推理耗时大概在 20-40ms 之间,具体取决于输入分辨率和模型结构。
NPU 支持的主流算子包括卷积、池化、全连接、激活函数等,像 YOLO 的 Detect 头里的 sigmoid 和 decode 操作,NPU 也支持,但在 RKNN 工具链里通常是放在 CPU 端做的。原因很简单:这些操作在 NPU 上算并不快,反而占用宝贵的 MAC 阵列时间,放到 CPU 端用向量指令处理反而更高效。
关于 RK3588 NPU 的“主网格阵列(Main Grid Array)”和“MAC 阵列工作原理”,简单理解就是:NPU 内部是一个大规模并行计算单元阵列,每个时钟周期可以对多个输入数据做乘加运算。卷积操作的本质就是乘加累加,所以 NPU 特别擅长卷积神经网络。但这个阵列是共享的,多个模型同时运行时,要么让驱动去抢占式调度,要么就在应用层串行调用,避免冲突。
3.2 三个模型的选型与配置策略
三个任务我选型完全不同:
人员入侵检测用 YOLOv8s。选这个的原因很直接:YOLOv8 在人员检测上精度比 v5 高,而且 RKNN 工具链对 v8 的适配已经很成熟了。输入分辨率设成 640x640,INT8 量化后模型体积约 21MB,单帧推理耗时约 32ms。这个模型对 1080P 视频里的人体目标检测效果不错,漏检率低。
烟火检测用 YOLOv5s 改的轻量版。为什么不用 v8?因为我手上有一份基于 v5 训练的烟火检测模型,换了 v8 重训成本高,而且烟火检测场景相对固定,v5 足够。关键是在这个模型上做了通道剪枝,把 640 通道的特征层砍到 384,模型体积从 14MB 降到 8MB,推理耗时从 28ms 降到 18ms。精度损失控制在 1% 以内。
垃圾分类用 ResNet18。垃圾图像分类是个典型图像分类问题,不需要目标检测那样输出位置框,直接用分类网络即可。ResNet18 在垃圾分类数据集上 Top-1 准确率能做到 92% 左右,INT8 量化后模型体积才 4.5MB,推理耗时 8ms,非常轻量。
3.3 RKNN-Toolkit2 模型转换完整流程
模型训练好之后,部署到 RK3588 上必须先转换成 RKNN 格式。这里分享一套我打磨过的完整流程:
首先,把 PyTorch 模型导出为 ONNX 格式。以 YOLOv8s 为例,导出时要注意设置 opset_version=12,太高或太低都可能导致 RKNN 转换报 op 不支持。另外要固定输入尺寸,动态尺寸在 RKNN 里支持不太好。导出代码核心片段:
import torch from ultralytics import YOLO model = YOLO("yolov8s.pt") model.model.eval() dummy_input = torch.randn(1, 3, 640, 640) torch.onnx.export( model.model, dummy_input, "yolov8s.onnx", opset_version=12, input_names=["images"], output_names=["output0"], dynamic_axes=None # 固定尺寸,不用动态轴 )导出 ONNX 之后,用 RKNN-Toolkit2 做转换和量化。这里有一个关键参数需要细说:mean_values和std_values,这两个参数必须和你训练时的预处理完全一致。YOLOv8 训练时用的是 mean=0, std=1 的归一化,也就是不归一化直接除以 255,对应到 RKNN 里就设置 mean_values=[0,0,0],std_values=[255,255,255]。我在这个上面踩过坑,设错会导致检测框全面偏移,模型精度断崖式下跌,我当时重训了一版才知道问题。
转换和量化的完整代码如下:
from rknn.api import RKNN rknn = RKNN() rknn.config( mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rk3588", quantized_dtype="asymmetric_quantized-8", optimization_level=3 ) ret = rknn.load_onnx(model="yolov8s.onnx") if ret != 0: raise RuntimeError("load onnx failed") ret = rknn.build(do_quantization=True, dataset="./dataset.txt") if ret != 0: raise RuntimeError("build failed") ret = rknn.export_rknn("./yolov8s.rknn") if ret != 0: raise RuntimeError("export rknn failed")dataset.txt里放的是量化校准图片的路径列表,每行一个图片路径,一般准备 200-500 张有代表性的图就够了。图片选取要覆盖实际场景中的亮度、角度、目标大小变化,量化精度才有保障。
转换完成部署的时候,有一点必须注意:新版 RKNN-Toolkit2 默认的 rknn_run 接口会带输入和输出的数据拷贝,对于实时视频流来说,这个拷贝很影响延迟。所以在推理循环里用rknn_inputs_set和rknn_outputs_get的零拷贝接口,配合 Python 的np.ctypeslib或者 C 推理接口,能把单帧延迟再压低 5-10ms。这是我调优过程中获得的最大收益之一。
4. 实操过程与核心环节实现
4.1 多线程任务调度框架搭建
前面说了要分时复用 NPU,具体到代码层面就是一个生产者-消费者模型。主线程作为生产者,从三路 RTSP 流分别取帧,按任务优先级放入队列。推理线程作为消费者,从队列里取任务执行 NPU 推理。
我用的框架比较简单,Python 的 threading 模块加 queue 模块就搞定了。核心代码如下:
import threading import queue import cv2 import numpy as np from rknnlite.api import RKNNLite # 定义任务优先级 PRIO_INTRUSION = 0 # 人员入侵,最高优先级 PRIO_FIRE = 1 # 烟火检测,次高优先级 PRIO_GARBAGE = 2 # 垃圾分类,最低优先级 # 任务队列 task_queue = queue.PriorityQueue(maxsize=10) # 加载三个模型 rknn_intrusion = RKNNLite() rknn_intrusion.load_rknn("./yolov8s_intrusion.rknn") rknn_intrusion.init_runtime(core_mask=RKNNLite.NPU_CORE_0) rknn_fire = RKNNLite() rknn_fire.load_rknn("./yolov5s_fire.rknn") rknn_fire.init_runtime(core_mask=RKNNLite.NPU_CORE_1) rknn_garbage = RKNNLite() rknn_garbage.load_rknn("./resnet18_garbage.rknn") rknn_garbage.init_runtime(core_mask=RKNNLite.NPU_CORE_2)这里重点说一下core_mask参数。虽然前面说不能让三模型同时跑,但作为优化手段,可以给每个模型设置不同的核心偏好。RKNNLite 提供了NPU_CORE_0/1/2和NPU_CORE_AUTO几种模式。我的策略是:高优先级任务用NPU_CORE_AUTO,让驱动自动选最空闲的核心;低优先级任务锁定到固定核心,尽量减少对高优先级任务的影响。
实测下来,这个配置比全部用NPU_CORE_AUTO的效果更好。因为驱动自动调度会频繁在核心之间搬移任务,产生额外的同步开销。低优先级任务锁核之后,虽然会有一定的性能损失,但高优先级任务的延迟稳定性明显提升了。
4.2 取帧、推理、后处理全链路优化
取帧部分,我直接用 OpenCV 的VideoCapture拉 RTSP 流。这里有个经验:OpenCV 默认的缓冲区会缓存几帧旧数据,导致画面延迟变大。解决办法是把CAP_PROP_BUFFERSIZE设置成 1:
cap = cv2.VideoCapture(rtsp_url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)如果不设置这个参数,OpenCV 默认缓冲约 4-5 帧,视频延迟直接增加 150-200ms,对于人员入侵检测这种实时性要求高的场景是无法接受的。
推理部分,rknnlite 的接口很简洁,核心就是inference方法。但有一个性能关键点:输入图像的缩放和通道变换,尽量用 OpenCV 的cv2.dnn.blobFromImage或者cv2.resize加cv2.cvtColor的组合,而不是用 numpy 手动操作,后者在 Python 层面会慢很多。
后处理部分,YOLO 类模型的 decode 逻辑放在 CPU 算。每个模型推理完拿到原始输出,再用 python 写 NMS 和后处理。这个过程在 Python 里如果写得不高效,耗时可能比 NPU 推理还长。我优化后的做法是优先用 numpy 向量化操作替代 for 循环,比如用np.argmax替代手动找最大值,用np.where替代条件循环。
推理线程的核心逻辑大概是这样:
def inference_worker(): while True: priority, task = task_queue.get() frame = task["frame"] model = task["model"] callback = task["callback"] if priority == PRIO_INTRUSION: outputs = rknn_intrusion.inference(inputs=[preprocess(frame, 640)]) # 后处理 + NMS boxes = decode_yolo(outputs, conf_thres=0.45) if len(boxes) > 0: callback(boxes, frame) elif priority == PRIO_FIRE: outputs = rknn_fire.inference(inputs=[preprocess(frame, 640)]) boxes = decode_yolo(outputs, conf_thres=0.35) if len(boxes) > 0: callback(boxes, frame) else: outputs = rknn_garbage.inference(inputs=[preprocess(frame, 224)]) cls_id = np.argmax(outputs[0]) callback(cls_id, frame)4.3 三路视频流的帧率分配与优先级调度
整个系统里最微妙的部分是三路视频流的帧率分配。如果每一路都以最大帧率往队列里投帧,哪怕推理线程跑得再快也会跟不上。必须在上游做帧率控制。
我给三路视频流设置的参数是:
- 人员入侵:15 FPS,也就是每 66ms 取一帧
- 烟火检测:10 FPS,每 100ms 取一帧
- 垃圾分类:0.5 FPS,每 2 秒取一帧
实现上,每个视频流对应一个独立线程,线程循环里用time.sleep控制取帧间隔。但注意不能用简单的sleep,因为取帧本身也有耗时,得用基于时间戳的调度:每一轮计算当前时间和下一帧应取时间的差值,如果取早了就 sleep 差值。
有人可能问:三路取帧线程 + 一路推理线程,总共 4 个线程同时跑,CPU 会不会吃紧?实测下来还好。RK3588 是 8 核 CPU,Python 的 GIL 对多线程有一定限制,但这里主要的耗时操作(取帧、推理)都是 I/O 密集型和 C 扩展调用,GIL 影响不大。如果你追求极致性能,可以把三个取帧线程改成进程,或者用 C++ 重写推理部分。
调度还有一个关键细节:任务队列满了怎么办。我的策略是直接丢帧,并且优先丢低优先级的任务。因为对于视频流来说,少处理一帧的代价远小于延迟堆积导致的实时性崩溃。每一路取帧线程在投入队列前检查队列占用率,如果超过 80%,就把当前帧直接丢弃,从而在源头上控制队列堆积。
4.4 显存与内存占用控制
三个模型同时加载进内存,显存占用是个必须算清的账。我实测了三个模型的内存占用情况:
| 模型 | 权重大小 | 推理时内存占用 | 输入分辨率 |
|---|---|---|---|
| YOLOv8s 人员入侵 | 21MB | 约 450MB | 640x640 |
| YOLOv5s 烟火检测(剪枝版) | 8MB | 约 300MB | 640x640 |
| ResNet18 垃圾分类 | 4.5MB | 约 120MB | 224x224 |
三个模型加起来大约占用 870MB 内存,对于 8GB 版的 RK3588 来说压力不大。但如果你的板子是 4GB 版本,就得注意了,加上系统本身占用,用户空间只剩不到 3GB,三个模型全加载后留给视频缓冲和处理的内存就不充裕了。
省内存的几个技巧:
- 用 cv2.CAP_PROP_BUFFERSIZE=1 减少视频流缓冲
- 录像如果不用不要开,别偷懒留一个开着录的进程
- 把系统不用的服务停掉,或者考虑用 docker 精简系统
- 如果用了 Python 做推理,记得用 numpy 的数组池化复用,避免频繁申请释放大数组
5. 模型部署与推理调优实录
5.1 RKNN 模型的量化精度差异对比
RK3588 NPU 原生支持 INT8 计算,但不同模型量化之后的精度损失差异非常大。我实际对比了三个模型在浮点模型和 INT8 量化模型上的性能指标:
| 模型 | 指标 | FP32 | INT8 量化 | 精度损失 |
|---|---|---|---|---|
| YOLOv8s 人员入侵 | mAP@0.5 | 0.852 | 0.831 | 2.1% |
| YOLOv5s 烟火检测 | mAP@0.5 | 0.781 | 0.749 | 3.2% |
| ResNet18 垃圾分类 | Top-1 | 0.936 | 0.924 | 1.2% |
从数据看,烟火检测的量化精度损失最大,因为烟火目标边缘模糊、与背景对比度低,量化时信息损失更严重。ResNet18 是纯分类模型,对量化鲁棒性更好。
如果你发现量化后精度掉得太多,有几种补救方法。第一,在量化校准数据集上下功夫,尽量选择与真实场景分布一致的图片,且数量不要少于 500 张——我一开始只用了 100 张图,火焰检测掉到 0.71 了,校准图片补到 400 张之后精度恢复了一些。第二,考虑混合量化,对精度敏感的层保留 FP16 甚至 FP32,对精度不敏感的层用 INT8。RKNN-Toolkit2 支持指定某些层不量化。第三,在训练阶段做量化感知训练(QAT),在训练时加入伪量化算子让网络适应 INT8 的精度限制,能有效减少量化损失。
5.2 模型剪枝与通道裁剪实例
烟火检测模型我用通道剪枝做瘦身。剪枝的思路是找出对最终输出贡献不大的通道,直接裁掉,然后用原始模型的知识蒸馏回训。用 PyTorch 的 torch.nn.utils.prune 可以快速验证哪些通道可以被剪掉。实际裁剪流程三步走:
第一步,对训练好的 YOLOv5s 做 BN 层 γ 系数统计。训练完成后,BN 层的 γ 系数反映了每个通道的重要性,γ 值接近 0 的通道对网络输出贡献极小,可以直接剪掉。我把所有通道的 γ 排序,设定保留比例 0.6,也就是剪掉 40% 的通道。
第二步,按通道裁剪后重新训练模型。做一个蒸馏式 fine-tune:让剪枝后的模型学习原始模型的输出,而不是直接用真实标签训练,训练收敛速度快很多。大概 30 个 epoch 就能恢复绝大多数精度。
第三步,导出 ONNX 再做 RKNN 量化。
剪枝后的模型大小和推理速度变化非常显著:模型从 14MB 压缩到 8MB,RKNN 版的推理耗时从 28ms 降到 18ms,NPU 占用时间少了三分之一。
这个操作有可复制性,但对不同任务效果不一致——目标检测比分类更适合剪枝,因为检测模型的冗余通道更多。分类模型本身比较紧凑,剪枝空间不大。我试过对 ResNet18 做剪枝,精度掉了 3% 但推理速度只快了 1ms,性价比很低。
5.3 推理耗时拆解与瓶颈定位
如果三个任务同时跑的时候出现整体卡顿,第一步要定位卡顿到底出在哪个环节。我的排查方法是给每段操作打时间戳,把耗时拆開看:
- RTSP 拉流耗时:正常情况下取一帧应在 5-15ms 之内
- 图像预处理耗时:resize 加归一化,640x640 输入约 2-3ms
- NPU 推理耗时:各模型不同,20-35ms 不等
- 后处理耗时:YOLO decode 加 NMS,约 5-10ms
用 Python 的 time.time() 做插桩测量。测量后发现一个让我意外的点:后处理耗时有时候比 NPU 推理还高。原因是在 Python 里做 for 循环逐框判断的 NMS 太慢了。后来把 NMS 改成用 numpy 矩阵操作一次性计算所有框的 IoU,速度提升明显,从 15ms 降到 3ms。
另外,RTSP 拉流的耗时在网络不稳定时波动很大。建议在取帧线程里设置超时机制,比如 2 秒内没取到新帧就主动重连。OpenCV 的 VideoCapture 在网络断开时不会自动重连,这是我实际使用中遇到的最隐蔽的坑。
6. 常见问题与排查技巧实录
6.1 NPU 推理错误排查速查表
我整理了一张 RK3588 NPU 开发中最常见的问题排查表,这些是从实际项目里踩坑踩出来的,每个问题都让我流过血:
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| RKNN load 返回 -1 | 模型转换时的 target_platform 和目标板卡型号不匹配 | 确认 target_platform="rk3588",低版本的 rknn-toolkit2 不支持 rk3588 |
| 推理输出全为 0 | 量化校准数据集和真实输入分布差异过大 | 检查 dataset.txt 里的图片是否覆盖实际场景;检查预处理 mean/std 是否正确 |
| 检测框整体偏移 | mean_values 和 std_values 与训练时不一致 | 逐项核对 preprocess 逻辑,尤其注意 RGB 还是 BGR 通道顺序 |
| 推理偶尔报错 "bus error" | 内存分配不足或模型初始化时没释放旧模型 | 检查是否有多次 init_runtime 未释放;保守时 8GB 内存板一次最多跑 5 个模型 |
| 火焰检测漏检严重 | 后处理 confidence 阈值设太高 | 烟火检测的 conf_thres 要比人员检测低 0.1 左右,建议 0.3-0.35 |
| 视频卡顿、实时性差 | OpenCV 缓冲区过大或模型推理耗时超过帧间隔 | 设置 CAP_PROP_BUFFERSIZE=1;检查推理耗时是否真的在预期范围内 |
| RTSP 断流后无法恢复 | OpenCV 不会自动重连 | 实现超时重连逻辑,超过 2 秒未取到帧就重新创建 VideoCapture |
6.2 模型直接跑烈焰检测效果差,怎么办
有读者朋友在评论区反复问火焰检测精度不高的问题。这部分特别值得展开说一说,因为烟火检测和其他目标检测有个本质区别:火焰没有固定的形状和边界。
烟火检测模型最常见的错误是漏检小火苗和误检红色杂物。我在训练阶段做了几个针对性处理:
第一,数据增强里加入随机颜色扰动。火焰的颜色范围很宽,从淡黄色到深红色都有,而且受光照影响大。通过 HSV 空间的随机变换,让模型学到“颜色+形状+运动”的联合特征,而不是只学一个固定的颜色特征。
第二,用视频帧序列训练而不是单帧。火焰闪烁的动态特性是区分真火焰和红色静物的重要线索。我当时的训练数据里包含了连续多帧,虽然部署时还是用单帧,但在训练阶段模型学到了更稳健的空间特征。
第三,后处理阶段做“时序平滑”。连续若干帧都在同一区域检测到火焰,才判定为真实火情;单帧的检测结果不触发报警。这会增加约 300ms 的确认延迟,但对降低误报率效果非常显著。
6.3 oneAPI 与 Intel NPU 开发对比:为什么选了 RK3588
开发过程中也遇到有人问为什么不走 Intel NPU 的方案。这里说说我的投影对比:Intel 的 NPU 开发工具链目前主要是 oneAPI 体系,它对 x86 平台友好,但嵌入式的功耗和尺寸优势就差多了。而 RK3588 是一颗集成 SoC,CPU、GPU、NPU 都在一个芯片里,配合 Debian 或 Ubuntu 系统就能直接跑,整板功耗一般也就 3-10W,非常适合边缘盒子产品。
如果你是在做 x86 工控机上的视频分析,可以考虑 Intel 方案;但如果你和我一样做的是嵌入式边缘设备、要部署在实际盯防现场,RK3588 这种高集成度 SoC 的综合优势更明显。选择开发平台不用盲目跟风,关键看你的产品形态和部署环境。
6.4 开发中的 3 个微小但致命的坑
最后分享几个很小但很可能让你抓狂的问题。
第一个是 RK3588 风扇转速读取。RK3588 开发板自带 PWM 风扇,系统里可以用cat /sys/class/hwmon/hwmon*/fan*_input这类路径读取转速。但注意不同的内核版本路径不一样,不要写死。如果需要调速,要注意修改设备树里的 pwm-fan 节点配置,单纯在用户态做 PWM 控制很可能和内核驱动冲突。
第二个是延时线报错,如果你在 dmesg 里看到 "can't find suitable delayline" 之类的报错信息,通常是 MIPI 相关的外设或 HDMI PHY 初始化失败。这颗芯片对某些劣质 HDMI 线和显示器兼容性差,可以在设备树里调整对应节点的延时配置,或者先换一根质量好的线测试。别急着觉得是芯片坏了,大概率是外设兼容性问题。
第三个是刷机的时候先确认板子是否进入了 MaskROM / Recovery 模式,用 USB Type-C 数据线连接电脑后上电才能识别到设备。如果按住 Recovery 键上电也无法进入 MaskROM,多半是按键时序或者线材问题。这个环节看着简单,但要是没提前搞清楚板子到底支持哪种模式,折腾半天刷不进去很正常。
7. 性能实测数据与业务效果评估
7.1 满载运行的实际性能数据
我把三路视频流同时跑起来,连续运行 5 个小时,记录了一组完整的性能数据:
| 指标 | 数值 |
|---|---|
| 人员入侵检测帧率 | 14.8 FPS |
| 烟火检测帧率 | 10.2 FPS |
| 垃圾分类检测频率 | 0.5 FPS |
| CPU 总占用率 | 62% |
| NPU 占用率 | 78% |
| 总内存占用 | 2.3GB |
| 核心温度 | 68°C(被动散热) |
这个数据是在 1080P RTSP 输入、三路同时工作的条件下测的。人员入侵帧率勉强达到预期的 15 FPS 目标,烟火检测超出预期。CPU 占用率偏高是因为三路视频的 decode 和后处理都吃 CPU,如果并发再高建议改用硬件解码。
NPU 占用率 78% 意味着还有余量,如果你的场景要加更多路视频流,只要想办法把 decode 环节搬到硬件编解码器(RK3588 自带 MPP 硬编解码模块),再上两路 1080P 问题不大。
7.2 业务效果与误报率统计
跑了一周的真实场景数据,效果评估如下:
- 人员入侵检测:检出率 97.2%,误报率 0.8%,在夜间红外模式下同样保持稳定
- 烟火检测:白天检出率 94.5%,夜间 89.7%,误报率 1.3%,雨天误报有所上升,计划通过引入更多的负样本进行优化
- 垃圾分类:Top-1 准确率 92.1%,常见垃圾类型中塑料瓶、纸箱识别效果好,透明塑料袋会偶尔误判
整体来看,三任务并行运行方案能达到业务要求,且系统有足够余量支持后续功能扩展。高峰期触发报警时 CPU 和 NPU 占用会有一次瞬时飙升,但很快回落,没有出现任务堆积。系统连续运行 96 小时无崩溃,运行稳定性通过了初期的考验。
8. 经验总结与可扩展方向
8.1 我在实战中的最深体会
实测中和各种大小坑交手之后,最大的体会是:嵌入式 AI 开发这个事,模型训练只占 20% 的精力,剩下的 80% 都耗在和硬件驱动、算子适配、内存调度这些工程细节搏斗上。RK3588 这套平台虽然文档丰富、工具链成熟,但真正把三个模型同时跑起来并且保证稳定,消耗最多的精力是在算性能预算、量化精度校准和调度策略迭代上。如果一开始就只盯着模型精度,忽视工程部署,后面很大概率会被各种诡异问题缠住。
8.2 后续性能优化的两个方向
这套三任务并行架构稳定运行后,我计划做两个方向的优化。
第一是引入硬件解码。目前三路视频流的 decode 工作全部由 CPU 软件解码完成,占用大约 20% 的 CPU。RK3588 内置的 MPP 硬解码模块支持 H.264/H.265 硬件解码,用起来之后 CPU 占用可以降到 45% 左右,省下来的资源可以再挂 2 路视频流。实现方式是用 GStreamer 插件 fakesink 输出,通过 appsink 拿到解码后的帧再送入推理队列。
第二是尝试把模型换成 YOLO26 做对比。YOLO26 是 UltraLytics 推出的新版本,理论上在计算效率和精度平衡上比 v8 更好,而且官方支持导出到 RKNN 工具链。待 RKNN-Toolkit2 发布新版本验过算子兼容性之后,我会在人员入侵检测模型上做一轮对比测试,量化一下新模型在同一块 RK3588 上能省多少 NPU 占用。
8.3 把方案移植到其他平台时的适配建议
这套“多模型分时复用”的架构思想不只适用于 RK3588,所有的 NPU 边缘设备都可以借鉴。换成其他平台时,要根据自己的硬件情况调整几个关键参数:算力冗余越大,可以适当提高低优先级任务的帧率;内存越紧,模型越要做极限剪枝。
比如移植到内存 16GB 的 RK3588S 上,可以更激进地开并行,让垃圾分拣任务每 500ms 跑一次;而如果移植到算力更弱的平台,就要相应降低高分辨率检测输入,比如将 YOLOv8s 的输入从 640 降到 480,在精度可接受范围内换取帧率稳定。框架搭好之后,模型和参数就是可配置的,关键是理解每一条参数背后的算力、内存和延迟约束,这样才能真正确保移植后的稳定运行。