从零复现:YOLO缺陷检测模型 TensorRT 全量化部署到 Jetson Orin Nano Super(FP32/FP16/INT8 三路对比)

从零复现:YOLO缺陷检测模型 TensorRT 全量化部署到 Jetson Orin Nano Super(FP32/FP16/INT8 三路对比)

文章定位 :零基础可跟做的完整部署教程,手把手讲清楚每一个"坑"和"为什么"。
关键词:TensorRT量化 / Jetson部署 / YOLO / INT8校准 / FP16推理 / MJPEG推流


一、背景与目标

做模型部署最头疼的不是代码,而是"跑不起来的时候不知道为什么"。这篇文章记录了我把一个工业表面缺陷检测YOLO模型,从Windows上训练好的 .pt 文件,一步步部署到 Jetson Orin Nano Super 上,实现 USB 摄像头实时推理 + 浏览器 MJPEG 直播的完整过程。

目标任务:检测电子元件表面的四类缺陷------

类别 含义
ZF-scratch 锌层划伤
scratch 普通划痕
broken 断裂缺陷
pinbreak 引脚折断

最终效果:FP16引擎在 Jetson Orin Nano Super 上实现实时推理,通过浏览器远程查看带标注框的视频流。


二、硬件与软件环境

2.1 我的设备配置

设备 规格
边缘计算板 NVIDIA Jetson Orin Nano Super (67 TOPS)
JetPack 6.2.1
CUDA 12.6
TensorRT 10.3.0
开发主机 Windows 10
USB摄像头 普通 UVC 免驱摄像头

2.2 Windows 开发机需要安装

bash 复制代码
pip install ultralytics      # YOLO模型加载和导出
pip install onnx onnxsim     # ONNX模型处理
pip install onnxruntime      # ONNX验证推理
pip install paramiko scp     # SSH远程操控Jetson

2.3 Jetson 上的 conda 环境

bash 复制代码
# 创建专用环境
conda create -n yolo_trt python=3.10 -y
conda activate yolo_trt

# 安装核心依赖
pip install pycuda

# ⚠️ 关键修复:解决 GLIBCXX 版本冲突(后面会讲为什么)
conda install libstdcxx-ng -c conda-forge -y

三、项目结构

整个项目按流水线方式组织,每个步骤独立一个文件夹,互不干扰,方便调试:

复制代码
Quantification/
├── best.pt             # 原始PyTorch模型(38.7MB,20.06M参数)
├── best.onnx           # 导出的ONNX模型(76.66MB,opset17)
└── steps_v2/
    ├── step01_model_inspect/     # 第一步:解剖模型
    ├── step02_onnx_export/       # 第二步:导出ONNX
    ├── step03_onnx_inspect/      # 第三步:验证ONNX
    ├── step04_jetson_env_check/  # 第四步:检查Jetson环境
    ├── step05_transfer/          # 第五步:传输文件
    ├── step06_trt_fp32/          # 第六步:构建FP32引擎
    ├── step07_trt_fp16/          # 第七步:构建FP16引擎
    ├── step_int8_calib/          # 第八步:INT8校准
    │   └── calib_images/         # 50张校准图片
    ├── step08_accuracy_compare/  # 第九步:三路精度对比
    └── step09_inference_demo/    # 第十步:实时推理演示
        ├── infer_v2.py           # 在Jetson上运行的推理脚本
        └── run.py                # 在Windows上运行的上传脚本

统一规则 :每个 run.py 都在 Windows 上运行,自动 SSH 进 Jetson 完成操作,实时打印日志。


四、模型信息确认(Step 01)

在动手之前,先搞清楚模型的"底细",避免后面出现莫名其妙的维度错误。

python 复制代码
from ultralytics import YOLO
model = YOLO("best.pt")
print(model.task)         # detect(检测任务,非分割/分类)
print(model.names)        # {0:'ZF-scratch', 1:'scratch', 2:'broken', 3:'pinbreak'}
print(model.info())       # 模型参数量

关键结果

复制代码
任务类型  : detect
类别数量  : 4
输入尺寸  : 640 × 640
输出形状  : (1, 8, 8400)
参数量    : 20.06 M
模型大小  : 38.7 MB

输出 (1, 8, 8400) 怎么理解?

复制代码
1    = batch_size
8    = 4 (cx, cy, w, h) + 4 (每个类别的置信度)
8400 = 8400个候选框(特征图上的锚点数)

后处理时,先按置信度过滤,再做NMS(非极大值抑制),才能得到最终检测框。


五、导出 ONNX(Step 02)

python 复制代码
from ultralytics import YOLO
model = YOLO("best.pt")
model.export(
    format="onnx",
    imgsz=640,
    opset=17,       # TensorRT 10.x 兼容,推荐 ≥ 13
    simplify=True,  # 用 onnxsim 精简计算图,加速TRT构建
    dynamic=False,  # 固定batch=1,TRT优化效果更好
)

导出后文件为 best.onnx(76.66MB),同时记录 MD5 哈希值用于后续传输校验。

为什么opset选17?

TensorRT 10 支持 opset 9-20,opset 17 包含了 LayerNormGroupNorm 等新算子,对 YOLO 系列模型覆盖最全。


六、验证 ONNX(Step 03)

传到Jetson之前,先在Windows本地验证ONNX是否正确,避免"传了个坏文件"这种低级失误。

python 复制代码
import onnxruntime as ort
import numpy as np

sess = ort.InferenceSession("best.onnx")
dummy = np.random.randn(1, 3, 640, 640).astype(np.float32)
output = sess.run(None, {"images": dummy})

print(f"输出形状: {output[0].shape}")  # (1, 8, 8400)
print(f"NaN数量: {np.isnan(output[0]).sum()}")   # 应为 0
print(f"Inf数量: {np.isinf(output[0]).sum()}")   # 应为 0

全部通过后再进行下一步。


七、检查 Jetson 环境(Step 04)

通过 SSH 远程检查 Jetson 是否满足部署条件:

python 复制代码
import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("192.168.0.166", username="nvidia", password="nvidia")

# 检查TensorRT版本
_, out, _ = ssh.exec_command("python -c \"import tensorrt; print(tensorrt.__version__)\"")
print(out.read().decode())  # 应输出 10.3.x

检查结果汇总

组件 状态 备注
TensorRT ✓ OK 10.3.0
pycuda ✓ OK 修复GLIBCXX后
torch+CUDA ✓ OK
cv2 ✓ OK
onnxruntime ✗ FAIL 不影响TRT流程,可忽略

同时自动创建部署目录结构:

复制代码
/home/nvidia/yolo_deploy/
├── models/      # 存放引擎文件
├── scripts/     # 存放推理脚本
├── logs/        # 存放日志
└── calib_data/  # 存放校准图片

八、传输文件到 Jetson(Step 05)

使用 SCP 传输 ONNX 文件并验证完整性:

python 复制代码
from scp import SCPClient

def progress(filename, size, sent):
    pct = int(sent / size * 100)
    print(f"\r  上传进度: {pct}%", end="", flush=True)

with SCPClient(ssh.get_transport(), progress=progress) as scp:
    scp.put("best.onnx", "/home/nvidia/yolo_deploy/models/best.onnx")

# 双端MD5校验
local_md5  = hashlib.md5(open("best.onnx","rb").read()).hexdigest()
remote_md5 = run_ssh(ssh, "md5sum /home/nvidia/yolo_deploy/models/best.onnx")[0].split()[0]
assert local_md5 == remote_md5, "文件传输损坏!"
print(f"MD5校验通过: {local_md5} ✓")

九、构建 TRT FP32 基准引擎(Step 06)

踩坑预警1:trtexec 不在 PATH 里!

JetPack 6 上 trtexec 的实际路径是 /usr/src/tensorrt/bin/trtexec,直接输入 trtexec 会报命令找不到。

bash 复制代码
# ❌ 错误写法
trtexec --onnx=best.onnx ...

# ✅ 正确写法(必须用完整路径并设置 LD_LIBRARY_PATH)
export LD_LIBRARY_PATH=/usr/src/tensorrt/lib:/usr/local/cuda-12.6/lib64:$LD_LIBRARY_PATH
/usr/src/tensorrt/bin/trtexec \
    --onnx=/home/nvidia/yolo_deploy/models/best.onnx \
    --saveEngine=/home/nvidia/yolo_deploy/models/best_fp32.engine \
    --memPoolSize=workspace:6G \
    --skipInference

构建耗时约 7 分钟,生成的 best_fp32.engine 约 100MB。


十、构建 TRT FP16 引擎(Step 07)

FP16 量化只需在 FP32 命令基础上加一个参数 --fp16

bash 复制代码
/usr/src/tensorrt/bin/trtexec \
    --onnx=/home/nvidia/yolo_deploy/models/best.onnx \
    --saveEngine=/home/nvidia/yolo_deploy/models/best_fp16.engine \
    --fp16 \          # ← 就这一个参数的区别
    --memPoolSize=workspace:6G \
    --skipInference

TRT 内部自动完成的工作

  1. 权重量化:将 float32 权重转换为 float16,内存减半
  2. Kernel 搜索:为每一层寻找支持 Tensor Core 的最优 CUDA kernel(这是构建慢的原因)
  3. 自动回退:对数值敏感的层(如 Softmax),自动保留 FP32 精度

构建耗时约 16 分钟,生成的 best_fp16.engine 约 50MB(比FP32小了一半)。


十一、INT8 量化校准(Step INT8)

11.1 什么是 INT8 量化

INT8 量化将神经网络的权重和激活值从 32-bit 浮点数压缩为 8-bit 整数。

复制代码
FP32: 每个数值占 4 字节,范围 ±3.4×10³⁸
INT8: 每个数值占 1 字节,范围 -128 ~ 127
压缩比: 4×

关键问题:如何把浮点范围映射到整数范围?这就需要校准(Calibration)

11.2 为什么需要校准图片

校准的本质是:用真实数据统计每一层激活值的分布范围,再确定最优的缩放因子(scale factor),使量化误差最小。

  • 校准图片越多、越有代表性 → INT8精度越高
  • 本项目用了 50 张真实缺陷图,是较低限(生产环境建议 500 张以上)

11.3 核心代码:自定义校准器

python 复制代码
import tensorrt as trt
import pycuda.driver as cuda
import numpy as np

class YOLOCalibrator(trt.IInt8EntropyCalibrator2):
    def __init__(self, calib_images, cache_file):
        super().__init__()
        self.imgs = calib_images
        self.idx  = 0
        self.cache_file = cache_file
        # 在GPU上预分配输入内存
        self.d_input = cuda.mem_alloc(1 * 3 * 640 * 640 * 4)

    def get_batch_size(self):
        return 1

    def get_batch(self, names):
        if self.idx >= len(self.imgs):
            return None  # 校准结束

        # 图像预处理:letterbox缩放 → RGB → /255 → NCHW float32
        img  = cv2.imread(str(self.imgs[self.idx]))
        blob = preprocess(img)  # shape: (1, 3, 640, 640)
        cuda.memcpy_htod(self.d_input, np.ascontiguousarray(blob))
        self.idx += 1
        print(f"校准进度: {self.idx}/{len(self.imgs)}")
        return [int(self.d_input)]

    def read_calibration_cache(self):
        if Path(self.cache_file).exists():
            return Path(self.cache_file).read_bytes()

    def write_calibration_cache(self, cache):
        Path(self.cache_file).write_bytes(cache)
        print(f"校准缓存已保存: {self.cache_file}")

构建INT8引擎:

python 复制代码
builder = trt.Builder(logger)
config  = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 6 * 1024**3)

# 开启INT8模式
config.set_flag(trt.BuilderFlag.INT8)
calibrator = YOLOCalibrator(calib_images, "calib_cache.bin")
config.int8_calibrator = calibrator

# 构建引擎(约16分钟)
engine = builder.build_serialized_network(network, config)

11.4 INT8 的局限性

由于本项目校准图片只有50张,INT8引擎在实际测试中检测框很少甚至没有,原因分析:

  1. 校准数据不足:50张远低于TRT推荐的500+张
  2. 数据分布偏差:校准图和实际推理图的缺陷分布可能不完全一致
  3. 量化误差累积:4.07% 的相对误差对置信度较低的小目标影响更大

结论:本项目最终选用 FP16 引擎进行部署,精度损失仅 0.202%,速度提升约 2×。


十二、三路精度对比(Step 08)

用相同的随机输入让三个引擎分别推理,以 FP32 输出为基准计算误差:

python 复制代码
# 三个引擎分别推理同一个 (1,3,640,640) 随机张量
fp32_out = run_engine(fp32_engine, test_input)
fp16_out = run_engine(fp16_engine, test_input)
int8_out = run_engine(int8_engine, test_input)

# 计算误差
def report_error(ref, pred, name):
    diff = np.abs(ref - pred)
    print(f"{name}:")
    print(f"  最大绝对误差: {diff.max():.4f}")
    print(f"  平均绝对误差: {diff.mean():.4f}")
    print(f"  平均相对误差: {diff.mean() / (np.abs(ref).mean() + 1e-6) * 100:.3f}%")

对比结果

量化方式 最大绝对误差 平均绝对误差 平均相对误差 推荐场景
FP32(基准) 0 0 0% 精度要求极高
FP16 1.086 0.048 0.202% 绝大多数场景首选
INT8 74.08 2.45 4.07% 需要最高速度 + 足量校准数据

十三、实时推理部署(Step 09)

13.1 部署架构

整体数据流:

复制代码
USB摄像头
    │  cv2.VideoCapture(0)
    ▼
图像预处理
    │  letterbox(640×640) → BGR转RGB → /255 → NCHW float32
    ▼
TRT FP16推理
    │  CUDA memcpy H→D → execute_async_v3 → CUDA memcpy D→H
    ▼
后处理
    │  置信度过滤(0.25) → NMS(IoU=0.45) → 坐标反缩放
    ▼
MJPEG HTTP服务器(:8080)
    │  multipart/x-mixed-replace
    ▼
Windows浏览器  http://192.168.0.166:8080/

13.2 TRT 10.x 推理代码

注意:TensorRT 10.x 的推理 API 与 8.x 有重大变化,网上大量教程用的是旧版本写法。

python 复制代码
# ❌ TRT 8.x 旧写法(不适用于 TRT 10.x)
context.execute_async_v2(bindings=[int(d_input), int(d_output)], stream_handle=stream.handle)

# ✅ TRT 10.x 正确写法
# 第一步:获取所有张量名称
names = [engine.get_tensor_name(i) for i in range(engine.num_io_tensors)]

# 第二步:按名称绑定内存地址
bufs = {n: cuda.mem_alloc(int(np.prod(engine.get_tensor_shape(n))) * 4) for n in names}
for n, b in bufs.items():
    ctx.set_tensor_address(n, int(b))

# 第三步:异步执行(v3 版本)
cuda.memcpy_htod_async(bufs["images"], input_np, stream)
ctx.execute_async_v3(stream.handle)
cuda.memcpy_dtoh_async(output_np, bufs["output0"], stream)
stream.synchronize()

13.3 MJPEG 推流服务器(纯 Python,不需要 Flask)

python 复制代码
from http.server import BaseHTTPRequestHandler, HTTPServer
import threading

_stream_frame = None  # 全局共享帧(线程间通信)
_stream_lock  = threading.Lock()

class MJPEGHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            # 返回包含图像刷新的HTML页面
            html = b"""<html><body style='background:#000'>
                <img src='/stream' style='width:100%;max-width:800px'>
                </body></html>"""
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(html)

        elif self.path == "/stream":
            # 返回MJPEG流
            self.send_response(200)
            self.send_header("Content-Type", "multipart/x-mixed-replace; boundary=frame")
            self.end_headers()
            while True:
                with _stream_lock:
                    frame = _stream_frame
                if frame is not None:
                    _, jpg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 85])
                    self.wfile.write(b"--frame\r\n")
                    self.wfile.write(b"Content-Type: image/jpeg\r\n\r\n")
                    self.wfile.write(jpg.tobytes())
                    self.wfile.write(b"\r\n")
                time.sleep(0.04)  # 控制推流帧率 ≈ 25fps

13.4 启动实时推理

第一步:在 Windows 上运行上传脚本:

bash 复制代码
cd steps_v2/step09_inference_demo
python run.py

第二步:SSH 到 Jetson 启动推理:

bash 复制代码
ssh nvidia@192.168.0.166

source ~/miniforge3/etc/profile.d/conda.sh
conda activate yolo_trt
export LD_LIBRARY_PATH=~/miniforge3/envs/yolo_trt/lib:$LD_LIBRARY_PATH
cd /home/nvidia/yolo_deploy
python scripts/infer_v2.py --source 0 --stream --conf 0.25

第三步:Windows 浏览器打开:

复制代码
http://192.168.0.166:8080/

就能看到实时带检测框的画面了!


十四、踩坑全记录

坑1:paramiko 没有安装

现象ModuleNotFoundError: No module named 'paramiko'

原因:用的是系统 Python 而不是 conda 环境里的 Python

bash 复制代码
# 错误:直接双击或用系统Python运行
C:\Users\xxx\Python38\python.exe run.py

# 正确:先激活conda环境
conda activate your_env
python run.py

坑2:trtexec 找不到

现象bash: line 1: trtexec: command not found

原因:JetPack 6 上 trtexec 没有加入 PATH

bash 复制代码
# 查找trtexec实际位置
find /usr -name trtexec 2>/dev/null
# 输出:/usr/src/tensorrt/bin/trtexec

# 使用时必须:
# 1. 用完整路径
# 2. 设置对应的 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/src/tensorrt/lib:/usr/local/cuda-12.6/lib64:$LD_LIBRARY_PATH
/usr/src/tensorrt/bin/trtexec --onnx=... --saveEngine=...

坑3:GLIBCXX 版本缺失

现象

复制代码
ImportError: /lib/aarch64-linux-gnu/libstdc++.so.6:
    version 'GLIBCXX_3.4.32' not found (required by pycuda)

原因 :pycuda 编译时链接的 libstdc++ 版本高于系统自带版本

解决方案

bash 复制代码
# 安装高版本 libstdc++(在yolo_trt环境内)
conda activate yolo_trt
conda install libstdcxx-ng -c conda-forge -y

# 关键:每次运行前必须把conda的lib路径放在最前面
export LD_LIBRARY_PATH=~/miniforge3/envs/yolo_trt/lib:$LD_LIBRARY_PATH

⚠️ 注意 :这个 export 必须在每个运行 pycuda 的命令之前执行,否则还会报同样的错误。

坑4:INT8 引擎无检测框

现象:FP32/FP16 都有检测结果,INT8 几乎为零

原因分析

  1. 校准图片太少(50张 vs 建议500张+)
  2. 校准图和测试图数据分布有差异
  3. INT8 量化误差(4.07%)对小目标检测影响较大

解决方案:改用 FP16(精度损失 0.202%,速度约提升 2×,完全可接受)

坑5:MJPEG 流显示黑屏

原因:摄像头设备号不对

bash 复制代码
# 先查看Jetson上的视频设备
ls /dev/video*
# 可能是 /dev/video0, /dev/video1 等

# 指定对应设备号
python infer_v2.py --source 1 --stream  # 改成实际的设备号

十五、量化效果总结

指标 FP32 FP16 INT8
引擎大小 ~100 MB ~50 MB ~30 MB
构建时间 ~7 min ~16 min ~16 min
典型延迟 ~15-20 ms ~8-12 ms ~5-8 ms
理论 FPS ~50-65 ~80-120 ~125-200
精度损失 基准 ~0.2% ~4%(依赖校准质量)
是否需要校准数据 是(建议500+张)

选型建议

  • 日常生产部署 → 选 FP16:精度几乎无损,速度翻倍,无需校准数据
  • 追求极限性能 → 选 INT8:前提是有足量高质量的校准图片
  • 精度基准验证 → 选 FP32:作为参考标准

十六、完整运行流程

按顺序执行以下命令(全部在 Windows 上运行):

bash 复制代码
# 第一步:解剖模型
cd steps_v2/step01_model_inspect && python run.py

# 第二步:导出ONNX
cd ../step02_onnx_export && python run.py

# 第三步:验证ONNX
cd ../step03_onnx_inspect && python run.py

# 第四步:检查Jetson环境
cd ../step04_jetson_env_check && python run.py

# 第五步:传输文件
cd ../step05_transfer && python run.py

# 第六步:构建FP32引擎(约7分钟)
cd ../step06_trt_fp32 && python run.py

# 第七步:构建FP16引擎(约16分钟)
cd ../step07_trt_fp16 && python run.py

# 第八步(可选):INT8校准(约16分钟,需校准图片)
cd ../step_int8_calib && python run.py

# 第九步:精度对比
cd ../step08_accuracy_compare && python run.py

# 第十步:上传推理脚本
cd ../step09_inference_demo && python run.py

然后 SSH 到 Jetson 启动实时推理,浏览器打开 http://192.168.0.166:8080/ 查看效果。


十七、参考资料


如果这篇文章对你有帮助,欢迎点赞收藏~有问题可以在评论区留言,我会尽快回复。

完整代码见 GitHub(马上开源)。

相关推荐
guo_xiao_xiao_2 小时前
YOLOv11城市道路自行车目标检测数据集-1022张-Bicycle-1_2
yolo·目标检测·目标跟踪
guo_xiao_xiao_3 小时前
YOLOv11海上多场景船只目标检测数据集-980张-Boat-1
人工智能·yolo·目标检测
MU在掘金916953 小时前
TokenTracker:给AI调用装个计费器
性能优化
小学生-山海3 小时前
【YOLO系列】基于YOLOv8/v11/v26与tkinter的车流量统计系统设计
python·yolo
weixin_398187754 小时前
YOLOv11改进:全维度动态卷积ODConv与C3k2模块创新
人工智能·yolo
这张生成的图像能检测吗4 小时前
(论文速读)SPR-YOLO:面向模糊场景的轻量级交通流检测算法
人工智能·yolo·计算机视觉·目标追踪
wangl_925 小时前
C#性能优化完全指南 - 从原理到实践
开发语言·性能优化·c#·.net·.netcore·visual studio
晚风_END8 小时前
Linux|操作系统|zfs文件系统的使用详解
linux·运维·服务器·数据库·postgresql·性能优化·宽度优先
guo_xiao_xiao_15 小时前
YOLOv11高空俯视场景猫狗人目标检测数据集-1488张-bag-1_4
人工智能·yolo·目标检测