GPU 服务器上的检测模型 mAP 很漂亮,搬到边缘盒子(Jetson / NPU 工控机)上却可能又慢又掉点:**边缘部署的难点不在模型而在”对齐”**——前后处理对齐、精度对齐、性能与功耗对齐。本文以 YOLO 类检测模型为例,走通 ONNX 导出 → 边缘引擎构建 → 前后处理对齐 → 实测验收的完整链路。

前置准备

边缘目标设备以 Jetson Orin Nano 为例(x86 工控机流程相同,引擎换 OpenVINO/ORT):

pip install onnx onnxruntime ultralytics   # 导出侧
# 设备侧: TensorRT 随 JetPack 自带, 或用 onnxruntime-gpu
  • 导出侧与设备侧各留一套相同验证集图片(200 张即可)做对齐测试
  • 明确输入契约:尺寸(如 640×640)、归一化方式(/255、BGR/RGB)

步骤一:导出 ONNX 并固定契约

动态轴在边缘引擎上支持差,导出时固定 batch 与尺寸:

yolo export model=best.pt format=onnx imgsz=640 batch=1 simplify=True
import onnx
m = onnx.load("best.onnx")
onnx.checker.check_model(m)
print([i.name for i in m.graph.input], [o.name for o in m.graph.output])
  • simplify 会用 onnx-simplifier 折叠常量节点,减少边缘引擎不支持的算子
  • 记录输入输出张量名与形状,写进部署文档——这是前后处理的唯一依据

步骤二:构建边缘推理引擎

Jetson 上用 TensorRT 做 FP16 引擎;x86 CPU 盒子用 ORT:

# Jetson: trtexec 构建引擎并序列化缓存
trtexec --onnx=best.onnx --saveEngine=best_fp16.engine --fp16 --workspace=4096
# x86 CPU 盒子: ORT + 线程绑定
import onnxruntime as ort
so = ort.SessionOptions()
so.intra_op_num_threads = 4
sess = ort.InferenceSession("best.onnx", so, providers=["CPUExecutionProvider"])

引擎文件与设备型号、JetPack 版本绑定——换硬件必须重构建,不能拷贝复用。

步骤三:前后处理对齐

边缘端掉点八成出在这里:letterbox 填充值、归一化顺序、NMS 阈值与训练侧不一致:

import cv2, numpy as np

def letterbox(img, size=640):
r = min(size / img.shape[0], size / img.shape[1])
nw, nh = int(img.shape[1] * r), int(img.shape[0] * r)
pad = (size - nw) // 2, (size - nh) // 2
canvas = np.full((size, size, 3), 114, np.uint8) # 填充值必须与训练一致
canvas[pad[1]:pad[1]+nh, pad[0]:pad[0]+nw] = cv2.resize(img, (nw, nh))
return canvas / 255.0, r, pad # /255 与训练一致
  • 坐标反算:(x - pad) / r 还原到原图——漏除 ratio 是框偏移的经典 bug
  • NMS 用与验证脚本相同的 IoU/conf 阈值;设备端实现不同就统一用 numpy 版

步骤四:精度与性能验收

同一验证集跑”PyTorch 原模型”与”边缘引擎”,双指标验收:

  • 精度对齐:mAP@0.5 差 < 0.5 个百分点;FP16 通常无感,INT8 需逐类检查
  • 性能验收:连续跑 30 分钟记录 FPS、功耗、核心温度——边缘设备降频是隐性杀手
# 设备侧压测: 记录频率与温度曲线
sudo tegrastats --interval 1000 > stats.log &
python bench_edge.py --engine best_fp16.engine --images val/ --minutes 30

验收标准写进发布单:FPS ≥ 目标值、30 分钟无降频掉速、mAP 差在门禁内。

常见问题

  • 框整体偏移:letterbox 的 pad/ratio 反算错误,或导出时内置了预处理又重复做了一遍
  • FP16 某类召回崩了:小目标 score 下溢——该层关 FP16 或改 INT8 校准覆盖小目标样本
  • 首帧极慢:引擎预热不足,上线前空跑 50 帧再接流
  • 多路视频 FPS 不达标:单引擎串行是瓶颈——按路数开多 context 或多进程绑核

小结

边缘部署 = 契约工程:导出时固定输入契约,构建时绑定硬件契约,前后处理复刻训练契约,验收时用同一验证集对齐精度、用长时间压测对齐性能。四个契约任何一个”大概一致”,都会在设备上变成可见的掉点或掉帧——把”对齐”做成 checklist,边缘部署就从玄学变成流水线。