yolo_v8 从如坑到入土(三)

60次阅读
没有评论

共计 8011 个字符,预计需要花费 21 分钟才能阅读完成。

适配 Ultralytics YOLOv8(pip install ultralytics)。如需 GPU 加速,请先正确安装对应平台的推理后端(CUDA/TensorRT、OpenVINO Runtime 等)。


0) 通用前置:导出与等效性检查

导出通式

yolo export model=best.pt format=onnx opset=12 dynamic=True half=True  # ONNX(动态尺寸,FP16)yolo export model=best.pt format=engine half=True                      # TensorRT(需本机装 TRT)yolo export model=best.pt format=openvino half=True                    # OpenVINO IR
# 其他:format=torchscript / coreml / tflite / onnxrt 等

等效性检查(强烈建议)

yolo predict model=best.onnx  source=images/val --save  # ORT 后端
yolo predict model=best.engine source=images/val --save  # TensorRT
yolo predict model=best_openvino_model/ source=images/val --save

关注:框 / 分数 / 类别与 PyTorch 版是否一致(容许极小数值差)。如偏差大,多半是 后处理 /NMS实现差异或 动态尺寸 处理不当。


1) ONNX Runtime(通用 CPU/GPU,快速起步)

1.1 导出与简化

yolo export model=best.pt format=onnx opset=12 dynamic=True half=False  # CPU 建议先用 FP32;GPU 可 half=True

dynamic=True 允许可变分辨率(需匹配预处理的 letterbox 逻辑)。

1.2 最小推理脚本(Python + onnxruntime)

import cv2, numpy as np, onnxruntime as ort
from pathlib import Path

# 预处理:letterbox 到 640,保持比例并做 padding
def letterbox(im, new_shape=(640, 640), color=(114,114,114)):
    shape = im.shape[:2]  # (h,w)
    r = min(new_shape[0]/shape[0], new_shape[1]/shape[1])
    new_unpad = (int(round(shape[1]*r)), int(round(shape[0]*r)))
    dw, dh = new_shape[1]-new_unpad[0], new_shape[0]-new_unpad[1]
    dw, dh = dw//2, dh//2
    im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    im = cv2.copyMakeBorder(im, dh, dh, dw, dw, cv2.BORDER_CONSTANT, value=color)
    return im, r, (dw, dh)

# NMS(简版 IOU)def nms(boxes, scores, iou=0.45):
    idxs = scores.argsort()[::-1]
    keep = []
    while idxs.size > 0:
        i = idxs[0]
        keep.append(i)
        if idxs.size == 1: break
        xx1 = np.maximum(boxes[i,0], boxes[idxs[1:],0])
        yy1 = np.maximum(boxes[i,1], boxes[idxs[1:],1])
        xx2 = np.minimum(boxes[i,2], boxes[idxs[1:],2])
        yy2 = np.minimum(boxes[i,3], boxes[idxs[1:],3])
        inter = np.maximum(0, xx2-xx1) * np.maximum(0, yy2-yy1)
        area_i = (boxes[i,2]-boxes[i,0])*(boxes[i,3]-boxes[i,1])
        area_r = (boxes[idxs[1:],2]-boxes[idxs[1:],0])*(boxes[idxs[1:],3]-boxes[idxs[1:],1])
        ovr = inter / (area_i + area_r - inter + 1e-6)
        idxs = idxs[1:][ovr <= iou]
    return keep

# 载入 ONNX
ep = ["CUDAExecutionProvider", "CPUExecutionProvider"]  # 无 GPU 时自动回退 CPU
sess = ort.InferenceSession("best.onnx", providers=ep)
input_name = sess.get_inputs()[0].name

im = cv2.imread("ultralytics/assets/bus.jpg")
img, r, (dw, dh) = letterbox(im, (640,640))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32)/255.0
img = np.transpose(img, (2,0,1))[None]  # NCHW

out = sess.run(None, {input_name: img})[0]  # [1, num, 85] -> xywh, conf, classes
pred = out[0]
boxes = pred[:, :4]
conf  = pred[:, 4]
cls   = pred[:, 5:]
cls_id = cls.argmax(1)
score  = conf * cls.max(1)

# xywh -> xyxy(映射回原图坐标)boxes[:,0] = (boxes[:,0] - boxes[:,2]/2 - dw)/r
boxes[:,1] = (boxes[:,1] - boxes[:,3]/2 - dh)/r
boxes[:,2] = (boxes[:,0] + boxes[:,2])
boxes[:,3] = (boxes[:,1] + boxes[:,3])

mask = score > 0.25
boxes, score, cls_id = boxes[mask], score[mask], cls_id[mask]
keep = nms(boxes, score, 0.45)
for i in keep:
    x1,y1,x2,y2 = boxes[i].astype(int)
    cv2.rectangle(im, (x1,y1), (x2,y2), (0,255,0), 2)
    cv2.putText(im, f"{int(cls_id[i])}:{score[i]:.2f}", (x1, y1-2), cv2.FONT_HERSHEY_SIMPLEX, 0.6,(0,255,0),2)
cv2.imwrite("out_onnx.jpg", im)
print("saved: out_onnx.jpg")

如果导出的 ONNX 已包含 NMS(部分版本 / 配置),输出维度将不同;此时可直接取模型输出,不再手写 NMS。

1.3 小技巧

  • CPU:设置 ORT 线程 intra_op_num_threadsinter_op_num_threads,用 OMP_NUM_THREADS 控制并行;
  • GPU:CUDAExecutionProvider + graph_optimization_level=ORT_ENABLE_ALL
  • 动态尺寸:推理前确保 letterbox 到 步长 32 的倍数(如 640/704)。

2) TensorRT(NVIDIA GPU/Jetson 的首选)

2.1 导出与构建

yolo export model=best.pt format=engine half=True  # 直接生成 .engine(需系统已装 TensorRT)

或用 trtexec 从 ONNX 构建:

trtexec --onnx=best.onnx --saveEngine=best.engine --fp16 \
        --workspace=4096 --avgRuns=50 --shapes=input:1x3x640x640
# 动态 shape:--minShapes=..., --optShapes=..., --maxShapes=...

优化配置 :为动态形状 设置合理的优化档(min/opt/max),并开启 --fp16(支持即用),INT8 需校准(见下节)。

2.2 INT8 量化(PTQ)

  • 准备 200–1000 张 代表性 图片做校准;
  • 使用校准缓存(cache)加速二次构建;
  • 边缘设备(Jetson)常见大幅收益:FP16→INT8 进一步降时延 / 功耗。

2.3 最小 TensorRT Python 推理

import tensorrt as trt, pycuda.driver as cuda, pycuda.autoinit
import numpy as np, cv2
TRT_LOGGER = trt.Logger(trt.Logger.INFO)
with open('best.engine', 'rb') as f, trt.Runtime(TRT_LOGGER) as runtime:
    engine = runtime.deserialize_cuda_engine(f.read())
    context = engine.create_execution_context()

# 绑定 I/O(假设 1x3x640x640)input_idx = engine.get_binding_index('images') if engine.has_implicit_batch is False else 0
output_idx= engine.get_binding_index('output0')
h,w = 640,640

# 预处理
def preprocess(im):
    im = cv2.cvtColor(cv2.resize(im,(w,h)), cv2.COLOR_BGR2RGB)/255.0
    return np.transpose(im, (2,0,1)).astype(np.float32)[None]

im = cv2.imread('ultralytics/assets/bus.jpg')
inp = preprocess(im)

# 设备内存
d_in  = cuda.mem_alloc(inp.nbytes)
# 假设输出 (1, num, 85)
outshape = (1, 8400, 85)
d_out = cuda.mem_alloc(np.prod(outshape)*4)
stream = cuda.Stream()

cuda.memcpy_htod_async(d_in, inp, stream)
context.execute_async_v2([int(d_in), int(d_out)], stream.handle)
out = np.empty(outshape, dtype=np.float32)
cuda.memcpy_dtoh_async(out, d_out, stream)
stream.synchronize()
print(out.shape)

实战用 TensorRT-LLM/NVDS(DeepStream)/Triton Server 可进一步提升吞吐与工程化程度。

2.4 Jetson/ 边缘注意

  • 使用 JetPack 对应版本的 TensorRT;
  • 充分利用 DLA(若可用):trtexec --useDLACore=0 --allowGPUFallback
  • I/O 管线尽量零拷贝(GStreamer/V4L2),避免 Python 频繁拷贝。

3) OpenVINO(Intel CPU/iGPU/NPU 的高效方案)

3.1 导出与运行

yolo export model=best.pt format=openvino half=True

目录结构类似:best_openvino_model/(含 xml/binir)。

3.2 最小推理脚本(Python + openvino.runtime)

from openvino.runtime import Core
import cv2, numpy as np

ie = Core()
model = ie.read_model('best_openvino_model/best.xml')
compiled = ie.compile_model(model, device_name='AUTO')  # CPU/GPU/NPU 自动选择
infer = compiled.create_infer_request()

im = cv2.imread('ultralytics/assets/bus.jpg')
im = cv2.cvtColor(cv2.resize(im,(640,640)), cv2.COLOR_BGR2RGB).astype(np.float32)/255.0
inp = np.transpose(im,(2,0,1))[None]
res = infer.infer({compiled.inputs[0]: inp})
out = list(res.values())[0]  # (1, N, 85)
print(out.shape)

性能开关

  • CPU 多流:CPU_BIND_THREAD=YESCPU_THREADS_NUM= 物理核数
  • 任务并行:compiled = ie.compile_model(model, 'CPU', {"PERFORMANCE_HINT":"THROUGHPUT"})
  • iGPU:确保驱动与 OpenCL 正确安装。

4) 后处理、阈值与可重复性

  • 阈值conf(分类置信度)与 iou(NMS 合并阈值)会影响召回 / 精度;上线前在验证集上网格搜索(见第 2 课 §12)。
  • NMS 差异:不同后端可能用不同 NMS 实现(batched/fast/softnms);务必保同一实现或在评估时用统一后处理。
  • Letterbox 一致性 :训练 / 导出 / 推理三处的 缩放与填充策略 必须一致;
  • 步长与对齐:输入分辨率保持 32 的倍数,避免输出网格错位。

5) INT8/PTQ & 量化感知

5.1 何时用 INT8

  • 边缘设备算力 / 功耗紧张、吞吐不足;
  • 轻微 mAP 损失可接受(常见 <1–2%)。

5.2 校准集建议

  • 来自真实分布、包含光照 / 尺度 / 角度多样性;
  • 200–1000 张即可起效;
  • 针对小目标任务,保证小目标占比与真实一致。

5.3 平台要点

  • TensorRT:Entropy/MinMax 校准,保存 cache;
  • OpenVINOpot(Post-Training Optimization Tool)支持对称 / 非对称量化;
  • TFLite:代表性数据生成器(representative_dataset)。

6) 端侧工程要点(Edge Checklist)

  • I/O 是头号瓶颈 :摄像头→预处理→后处理尽量 零拷贝;批量 / 流水线(async)减少等待;
  • 线程与亲和性:将解码 / 预处理 / 推理分线程,绑定核心避免抖动;
  • Warmup:启动预热 10–50 次,消除 JIT/ 缓存冷启动;
  • 功耗与热设计:Jetson/NUC 等设备关注功耗模式与温控节流;
  • 延迟预算:相机采样(~x ms)+ 预处理(~x)+ 推理(~x)+ NMS/ 绘制(~x);
  • 稳定性:监控 FPS/latency/p95 与丢帧率,异常时降分辨率或切 FP16/INT8。

7) 服务化与打包(最小 FastAPI 服务)

# app.py
from fastapi import FastAPI, UploadFile
import uvicorn, onnxruntime as ort, numpy as np, cv2
app = FastAPI()
sess = ort.InferenceSession('best.onnx', providers=['CPUExecutionProvider'])
name = sess.get_inputs()[0].name

@app.post('/predict')
async def predict(file: UploadFile):
    data = await file.read()
    im = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)
    im = cv2.cvtColor(cv2.resize(im,(640,640)), cv2.COLOR_BGR2RGB)/255.0
    x = np.transpose(im,(2,0,1))[None].astype(np.float32)
    y = sess.run(None,{name:x})[0]
    return {"shape": y.shape}

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)

高吞吐可改用 Triton Inference Server(支持 ORT/TRT/OV)或 DeepStream(视频管线)。


8) 基准与压测

简单延迟基准(单进程)

import time, numpy as np, onnxruntime as ort
sess = ort.InferenceSession('best.onnx', providers=['CUDAExecutionProvider','CPUExecutionProvider'])
x = np.random.rand(1,3,640,640).astype(np.float32)
for _ in range(20): sess.run(None,{sess.get_inputs()[0].name:x})  # warmup
N=200; t=time.time()
for _ in range(N): sess.run(None,{sess.get_inputs()[0].name:x})
print(f"avg {1000*(time.time()-t)/N:.2f} ms")

视频吞吐建议 :多路输入用 异步推理 + 环形缓冲 ;统计 p50/p95/p99 丢帧率,并保存异常帧。


9) 常见错误与排查

  • ONNX opset/ 算子不支持:提高 / 降低 opset,或导出 TorchScript/OpenVINO 代替;
  • NMS 不一致:确认是否模型内置 NMS;若外置,统一实现与阈值;
  • TensorRT shape 错误:未设置动态 shape 优化档;或输入未对齐 32 步长;
  • Jetson 构建失败:TRT/JetPack 版本不匹配;
  • OpenVINO 设备不可用:驱动缺失或未安装对应插件;
  • INT8 精度掉太多:校准集不代表真实;尝试 per-channel/symmetric 与更大校准集;
  • CPU 利用率低:未开启多线程 / 流水线,或被 I/O 阻塞;
  • GPU 利用率低:batch 太小 / 数据搬运频繁;尝试 pinned memory、异步拷贝与更大 batch(离线)。

10) 练习(60–120 分钟)

  1. best.pt 导出为 ONNX/ENGINE/OPENVINO 三种格式,分别在相同 100 张验证图像上评估 mAP、平均延迟与 p95;
  2. TensorRT 上开启 FP16 与 INT8,对比三组延迟与 mAP;
  3. OpenVINO 的 THROUGHOUT 模式跑 4 路并发图片,记录吞吐与 CPU 占用;
  4. 实现一个 异步视频管线(解码→推理→渲染),验证在 30 FPS 输入下的稳定性;
  5. 写一页短报告:阈值(conf/iou)与输入尺寸对延迟与 mAP 的影响曲线,并选定上线参数。

小结

  • ONNX/ORT 快速通吃多平台,TensorRT/NVIDIA 与 OpenVINO/Intel 是 平台最优 路线;
  • FP16 为默认优化,INT8 需代表性校准;
  • 一致的预处理 / 后处理与动态形状配置决定了“离线 = 在线”;
  • 工程化的 I/O 管线与监控比“网络再大一点”更能换到真实 FPS。

正文完
 0
一诺
版权声明:本站原创文章,由 一诺 于2025-09-10发表,共计8011字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码