1. 项目概述:从YOLOv8到ONNX推理的工程化之路
最近在部署一个边缘侧的目标检测项目,模型选型上直接敲定了YOLOv8。这没什么好犹豫的,Ultralytics这家公司把YOLO系列做得越来越像“开箱即用”的工业品,从训练到验证的pipeline顺畅得让人感动。但问题来了,我们的应用场景五花八门:有的在x86服务器上用Python做视频流分析,有的要集成到C++的桌面应用里,还有的最终目标是跑到ARM架构的嵌入式设备上。如果每个环境都去折腾一遍PyTorch的依赖和动态图,光是环境对齐和性能调优就能把人逼疯。
这时候,ONNX(Open Neural Network Exchange)就成了我们的“救星”。它本质上是一个开放的模型格式标准,让你可以把PyTorch、TensorFlow等框架训练好的模型,转换成一种中间表示。有了这个.onnx文件,你就可以用统一的ONNX Runtime推理引擎,在各种硬件和操作系统上跑起来,彻底告别“一个模型,N套环境”的部署噩梦。这次要聊的,就是如何把YOLOv8模型转换成ONNX格式,并完成跨平台的推理部署。这不仅仅是跑通一个demo,更是一套从训练到落地的完整工程实践,里面有不少从实际项目中踩坑总结出来的门道。
2. 核心思路与工具链选型
2.1 为什么选择ONNX?—— 统一部署的必然选择
在模型部署的战场上,框架和硬件平台就像一个个孤岛。PyTorch模型在Python生态里如鱼得水,但想把它塞进一个没有Python环境的C++程序,或者部署到内存和算力都紧张的边缘设备,就变得异常棘手。ONNX的出现,就是为了在这些孤岛之间架起桥梁。
它的核心价值在于标准化和硬件支持广泛。一旦模型被转换为ONNX格式,你就可以使用ONNX Runtime这个高性能推理引擎。ORT针对CPU、GPU(CUDA、TensorRT)、ARM NPU等都有专门的执行提供者(Execution Provider),能最大程度发挥硬件算力。对于我们这个YOLOv8项目而言,这意味着:
- 研发效率:算法工程师用PyTorch训练和调试模型,交付时给一个.onnx文件和一纸接口说明即可,下游的工程团队无需深入AI框架细节。
- 部署灵活性:同一个.onnx文件,可以在Windows/Linux服务器、Jetson系列边缘设备、甚至手机端(通过ONNX Runtime Mobile)进行推理,极大降低了多平台适配成本。
- 性能优化:ONNX Runtime本身经过了大量优化,并且可以进一步接入像TensorRT、OpenVINO这样的后端进行图优化和量化,从而获得极致的推理速度。
2.2 YOLOv8模型导出ONNX的深度解析
YOLOv8的官方仓库(ultralytics)提供了非常便捷的导出功能,一行命令model.export(format='onnx')就能搞定。但如果你只看到这一步,那可能只理解了表面的10%。导出过程背后有几个关键点,直接影响到后续推理的效率和正确性。
2.2.1 动态轴与静态形状的权衡
在导出时,你会遇到一个核心选项:dynamic参数。YOLOv8默认的导出是动态批处理(Dynamic Batch Size)和动态尺寸(Dynamic Image Size)。这意味着模型输入的形状是[batch_size, 3, height, width],其中batch_size、height、width都是符号维度,可以在推理时指定。
from ultralytics import YOLO model = YOLO('yolov8n.pt') # 导出为动态尺寸的ONNX model.export(format='onnx', dynamic=True)动态导出的优势:灵活。同一个模型可以处理任意尺寸的输入图片,非常适合需要处理不同分辨率视频流或图片的场景。
动态导出的挑战:某些后端推理引擎(特别是针对特定硬件深度优化的工具链,如一些NPU的编译器)对动态形状的支持不友好,可能要求固定输入尺寸以获得最佳性能,甚至完全不支持动态轴。
因此,如果你的部署目标明确,输入图片尺寸固定(例如,监控摄像头总是640x480),那么我强烈建议使用静态导出:
# 导出为固定尺寸 640x640 的ONNX model.export(format='onnx', imgsz=640)静态模型在推理时,ONNX Runtime或TensorRT等引擎能进行更激进的内核融合和内存优化,推理速度通常会有5%-20%的提升。
2.2.2 算子兼容性与OP版本
这是导出过程中最大的“暗坑”。YOLOv8模型中使用了一些较新的PyTorch算子,在转换为ONNX时,必须确保ONNX opset版本能够支持。Ultralytics默认使用opset=17。大部分情况下这是没问题的,但如果你需要将ONNX模型进一步转换到其他格式(如TensorRT、NCNN、MNN),就必须关注目标推理引擎支持的ONNX算子集。
一个经典的例子是GridSample算子。在较早的opset版本中,它的坐标映射模式可能与某些推理引擎的实现有细微差别,导致转换失败或结果异常。我的经验是:
- 如果最终后端是ONNX Runtime,保持opset=17或更新版本即可。
- 如果目标是TensorRT,建议查阅其官方文档支持的ONNX opset版本(例如TensorRT 8.x对应ONNX opset 13-17),并在此范围内导出。
- 如果目标是移动端或边缘设备专用推理框架(如NCNN、TNN),可能需要尝试opset=11或12,以获取最好的兼容性。
实操心得:在项目初期,就建立一个简单的“导出-验证”流水线。导出ONNX后,不要急于部署,先用ONNX Runtime在CPU上跑一遍推理,用PyTorch原始模型在相同输入上跑一遍,对比两者的输出(如检测框坐标、类别置信度)。确保数值差异在可接受的误差范围内(例如,使用余弦相似度或L2误差)。这能提前发现因算子转换带来的精度损失问题。
3. ONNX模型推理的完整实现
拿到.onnx文件只是第一步,如何高效、正确地加载并运行它,才是工程上的重头戏。这里我们分别以Python和C++两种最常用的语言为例,拆解其中的关键步骤。
3.1 Python环境下的ONNX Runtime推理
Python接口简单直观,适合快速原型验证和服务端部署。首先安装必要的包:pip install onnxruntime或者针对GPU的pip install onnxruntime-gpu。
3.1.1 会话创建与提供者选择
创建推理会话(InferenceSession)是第一步,这里的选择直接影响性能。
import onnxruntime as ort import numpy as np # 方式1:使用默认CPU执行提供者 session_cpu = ort.InferenceSession('yolov8n.onnx') # 方式2:指定使用CUDA执行提供者(如果可用) providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] session_gpu = ort.InferenceSession('yolov8n.onnx', providers=providers) # 方式3:使用TensorRT执行提供者(需要单独安装onnxruntime-gpu-tensorrt) # providers = ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']注意:
providers列表的顺序代表优先级。将CUDAExecutionProvider放在前面,ORT会优先尝试将计算图加载到GPU上。如果CUDA不可用,则会自动回退到CPU。
3.1.2 预处理与后处理适配
YOLOv8的ONNX模型输入输出是固定的,但我们需要将原始的图像数据适配进去。
预处理:模型期望的输入是[1, 3, H, W],数值范围[0, 1],格式为BGR(这是YOLOv8训练时默认的预处理顺序)。常见的OpenCV读取的图像是HWC格式且为BGR。预处理步骤通常包括:调整大小到模型输入尺寸(如640x640)、归一化(除以255)、转换颜色通道顺序(如果必要)、以及从HWC转为CHW。
import cv2 def preprocess(image_path, input_size=640): img = cv2.imread(image_path) # 保持宽高比进行resize,并在边缘填充灰色 h, w = img.shape[:2] scale = min(input_size / h, input_size / w) new_h, new_w = int(h * scale), int(w * scale) img_resized = cv2.resize(img, (new_w, new_h)) # 创建画布并填充 canvas = np.full((input_size, input_size, 3), 114, dtype=np.uint8) canvas[:new_h, :new_w, :] = img_resized # 转换:HWC -> CHW, BGR -> RGB? 不,YOLOv8默认是BGR输入 # 根据官方代码,其预处理是 `im /= 255` 且保持BGR顺序 image_data = canvas.transpose(2, 0, 1) # 转为 CHW image_data = image_data.astype(np.float32) / 255.0 image_data = np.expand_dims(image_data, axis=0) # 增加batch维度 -> [1,3,640,640] return image_data, (h, w), scale, (new_h, new_w)后处理:YOLOv8的ONNX模型输出与PyTorch版本略有不同。对于检测模型,它通常输出一个形状为[1, 84, 8400]的张量(以YOLOv8n为例)。这里的8400是锚框数量(基于不同尺度的特征图),84是每个锚框的预测数据:前4个是边界框坐标(cx, cy, w, h),第5个是目标置信度,后79个是类别概率(COCO数据集有80类,但索引从0开始,所以是79个类别分数,加上背景?这里需要澄清:YOLOv8通常输出4+1+num_classes,对于COCO,4+1+80=85,但8400这个数字对应的是输出维度85?这里需要根据实际导出模型确认。实际上,YOLOv8的无锚框设计输出是[batch, 4+1+num_classes, num_anchors],我们需要核对模型输出形状)。
一个更可靠的后处理方法是,直接加载模型后打印其输出信息:
session = ort.InferenceSession('yolov8n.onnx') output_name = session.get_outputs()[0].name output_shape = session.get_outputs()[0].shape print(f"Output name: {output_name}, shape: {output_shape}")假设输出形状是[1, 84, 8400],那么后处理流程如下:
- 解析输出:将
[1, 84, 8400]重塑为[8400, 84]。每一行代表一个预测。 - 提取数据:每行的前4列是
[cx, cy, w, h](相对于特征图网格的坐标),需要根据输入图像尺寸和特征图步长(stride)还原到原始输入尺寸(640x640)上的坐标。第5列是objectness置信度。第6列开始是类别概率。 - 计算类别分数:最终的置信度分数 =
objectness*max(class_probability)。 - 应用阈值过滤:设定一个置信度阈值(如0.5)和NMS阈值(如0.5),过滤掉低置信度的预测框,并对重叠的框进行非极大值抑制。
- 坐标映射回原图:将过滤后框的坐标,根据之前预处理时的缩放比例和填充偏移量,映射回原始图像的坐标空间。
3.1.3 完整的Python推理示例
结合以上步骤,一个完整的推理函数如下:
def run_inference(session, image_data, conf_thres=0.5, iou_thres=0.5): input_name = session.get_inputs()[0].name output_name = session.get_outputs()[0].name # 运行推理 outputs = session.run([output_name], {input_name: image_data}) predictions = outputs[0] # shape: [1, 84, 8400] # 后处理 (这里是一个简化示例,实际需要完整的解码和NMS) # 1. 转置并重塑 predictions = predictions.squeeze(0).T # [8400, 84] # 2. 过滤掉objectness低的预测 scores = predictions[:, 4:5] * predictions[:, 5:] # [8400, 80] max_scores = np.max(scores, axis=1) keep = max_scores > conf_thres predictions = predictions[keep] max_scores = max_scores[keep] class_ids = np.argmax(scores[keep], axis=1) # 3. 获取框的坐标 (cx, cy, w, h),并转换为 (x1, y1, x2, y2) 格式 boxes = predictions[:, :4] # ... 这里需要根据YOLOv8的解码方式将cx,cy,w,h转换为像素坐标 ... # 假设已经转换完成,得到boxes_xyxy # 4. 执行NMS (可以使用torchvision.ops.nms或自己实现) # indices = nms(boxes_xyxy, max_scores, iou_thres) # final_boxes = boxes_xyxy[indices] # final_scores = max_scores[indices] # final_class_ids = class_ids[indices] return final_boxes, final_scores, final_class_ids3.2 C++环境下的高性能推理部署
对于嵌入式、桌面应用或对延迟要求极高的服务,C++是更优的选择。ONNX Runtime提供了完整的C++ API。
3.2.1 环境搭建与项目配置
首先,你需要获取ONNX Runtime的C++库。有两种方式:
- 下载预编译包:从ONNX Runtime的GitHub Release页面下载对应平台(Windows/Linux)和架构(x64/ARM64)的预编译包。里面包含头文件(include)和库文件(lib或.so/.a)。
- 从源码编译:如果需要定制化(如只启用特定执行提供者),可以克隆源码进行编译。
以Linux系统使用预编译包为例,假设解压到/opt/onnxruntime-linux-x64-gpu-1.15.1。在CMakeLists.txt中配置:
cmake_minimum_required(VERSION 3.16) project(YOLOv8Inference) set(CMAKE_CXX_STANDARD 17) # 找到ONNX Runtime find_package(onnxruntime REQUIRED PATHS /opt/onnxruntime-linux-x64-gpu-1.15.1) add_executable(yolov8_inference main.cpp) target_link_libraries(yolov8_inference onnxruntime)3.2.2 C++推理核心代码解析
C++ API的使用模式与Python类似,但更显式,需要手动管理内存和会话选项。
#include <onnxruntime/core/session/onnxruntime_cxx_api.h> #include <opencv2/opencv.hpp> #include <vector> int main() { // 1. 初始化环境 Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "YOLOv8Inference"); // 2. 配置会话选项 Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); // 设置并行线程数 session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); // 3. 选择执行提供者 (例如CUDA) OrtCUDAProviderOptions cuda_options; cuda_options.device_id = 0; session_options.AppendExecutionProvider_CUDA(cuda_options); // 4. 创建会话 Ort::Session session(env, "yolov8n.onnx", session_options); // 5. 获取模型输入输出信息 auto input_info = session.GetInputTypeInfo(0); auto input_tensor_info = input_info.GetTensorTypeAndShapeInfo(); std::vector<int64_t> input_shape = input_tensor_info.GetShape(); // e.g., {1, 3, 640, 640} size_t input_tensor_size = std::accumulate(input_shape.begin(), input_shape.end(), 1, std::multiplies<int64_t>()); // 6. 准备输入数据 (使用OpenCV读取并预处理图像) cv::Mat img = cv::imread("test.jpg"); cv::Mat resized, float_img; cv::resize(img, resized, cv::Size(640, 640)); resized.convertTo(float_img, CV_32FC3, 1.0 / 255.0); // 将HWC转换为CHW,并转换为BGR顺序的连续内存 std::vector<cv::Mat> channels(3); cv::split(float_img, channels); // 注意:YOLOv8训练时通常使用BGR顺序,所以这里保持BGR std::vector<float> input_tensor_values; for (int c = 2; c >= 0; --c) { // 如果是BGR顺序,则按B,G,R顺序展开 input_tensor_values.insert(input_tensor_values.end(), channels[c].begin<float>(), channels[c].end<float>()); } // 7. 创建输入Tensor auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_shape.data(), input_shape.size()); // 8. 运行推理 const char* input_names[] = {"images"}; // 需要根据模型实际输入名调整 const char* output_names[] = {"output0"}; // 需要根据模型实际输出名调整 std::vector<Ort::Value> output_tensors = session.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor, 1, output_names, 1); // 9. 解析输出 (后处理逻辑与Python类似,但需要用C++实现) float* output_data = output_tensors[0].GetTensorMutableData<float>(); auto output_shape = output_tensors[0].GetTensorTypeAndShapeInfo().GetShape(); // output_shape 可能是 [1, 84, 8400] // ... 后续的解码、阈值过滤、NMS等操作 ... return 0; }注意事项:C++代码中,输入输出张量的名称(如
"images","output0")必须与ONNX模型中的名称严格一致。最稳妥的方式是在Python中先查看模型信息:print([i.name for i in session.get_inputs()])。
3.2.3 内存管理与性能优化
在C++中,性能至关重要。除了使用GPU执行提供者,还需注意:
- 避免数据拷贝:预处理后的图像数据应直接放入
std::vector<float>,然后用于创建Ort::Value。避免中间不必要的拷贝。 - 复用输入输出Tensor:对于连续的视频流推理,可以预先分配好输入输出Tensor的内存,在循环中重复使用,减少动态内存分配的开销。
- 批处理(Batch Inference):如果硬件支持,尽量使用批处理。将多张图片预处理后拼接成一个
[batch_size, 3, H, W]的Tensor进行推理,能显著提升GPU利用率。这需要在导出模型时就考虑支持动态或静态的batch_size。
4. 跨平台部署与高级优化策略
4.1 面向边缘设备的部署:以RK3588和K230为例
将YOLOv8 ONNX模型部署到嵌入式设备(如瑞芯微RK3588、嘉楠K230)是常见需求。这些设备通常带有NPU(神经网络处理单元),能提供远超CPU的AI算力。通用流程是:ONNX -> 专用模型格式 -> 设备端推理引擎。
4.1.1 RK3588部署路径
RK3588通常使用RKNN(Rockchip Neural Network)工具链。部署步骤为:
模型转换:使用RKNN-Toolkit2,将ONNX模型转换为
.rknn格式。这个过程会进行量化(INT8/FP16)、图优化和算子适配。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(model='yolov8n.onnx') rknn.build(do_quantization=True, dataset='./dataset.txt') # 量化需要校准数据集 rknn.export_rknn('./yolov8n.rknn')踩坑记录:量化是提升NPU性能的关键,但可能带来精度下降。务必使用有代表性的校准数据集(几百张覆盖各种场景的图片),并在转换后立即在PC上用RKNN Toolkit模拟运行,评估精度损失。对于YOLOv8,关注mAP@0.5的下降是否在可接受范围(通常<2%)。
C++推理集成:在设备端,使用RKNN提供的C++ API加载
.rknn文件并执行推理。需要注意内存分配和输入输出格式与RKNN SDK的匹配。
4.1.2 K230部署路径
嘉楠K230使用自家的Kendryte AI工具链,核心是NNCase编译器。流程是:ONNX -> KMODEL。
- 环境准备:安装NNCase。根据其官方文档或CSDN博客(如《【onnx模型转kmodel】记录和踩坑——nncase-v1.9使用》)的指引,配置Python环境。
- 模型编译:使用NNCase命令行工具或Python接口进行编译。关键参数包括目标架构(k230)、量化类型(uint8/int8)、输入类型等。
nncase compile yolov8n.onnx yolov8n.kmodel --target k230 --input-type float32 --input-shape [1,3,640,640] --dataset ./calib_images/ --dataset-format image --calibrate-method kld - 设备端推理:在K230的SDK中,调用
kmodel的运行时接口进行推理。需要仔细处理输入数据的布局(例如,可能是NHWC而非NCHW)。
核心挑战:不同NPU的工具链对ONNX算子的支持程度、数据排布(Layout)要求、量化方式差异巨大。务必仔细阅读官方文档和示例代码,并准备一个完整的验证集,在每一步转换后都进行精度测试,确保转换流程无误。
4.2 性能压榨:图优化、量化与提供者选择
即使到了ONNX Runtime这一步,仍有巨大的性能优化空间。
4.2.1 会话选项优化
创建Ort::SessionOptions或Python的SessionOptions时,可以开启一系列优化:
import onnxruntime as ort options = ort.SessionOptions() options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL options.intra_op_num_threads = 4 # 设置并行线程数,根据CPU核心数调整 options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL # 对于CPU,可以尝试启用ARM64的特定优化 # options.add_session_config_entry('session.intra_op.allow_spinning', '0') # 在某些ARM设备上禁用自旋可能省电 session = ort.InferenceSession('model.onnx', options)4.2.2 使用更快的执行提供者
- TensorRT:如果部署在NVIDIA GPU上,
TensorrtExecutionProvider能带来显著的性能提升。它会对计算图进行算子融合、内核自动调优,并利用FP16/INT8量化。需要单独安装onnxruntime-gpu-tensorrt包,并在创建会话时指定该提供者。 - OpenVINO:对于Intel CPU或集成显卡,
OpenVINOExecutionProvider是绝佳选择,它能充分利用Intel平台的指令集和硬件加速。 - CUDA/CANN/ROCM:根据你的GPU厂商(NVIDIA/华为/AMD)选择对应的提供者。
4.2.3 静态图优化与模型简化
在导出ONNX模型后、投入部署前,可以进行一次“模型手术”:
- 常量折叠(Constant Folding):使用ONNX Runtime的
optimizer模块或onnx-simplifier工具,将模型中那些输入为常量的算子(如固定形状的Reshape、Slice)预先计算出来,简化计算图。python -m onnxsim yolov8n.onnx yolov8n_sim.onnx - 算子融合:一些工具可以尝试将连续的
Conv-BatchNorm-ReLU等模式融合成单个算子,减少内核启动开销。 - 删除无用节点:检查模型中是否有仅用于训练的输出分支(如训练时的辅助损失头),在ONNX中将其移除。
4.3 常见问题排查与调试技巧
在实际部署中,你一定会遇到各种奇怪的问题。下面是一个快速排查清单:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 推理结果完全错误 | 1. 预处理/后处理逻辑与训练时不匹配。 2. ONNX导出时节点转换错误。 | 1.黄金法则:用同一张图片,分别用原始PyTorch模型和ONNX模型推理,逐层对比中间输出(可用Netron查看模型结构,定位问题层)。 2. 确保颜色通道顺序(RGB/BGR)、归一化方式(/255或减均值除标准差)、输入尺寸完全一致。 |
| ONNX Runtime加载失败 | 1. ONNX文件损坏或不完整。 2. 包含不支持的算子或opset版本过高。 | 1. 使用onnx.checker.check_model('model.onnx')验证模型完整性。2. 使用Netron可视化模型,检查是否有未知算子。尝试用更低opset版本重新导出模型。 |
| GPU推理速度慢 | 1. 数据在CPU和GPU间频繁拷贝。 2. 未使用最适合的Execution Provider。 3. 输入尺寸过小,GPU利用率低。 | 1. 确保输入数据在GPU内存中(如使用CUDA的cudaMalloc分配,或使用支持GPU的预处理库)。2. 确认 CUDAExecutionProvider或TensorrtExecutionProvider已成功加载。3. 尝试增大 batch_size进行批处理推理。 |
| 内存占用过高 | 1. 会话选项未优化。 2. 模型本身过大。 3. 内存泄漏(C++中常见)。 | 1. 检查是否开启了ORT_ENABLE_ALL优化,它可能增加内存换取速度。对于内存敏感场景,可尝试ORT_ENABLE_BASIC。2. 考虑模型量化(FP16/INT8)或使用更小的YOLOv8变体(如YOLOv8n)。 3. 在C++中,确保所有 Ort::Value和Ort::Session等对象在作用域结束时正确释放。 |
| 量化后精度暴跌 | 1. 校准数据集不具代表性。 2. 量化参数(如裁剪阈值)设置不当。 3. 模型中某些层对量化敏感。 | 1. 校准集应尽可能覆盖实际应用中的所有场景和物体尺度。 2. 尝试不同的量化算法(如KLD、EQ)。对于YOLO,通常对输出层的量化需要更谨慎。 3. 尝试混合精度量化,对敏感层保持FP16。 |
调试利器:Netron遇到任何模型相关的问题,第一反应应该是用Netron(一个在线或本地的模型可视化工具)打开你的.onnx文件。它能清晰地展示整个计算图,让你看到每一个算子的输入输出形状、参数,这对于验证导出是否正确、理解模型结构、定位不兼容的算子至关重要。
5. 从项目实践中来的经验之谈
折腾了这么多YOLOv8的ONNX部署项目,有些经验是文档里不会写的。首先,不要盲目追求最新的opset版本。新版本算子支持固然好,但下游推理引擎的跟进需要时间。如果你的最终部署目标是某个特定的边缘设备或推理框架,先去查它官方文档支持的ONNX opset版本,然后用那个版本导出,能避免99%的兼容性问题。
其次,预处理和后处理是精度丢失的重灾区,而且比模型本身更难调试。我的做法是,把这些逻辑单独封装成一个类或模块,并为其编写详尽的单元测试。测试用例要覆盖各种极端情况:纯黑/纯白图片、不同宽高比的图片、边界框刚好在图像边缘的情况。确保这部分代码的鲁棒性,整个部署流程就稳了一半。
关于量化,这是一个“没有银弹”的领域。量化永远会损失精度,关键是如何控制损失。对于目标检测,我发现在校准集上mAP掉点不超过3%,在实际场景中肉眼几乎看不出差异。但如果你的应用对“小目标”检测非常敏感(比如遥感图像中的车辆),那么量化需要格外小心,可能需要针对性地在数据集中增加小目标的样本,或者对负责小目标检测的浅层特征图采用更保守的量化策略。
最后,也是最重要的一点:建立端到端的评估基准。从原始图片输入,到最终画出检测框,整个pipeline的延迟和精度才是衡量部署成功与否的唯一标准。不要只盯着模型推理的毫秒数,图像解码、预处理、后处理、结果渲染的时间可能加起来比模型推理本身还长。优化是一个系统工程,需要全局视角。