边缘计算: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 量化模型。
转换流程:
bash
# 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 做前后处理,瓶颈就在数据搬运上。
python
# ❌ 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,绑核可以并行:
python
# 三个核心绑不同任务
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%。
python
img = cv2.resize(img, (416, 416)) # 够用
第五步:帧跳过(稳在 30fps)
不是每一帧都需要推理。15fps 采集,隔帧推理(7-8fps 实际推理),检测结果用 ByteTrack 做追踪插值,视觉上仍然流畅。
采集:帧1 帧2 帧3 帧4 帧5 帧6 ...(15fps)
推理:帧1 帧3 帧5 ...(7.5fps)
追踪:帧2用帧1的结果插值,帧4用帧3的结果插值...
效果:感知上 15fps,实际 NPU 只跑 7.5fps,算力绰绰有余。
三、多摄像头不掉帧的秘诀
售货柜一般装两个摄像头(上下各一,或者左右各一)。两个摄像头同时采流,USB 带宽不够会掉帧。
解决方案:
python
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 推送,设备本地缓存最新价格表 |
| 扣款 | 离线期间生成「待支付订单」,联网后批量代扣 |
python
# 本地 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 做进程守护:
ini
# /etc/systemd/system/vending.service
[Service]
ExecStart=/usr/bin/python3 /app/main.py
Restart=always
RestartSec=10
六、性能对照表
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 推理帧率 | 2fps(CPU) | 30fps(NPU) |
| 单帧推理耗时 | 500ms | 30ms |
| CPU 使用率 | 95% | 35% |
| 内存占用 | 2.1GB | 1.2GB |
| 模型大小 | 12MB(.pt) | 6.2MB(.rknn) |
| 双摄像头 | 掉帧 | 不掉帧 |
下一篇预告
边缘优化讲完了,下一篇讲运维------《无人售货柜运维自动化:100 台设备如何远程管理?》,ADB 远程调试、OTA 升级、故障自动恢复。
💬 互动:你在边缘设备上跑过 AI 吗?帧率卡在多少了?