边缘计算:RK3588 上跑 AI 模型的性能优化指南

0 阅读5分钟

边缘计算:RK3588 上跑 AI 模型的性能优化指南

YOLOv8 在 PC 上跑得好好的,一放到 RK3588 上就只有 2fps,根本没法用。怎么用 NPU 加速到 30fps?多路摄像头怎么不掉帧?离线模式怎么做?这篇文章把边缘端性能优化的全部技巧交给你。


大家好,我是黒漂技术佬。

上一篇讲了小程序端。今天回到边缘端,聊聊怎么把 AI 模型在 RK3588 上跑出实时效果。如果你正在用边缘设备跑推理,这篇文章的优化技巧应该能帮你省不少时间。


一、为什么需要边缘计算?

售货柜放在便利店门口或者工厂车间里,网络不稳定是常态。如果每帧图像都传回云端做识别:

  • 一帧 200KB,15fps,一秒 3MB
  • 4G 上行带宽 5Mbps,传一帧 300ms
  • 用户等 3 秒才能知道扣了多少钱

边缘算 = 快 + 省流量 + 能离线跑。


二、从 2fps 到 30fps 的五步优化

第一步:模型量化(2fps → 10fps)

原始 YOLOv8n 是 FP32 精度,在 CPU 上跑一帧 500ms(2fps)。RK3588 有 6TOPS 的 NPU,但只支持 INT8 量化模型。

转换流程

# 1. PyTorch → ONNX
yolo export model=yolov8n.pt format=onnx opset=12 imgsz=640

# 2. ONNX → RKNN(量化)
rknn-toolkit2/rknn_convert.py --input yolov8n.onnx --output yolov8n.rknn --quantize

量化后推理一帧从 500ms 降到 100ms(10fps),提升 5 倍。但还不够。

第二步:NPU 推理替代 CPU(10fps → 25fps)

量化后模型虽然能跑在 NPU 上,但如果代码还在用 CPU 做前后处理,瓶颈就在数据搬运上。

# ❌ CPU 推理(慢)
outputs = model(img)  # 500ms

# ✅ NPU 推理(快)
rknn = RKNNLite()
rknn.load_rknn('yolov8n.rknn')
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
outputs = rknn.inference(inputs=[img])  # 30ms

关键:让 NPU 单独跑推理,CPU 只负责前后处理。一帧总耗时:

  • 预处理:10ms(CPU resize)
  • NPU 推理:30ms
  • 后处理:5ms(CPU NMS)
  • 合计:45ms → 22fps

第三步:多核 NPU(25fps → 30fps)

RK3588 有三个 NPU 核心。默认只用核心 0,绑核可以并行:

# 三个核心绑不同任务
rknn_core0.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
rknn_core1.init_runtime(core_mask=RKNNLite.NPU_CORE_1)
rknn_core2.init_runtime(core_mask=RKNNLite.NPU_CORE_2)

# 两个摄像头各用一个核心做推理
thread_pool.submit(camera_0_infer, rknn_core0)
thread_pool.submit(camera_1_infer, rknn_core1)

双摄像头各绑一个 NPU 核心,第三个核心留给行为分析和日志。

第四步:降低输入分辨率(30fps → 35fps)

640×640 对售货柜场景来说精度过剩。降到 416×416,NPU 推理从 30ms 降到 18ms,准确率只降了不到 1%。

img = cv2.resize(img, (416, 416))  # 够用

第五步:帧跳过(稳在 30fps)

不是每一帧都需要推理。15fps 采集,隔帧推理(7-8fps 实际推理),检测结果用 ByteTrack 做追踪插值,视觉上仍然流畅。

采集:帧123456 ...(15fps)
推理:帧135      ...(7.5fps)
追踪:帧2用帧1的结果插值,帧4用帧3的结果插值...

效果:感知上 15fps,实际 NPU 只跑 7.5fps,算力绰绰有余。


三、多摄像头不掉帧的秘诀

售货柜一般装两个摄像头(上下各一,或者左右各一)。两个摄像头同时采流,USB 带宽不够会掉帧。

解决方案

import threading
import queue

# 每个摄像头独立线程采集,帧放进队列
def capture_thread(camera_id, frame_queue):
    cap = cv2.VideoCapture(camera_id)
    cap.set(cv2.CAP_PROP_FPS, 15)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
    while True:
        ret, frame = cap.read()
        if frame_queue.full():
            frame_queue.get()  # 丢旧帧,保证实时性
        frame_queue.put(frame)

cam0_queue = queue.Queue(maxsize=3)
cam1_queue = queue.Queue(maxsize=3)
threading.Thread(target=capture_thread, args=(0, cam0_queue)).start()
threading.Thread(target=capture_thread, args=(1, cam1_queue)).start()
  • 每个摄像头独立线程
  • 队列满时丢旧帧,保证时延不累积
  • 分辨率降到 640×480(H.264 压缩后一帧 < 50KB)

四、离线模式:断网也能用

设备断网时,开门扣款都得继续工作:

场景方案
开门控制本地缓存用户白名单(定时从云端同步),断网时用本地缓存校验
订单存储本地 SQLite 存订单,联网后批量上传
价格更新云端变更后 MQTT 推送,设备本地缓存最新价格表
扣款离线期间生成「待支付订单」,联网后批量代扣
# 本地 SQLite 存离线订单
import sqlite3
conn = sqlite3.connect('/data/offline_orders.db')
conn.execute(
    'INSERT INTO orders (order_no, product_code, amount, created_at, synced) '
    'VALUES (?, ?, ?, ?, 0)',
    (order_no, product_code, amount, time.time())
)

# 联网后批量上传
def sync_orders():
    orders = conn.execute('SELECT * FROM orders WHERE synced = 0').fetchall()
    for order in orders:
        response = requests.post(CLOUD_API + '/orders/batch', json=order)
        if response.status_code == 200:
            conn.execute('UPDATE orders SET synced = 1 WHERE id = ?', (order['id'],))

五、监控与自愈

边缘设备没人值守,出了问题必须自动恢复:

监控项阈值动作
CPU 温度> 80°C降频
内存使用> 85%重启 Python 进程
磁盘空间< 200MB清理旧日志和图片
摄像头掉线连续 3 帧黑屏重启摄像头驱动
4G 断网心跳超时 90 秒重启 4G 模块

systemd 做进程守护:

# /etc/systemd/system/vending.service
[Service]
ExecStart=/usr/bin/python3 /app/main.py
Restart=always
RestartSec=10

六、性能对照表

指标优化前优化后
推理帧率2fps(CPU)30fps(NPU)
单帧推理耗时500ms30ms
CPU 使用率95%35%
内存占用2.1GB1.2GB
模型大小12MB(.pt)6.2MB(.rknn)
双摄像头掉帧不掉帧

下一篇预告

边缘优化讲完了,下一篇讲运维——《无人售货柜运维自动化:100 台设备如何远程管理?》,ADB 远程调试、OTA 升级、故障自动恢复。


💬 互动:你在边缘设备上跑过 AI 吗?帧率卡在多少了?