边缘计算网关:本地 AI 推理,断网也能用

边缘计算网关:本地 AI 推理,断网也能用

前几篇的 CV、环控、行为分析,都提到了「在边缘侧跑」。这篇专门讲边缘网关------为什么用 RK3588、怎么部署模型、断网时数据怎么缓存补传、以及边缘-云端如何协同。


为什么需要边缘网关?

三个字:快、稳、省。

对比维度 边缘推理(RK3588) 云端推理(API)
延迟 50-100ms 300-800ms
断网 正常工作 全瘫
带宽 只传结果,0.2KB/次 传原图,200KB/次
成本 一次性 1000 元 按量付费,1万次/月≈100元

农业场景断网是常态。农村 4G 基站覆盖不稳定,刮风下雨掉线是家常便饭。病虫害巡检的轨道车边走边拍------如果每张照片都要传云端等结果,100ms 延迟意味着小车要多走 10cm,错过的病叶就漏掉了。


RK3588 硬件选购与刷机

型号 内存 存储 价格 推荐
Orange Pi 5 Plus 8GB 32GB eMMC 800 元 ⭐ 性价比之选
Firefly ITX-3588J 16GB 64GB eMMC 1500 元 工业级,-20℃~60℃
Radxa Rock 5B 8GB 无 eMMC(插 TF 卡) 700 元 最便宜

推荐 Orange Pi 5 Plus。 8GB 内存足够跑 YOLOv8n + 姿态估计 + 本地 SQLite + MQTT 客户端,余量还很多。

刷机(以 Orange Pi 5 为例):

bash 复制代码
# 1. 下载官方 Ubuntu 22.04 镜像
wget https://github.com/Joshua-Riek/ubuntu-rockchip/releases/download/...

# 2. 用 Balena Etcher 或 rkdeveloptool 烧录到 TF 卡
sudo rkdeveloptool db rk3588_spl_loader_v1.08.111.bin
sudo rkdeveloptool wl 0 ubuntu-22.04-preinstalled.img

# 3. 插卡开机,第一次需要接显示器设置网络
# 4. 启用 NPU
sudo apt install rockchip-mpp rockchip-npu-driver

模型转换与部署:PyTorch → RKNN

RK3588 的 NPU(6 TOPS)必须用 RKNN 格式,不能直接跑 PyTorch。

bash 复制代码
# 1. 安装 RKNN-Toolkit2(在 x86 开发机上)
pip install rknn-toolkit2

# 2. PyTorch → ONNX
python -c "
from ultralytics import YOLO
model = YOLO('yolov8n.pt')
model.export(format='onnx', opset=12)
"

# 3. ONNX → RKNN(量化到 int8 提速)
python -c "
from rknn.api import RKNN

rknn = RKNN()
rknn.config(mean_values=[[0,0,0]], std_values=[[255,255,255]], target_platform='rk3588')
rknn.load_onnx('yolov8n.onnx')
rknn.build(do_quantization=True, dataset='calibration.txt')  # 量化数据集
rknn.export_rknn('yolov8n.rknn')
"

# 4. 把 rknn 文件 scp 到 RK3588 上
scp yolov8n.rknn orangepi@192.168.1.100:~/models/

在 RK3588 上跑推理:

python 复制代码
from rknnlite.api import RKNNLite
import cv2, numpy as np

rknn = RKNNLite()
rknn.load_rknn('models/yolov8n.rknn')
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO)

def detect(frame):
    img = cv2.resize(frame, (640, 640))
    img = np.expand_dims(img, 0)
    
    outputs = rknn.inference(inputs=[img])
    # outputs 是 (1, 84, 8400) 的 numpy 数组
    # 后处理:解码 bbox + NMS
    boxes = decode_outputs(outputs, conf_thres=0.5, iou_thres=0.45)
    
    return boxes

# 接 USB 摄像头实时检测
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    boxes = detect(frame)
    draw_boxes(frame, boxes)
    cv2.imshow('Edge Detection', frame)
    k = cv2.waitKey(1)
    if k == 27: break

YOLOv8n 在 RK3588 NPU 上跑 640×640 推理:约 18-22ms(~50 FPS)。够你在猪舍里实时数猪了。


断网数据缓存与补传------SQLite + 本地队列

网络断了,数据不能丢。方案:

复制代码
传感器数据 → ESP32 → (WiFi) → RK3588 本地接收
                              ├── MQTT 在线 → 直传 EMQX
                              └── MQTT 离线 → 存 SQLite → 网络恢复后补传

实现:

python 复制代码
import sqlite3
import json
import paho.mqtt.client as mqtt

class EdgeDataBuffer:
    def __init__(self, db_path='cache.db'):
        self.conn = sqlite3.connect(db_path)
        self.conn.execute('''
            CREATE TABLE IF NOT EXISTS data_cache (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                topic TEXT,
                payload TEXT,
                qos INTEGER,
                ts INTEGER
            )
        ''')
    
    def push(self, topic, payload, qos=1):
        """存入本地缓存"""
        self.conn.execute(
            'INSERT INTO data_cache (topic, payload, qos, ts) VALUES (?, ?, ?, ?)',
            (topic, json.dumps(payload), qos, int(time.time()))
        )
        self.conn.commit()
    
    def flush(self, mqtt_client):
        """补传所有缓存数据,发一条删一条"""
        rows = self.conn.execute(
            'SELECT id, topic, payload, qos FROM data_cache ORDER BY id'
        ).fetchall()
        
        for row in rows:
            id_, topic, payload, qos = row
            result = mqtt_client.publish(topic, payload, qos=qos)
            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                self.conn.execute('DELETE FROM data_cache WHERE id = ?', (id_,))
        
        self.conn.commit()
        remaining = self.conn.execute('SELECT COUNT(*) FROM data_cache').fetchone()[0]
        return remaining

MQTT 的 on_connect 回调里自动触发补传:

python 复制代码
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print('MQTT 已连接,开始补传缓存数据...')
        remaining = buffer.flush(client)
        print(f'补传完成,剩余 {remaining} 条')

边缘-云端协同策略

不是所有数据都值得发到云端。在边缘侧做一次过滤,只传有价值的:

python 复制代码
def should_upload(data):
    """判断这条数据是否需要上云"""
    # 1. 告警数据:无条件上传
    if data.get('alarm'):
        return True
    
    # 2. 普通传感器数据:时间间隔采样
    if time.time() - last_upload_time[data['dev']] < 60:
        return False  # 1 分钟内不重复传
    
    # 3. 变化超过阈值的才传
    last = last_known_value.get(data['dev'], {})
    if abs(data.get('air_temp', 0) - last.get('air_temp', 0)) < 0.5:
        return False  # 温度没变化超过 0.5℃,免传
    
    # 4. 图像数据:只传检测到异常(有病斑/有异常行为)的帧
    if data['type'] == 'image' and not data.get('anomaly_detected'):
        return False
    
    return True

云边协同的数据流:

复制代码
边缘层(RK3588)                     云端(Spring Boot)
─────────────────                   ─────────────────
采集数据                           
  ↓                                
本地推理(CV/异常检测)             
  ↓                                
should_upload?                      
  ├─ No → 本地 SQLite(7天过期)    
  └─ Yes → MQTT publish ────────→  EMQX → TDengine(永久存储)
                                     ↓
                                   规则引擎 → 告警
                                     ↓
                                   ECharts 大屏

运维要点

  • 监控 NPU 温度:RK3588 满载时能到 70℃,盛夏季节需要主动散热(5V 小风扇即可)
  • TF 卡寿命:频繁写 SQLite 会磨损 TF 卡。缓存数据存 eMMC,日志做 logrotate 7 天清理
  • OTA 升级 :通过 MQTT 下发模型文件 URL → RK3588 下载新 .rknn 文件 → reload → 无缝切换。不要用 SSH 一台台登。
  • 看门狗:设一个守护进程,每 30 秒检测 NPU 推理进程是否存活,死了自动重启

下一篇:《智慧农业创业半年:我踩过的非技术坑》------怎么找客户、怎么报价、怎么落地,以及(适度打码的)真实营收数据。