简介:本资源是面向C++开发者与计算机视觉工程师的YOLOv5-v7.0多任务部署实践包,聚焦图像分类、目标检测与实例分割三大核心能力在OpenCV环境下的高效落地。资源提供完整可运行的C++工程,涵盖模型加载、图像预处理、ONNX推理调用及后处理(含NMS与掩码解码)等关键环节,适用于智能监控、工业质检、边缘端AI部署等实际场景。压缩包共11个文件,含3个ONNX模型文件(分别对应分类/检测/分割任务)、3个功能明确的CPP主程序(demo_classification.cpp等)、3个配置说明txt文件及2张测试图(goldfish.jpg、bus.jpg),整体大小19.85MB,结构清晰、模块解耦,便于二次开发与跨平台移植。目前已有257人学习下载,读者可直接复用该工程框架,快速验证YOLOv5-v7.0在C++环境中的多任务性能,并基于源码理解OpenCV DNN模块调用细节与后处理实现逻辑。
1. 为什么用 OpenCV C++ 部署 YOLOv5-v7.0 不是“降级”,而是工业级落地的理性选择
很多人看到“YOLOv5-v7.0 + OpenCV C++”第一反应是:Python PyTorch 模型不是更原生?何必绕路?但真实产线场景里,90% 的嵌入式视觉终端(如工控机、边缘盒子、国产 ARM 平台)、车载 ADAS 前端模块、或需与 Qt/MFC/ROS2 C++ 生态深度耦合的系统,根本不装 Python,不跑 torchscript,也不允许动态链接 CUDA 运行时。YOLOv5-v7.0 的官方模型结构(含 Focus 层、SPPF、Detect head)虽在 PyTorch 中定义清晰,但直接转 ONNX 再用 OpenCV DNN 模块加载时,会因算子兼容性、张量 layout(NHWC/NCHW)、输出解析逻辑差异导致推理结果错位——这不是 OpenCV 的缺陷,而是它刻意保持轻量、跨平台、零依赖的设计哲学决定的。本 demo 的核心价值,是提供一套可验证、可调试、可嵌入、不依赖 CUDA Toolkit 或 cuDNN 版本绑定的 C++ 部署路径:从模型导出、预处理对齐、后处理解码到 OpenCV 绘图全链路闭环,所有代码基于 OpenCV 4.5.2+(支持 ONNX Runtime 后端可选),适配 x86_64 和 aarch64 架构,且关键参数(如 input size、stride、conf threshold)全部外置化,避免硬编码。适合需要快速集成到现有 C++ 工程、或对启动时间/内存 footprint 敏感的开发者。
2. 从 YOLOv5-v7.0 模型导出到 OpenCV 兼容 ONNX 的完整链路
2.1 确认模型版本与导出约束条件
YOLOv5-v7.0 是 Ultralytics 官方在 2023 年发布的稳定分支,其models/yolov5s.yaml中定义的 Detect head 输出为(batch, 3, grid_h, grid_w, nc+5)格式,其中nc为类别数,5对应(x,y,w,h,conf)。OpenCV DNN 模块(≥4.5.0)支持 ONNX opset 11~15,但不支持 DynamicQuantizeLinear、NonMaxSuppression(NMS)等后处理算子——这意味着 NMS 必须在 C++ 层手动实现。因此导出时必须禁用模型内置 NMS,保留原始 logits 输出。常见错误是直接运行export.py默认参数,导致 ONNX 中包含NonMaxSuppression节点,OpenCV 加载时报Unsupported op type: NonMaxSuppression。
提示:YOLOv5-v7.0 的
export.py需显式传参--include onnx --opset 12 --dynamic --simplify --no-nms。--no-nms是关键开关,它强制模型只输出 raw output,不封装后处理逻辑。
2.1.1 导出命令与验证步骤
# 在 yolov5-v7.0 根目录执行(假设已安装 torch>=1.12, onnx>=1.12) python export.py \ --weights yolov5s.pt \ --include onnx \ --opset 12 \ --dynamic \ --simplify \ --no-nms \ --img-size 640 640导出后得到yolov5s.onnx。验证其输出结构是否符合预期:
import onnx model = onnx.load("yolov5s.onnx") for output in model.graph.output: print(f"Output name: {output.name}, shape: {output.type.tensor_type.shape}") # 正确输出应类似: # Output name: output0, shape: [1, 3, 80, 80, 85] # Output name: output1, shape: [1, 3, 40, 40, 85] # Output name: output2, shape: [1, 3, 20, 20, 85]若出现output0形状为[1, 25200, 85](即展平后的 anchor-free 输出),说明导出时未正确启用--dynamic或模型配置有误,需回查models/yolov5s.yaml中head部分是否为标准 Detect 类。
2.2 OpenCV DNN 模块加载 ONNX 的关键配置
OpenCV 4.5.2 引入了对 ONNX 的原生支持,但默认使用内置 CPU 推理引擎(dnn::Net::setPreferableBackend(DNN_BACKEND_OPENCV))。若需 GPU 加速,必须显式启用 DNN_BACKEND_CUDA(要求 OpenCV 编译时开启 CUDA 支持):
cv::dnn::Net net = cv::dnn::readNetFromONNX("yolov5s.onnx"); // CPU 模式(通用,无需额外依赖) net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); // CUDA 模式(需 OpenCV with CUDA 编译,且显卡驱动 ≥470.0+) // net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); // net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);注意:
DNN_BACKEND_CUDA在 OpenCV 4.5.2 中仅支持 FP16 推理(需net.setHalfPrecision(true)),且必须确保 ONNX 模型中无 unsupported op(如Softmax的 axis 参数若为负值,CUDA backend 可能失败)。建议首次调试始终用 CPU backend,确认逻辑正确后再切 GPU。
2.2.1 输入预处理:尺寸、归一化、通道顺序三重对齐
YOLOv5-v7.0 训练时采用 BGR 输入(OpenCV 默认)、[0,255] → [0,1]归一化、再×255.0(实际是除以 255.0),且 mean/std 为[0.0,0.0,0.0] / [1.0,1.0,1.0](即仅做缩放)。这与 TorchVision 的normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])完全不同。C++ 中必须严格复现:
cv::Mat preprocess(const cv::Mat& src, const cv::Size& input_size) { cv::Mat resized; cv::resize(src, resized, input_size); // 保持长宽比?否!YOLOv5 使用 strict resize(拉伸) cv::Mat blob = cv::dnn::blobFromImage( resized, 1.0 / 255.0, // scale factor input_size, cv::Scalar(0, 0, 0), // mean (BGR order) true, // swap RB? no — YOLOv5 uses BGR false // crop? false — use resize, not letterbox ); return blob; }swapRB=false:YOLOv5 输入是 BGR,OpenCVblobFromImage默认swapRB=true(转 RGB),此处必须设false。crop=false:官方训练用rectangular resize(非 letterbox),故cv::resize直接拉伸,blobFromImage不裁剪。1.0/255.0:缩放因子,非1/255.0f(float 字面量),避免整数除法。
2.3 输出解析:从 raw logits 到 bounding box 的数学映射
YOLOv5-v7.0 的 Detect head 输出为(batch, anchors, grid_h, grid_w, nc+5),每个 grid cell 对应 3 个 anchor。OpenCV 加载后,输出 blob 的维度为[1, 3, h, w, 85](以yolov5s为例)。需按以下步骤解码:
| 输出层 | grid size | stride | anchor count |
|---|---|---|---|
| output0 | 80×80 | 8 | 3 |
| output1 | 40×40 | 16 | 3 |
| output2 | 20×20 | 32 | 3 |
解码公式(以 output0 为例):
x = (sigmoid(x) * 2 - 0.5 + cx) * stridey = (sigmoid(y) * 2 - 0.5 + cy) * stridew = (sigmoid(w) * 2)² * anchor_wh = (sigmoid(h) * 2)² * anchor_hconf = sigmoid(obj_conf) * sigmoid(cls_conf)
其中cx, cy为 grid cell 坐标(0~79),anchor_w/h来自models/yolov5s.yaml中anchors字段(如[[10,13, 16,30, 33,23]])。
2.3.1 C++ 中实现 sigmoid 与坐标解码
std::vector<cv::Rect> decode_output(const cv::Mat& output, const std::vector<std::vector<float>>& anchors, int stride, int grid_h, int grid_w, float conf_threshold, float iou_threshold) { std::vector<cv::Rect> boxes; std::vector<float> scores; std::vector<int> class_ids; const float* data = output.ptr<float>(); for (int a = 0; a < 3; ++a) { // 3 anchors per layer for (int y = 0; y < grid_h; ++y) { for (int x = 0; x < grid_w; ++x) { int offset = a * grid_h * grid_w * 85 + y * grid_w * 85 + x * 85; float obj_conf = 1.0f / (1.0f + expf(-data[offset + 4])); // sigmoid if (obj_conf < conf_threshold) continue; // class score: max of nc classes float cls_conf = 0.0f; int cls_id = 0; for (int c = 0; c < 80; ++c) { // nc=80 for coco float score = data[offset + 5 + c]; if (score > cls_conf) { cls_conf = score; cls_id = c; } } float conf = obj_conf * (1.0f / (1.0f + expf(-cls_conf))); // sigmoid if (conf < conf_threshold) continue; // decode xywh float tx = data[offset + 0], ty = data[offset + 1]; float tw = data[offset + 2], th = data[offset + 3]; float bx = (sigmoid(tx) * 2.f - 0.5f + x) * stride; float by = (sigmoid(ty) * 2.f - 0.5f + y) * stride; float bw = powf(sigmoid(tw) * 2.f, 2.f) * anchors[a][0]; float bh = powf(sigmoid(th) * 2.f, 2.f) * anchors[a][1]; int left = static_cast<int>(bx - bw/2.f); int top = static_cast<int>(by - bh/2.f); boxes.emplace_back(left, top, static_cast<int>(bw), static_cast<int>(bh)); scores.push_back(conf); class_ids.push_back(cls_id); } } } // NMS std::vector<int> indices; cv::dnn::NMSBoxes(boxes, scores, conf_threshold, iou_threshold, indices); std::vector<cv::Rect> final_boxes; for (int idx : indices) { final_boxes.push_back(boxes[idx]); } return final_boxes; }sigmoid(x) = 1/(1+exp(-x))必须手写,不可调用std::exp外部函数(性能敏感)。anchors[a][0]对应 width,anchors[a][1]对应 height,顺序不能颠倒。cv::dnn::NMSBoxes是 OpenCV 提供的 CPU NMS 实现,输入为std::vector<cv::Rect>,非 raw logits。
3. 构建可运行的 C++ Demo:编译、参数化与多任务支持
3.1 CMakeLists.txt 关键配置与跨平台兼容性
OpenCV C++ 项目必须明确指定 OpenCV 的头文件路径和库链接。以下为最小可行 CMake 配置(支持 Linux/macOS/Windows):
cmake_minimum_required(VERSION 3.10) project(yolov5_opencv_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找 OpenCV(自动检测 pkg-config 或 cmake config) find_package(OpenCV REQUIRED COMPONENTS core imgproc dnn highgui) # 添加可执行文件 add_executable(yolov5_demo main.cpp) # 链接 OpenCV 库 target_link_libraries(yolov5_demo ${OpenCV_LIBS}) # 包含 OpenCV 头文件 target_include_directories(yolov5_demo PRIVATE ${OpenCV_INCLUDE_DIRS}) # Windows 下需额外链接 ws2_32(网络功能)和 vfw32(视频捕获) if(WIN32) target_link_libraries(yolov5_demo ws2_32 vfw32) endif() # macOS 下需链接 AVFoundation(摄像头支持) if(APPLE) find_library(AVFOUNDATION_LIBRARY AVFoundation) if(AVFOUNDATION_LIBRARY) target_link_libraries(yolov5_demo ${AVFOUNDATION_LIBRARY}) endif() endif()提示:若 OpenCV 为源码编译安装(非 apt-get 或 brew),需设置
OpenCV_DIR环境变量指向opencv/build/install/lib/cmake/opencv4,否则find_package会失败。
3.1.1 main.cpp 主流程:支持分类、检测、分割三模式切换
YOLOv5-v7.0 原生不支持分割(Segmentation),但可通过修改 Detect head 为 Segment head(添加 protos 分支)并导出 ONNX 实现。本 demo 将三种任务抽象为同一入口:
int main(int argc, char** argv) { if (argc < 3) { std::cerr << "Usage: " << argv[0] << " <model.onnx> <input.jpg|0> [--task detect|classify|segment]" << std::endl; return -1; } std::string model_path = argv[1]; std::string input_path = argv[2]; std::string task = "detect"; // default if (argc > 3 && std::string(argv[3]) == "--task") { task = argv[4]; } cv::dnn::Net net = cv::dnn::readNetFromONNX(model_path); net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); if (task == "detect") { run_detection(net, input_path); } else if (task == "classify") { run_classification(net, input_path); } else if (task == "segment") { run_segmentation(net, input_path); } return 0; }--task参数使 demo 具备多模态能力,无需编译多个二进制。run_classification仅需取 output 最大 logit index,run_segmentation需额外解析 protos 分支(见 4.2)。
3.2 检测任务完整 pipeline:从图像读取到结果绘制
run_detection函数封装了预处理、推理、后处理、可视化全流程:
void run_detection(cv::dnn::Net& net, const std::string& input_path) { cv::Mat frame = cv::imread(input_path); if (frame.empty()) { std::cerr << "Failed to load image: " << input_path << std::endl; return; } const cv::Size input_size(640, 640); cv::Mat blob = preprocess(frame, input_size); net.setInput(blob); std::vector<cv::Mat> outputs; net.forward(outputs, net.getUnconnectedOutLayersNames()); // anchors from yolov5s.yaml: [[10,13, 16,30, 33,23], [30,61, 62,45, 59,119], [116,90, 156,198, 373,326]] std::vector<std::vector<std::vector<float>>> anchors = { {{10,13}, {16,30}, {33,23}}, {{30,61}, {62,45}, {59,119}}, {{116,90}, {156,198}, {373,326}} }; std::vector<cv::Rect> all_boxes; std::vector<float> all_scores; std::vector<int> all_class_ids; for (size_t i = 0; i < outputs.size(); ++i) { int stride = (i == 0) ? 8 : (i == 1) ? 16 : 32; int grid_h = input_size.height / stride; int grid_w = input_size.width / stride; auto boxes = decode_output(outputs[i], anchors[i], stride, grid_h, grid_w, 0.25f, 0.45f); all_boxes.insert(all_boxes.end(), boxes.begin(), boxes.end()); } // Draw results for (size_t i = 0; i < all_boxes.size(); ++i) { cv::rectangle(frame, all_boxes[i], cv::Scalar(0,255,0), 2); std::string label = "person: " + std::to_string(static_cast<int>(all_scores[i]*100)) + "%"; int baseline; cv::Size text_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline); cv::rectangle(frame, cv::Point(all_boxes[i].x, all_boxes[i].y - text_size.height - 10), cv::Point(all_boxes[i].x + text_size.width, all_boxes[i].y), cv::Scalar(0,255,0), -1); cv::putText(frame, label, cv::Point(all_boxes[i].x, all_boxes[i].y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,0,0), 1); } cv::imshow("YOLOv5 Detection", frame); cv::waitKey(0); }net.getUnconnectedOutLayersNames()自动获取所有输出层名,避免硬编码"output0"等。cv::getTextSize计算文本框尺寸,确保标签背景不溢出 bbox。cv::Scalar(0,255,0)为绿色,符合 OpenCV BGR 通道顺序。
3.3 分类与分割任务的差异化实现要点
3.3.1 分类任务:简化输出解析,提升吞吐量
YOLOv5 分类模型(如yolov5s-cls.onnx)输出为(1, 1000)logits,无需 grid 解码。run_classification只需:
void run_classification(cv::dnn::Net& net, const std::string& input_path) { cv::Mat frame = cv::imread(input_path); cv::Mat blob = cv::dnn::blobFromImage(frame, 1.0/255.0, cv::Size(224,224), cv::Scalar(0,0,0), true); net.setInput(blob); cv::Mat output = net.forward(); cv::Point class_id; double confidence; cv::minMaxLoc(output, nullptr, &confidence, nullptr, &class_id); std::cout << "Predicted class: " << class_id.x << ", confidence: " << confidence << std::endl; }- 输入尺寸为
224x224(分类标准),非640x640。 cv::minMaxLoc直接获取最大值位置,比遍历更快。
3.3.2 分割任务:protos 分支解析与 mask 合成
YOLOv5-v7.0 的 Segmentation 模型(如yolov5s-seg.onnx)输出包含两部分:
output0: detection logits(同 detect)output1: protos tensor(1, 32, 160, 160)(32-channel prototype masks)
mask 合成公式:mask = Σ (proto[i] × uconv[i]),其中uconv为 detection head 输出的 mask coefficients(每 bbox 32 维)。
void run_segmentation(cv::dnn::Net& net, const std::string& input_path) { // ... same preprocessing as detection ... net.setInput(blob); std::vector<cv::Mat> outputs; net.forward(outputs, net.getUnconnectedOutLayersNames()); // outputs[0]: detection, outputs[1]: protos (1,32,160,160) cv::Mat protos = outputs[1]; // (1,32,160,160) cv::Mat detection = outputs[0]; // (1,3,80,80,85) etc. // decode detection to get mask coefficients (last 32 elements of each bbox) // then compute mask = protos @ coefficients // ... (detailed matrix multiplication omitted for brevity) ... }protos尺寸为160x160,需双线性上采样至原图尺寸。- mask 系数维度必须与 protos channel 数(32)一致,否则矩阵乘法维度不匹配。
4. 调试与性能优化:定位 OpenCV DNN 加载失败的 3 类根源
4.1 ONNX 加载失败的诊断树
当cv::dnn::readNetFromONNX()抛出异常(如cv::Exception),按以下顺序排查:
| 现象 | 可能原因 | 验证命令 | 修复方式 |
|---|---|---|---|
Can't create layer "xxx" of type "yyy" | ONNX op 不被 OpenCV 支持(如GatherND,ScatterND) | onnx.shape_inference.infer_shapes(model) | 用onnx-simplifier简化模型,或修改 PyTorch 导出代码(如替换torch.gather为index_select) |
Unsupported data type: 10 (double) | ONNX 中存在 double 类型张量(OpenCV 仅支持 float32) | python -c "import onnx; m=onnx.load('x.onnx'); print([n.type.tensor_type.elem_type for n in m.graph.input])" | 导出时加--half(FP16)或确保 PyTorch tensor 为float32 |
Net::forward() exception: ... invalid pointer | 输入 blob 维度与模型期望不符(如[1,3,640,640]vs[1,3,640,640,1]) | print(net.getInputShape())in Python | 检查blobFromImage参数,禁用crop=true或swapRB=false错误 |
4.1.1 使用 OpenCV 自带工具验证 ONNX
OpenCV 提供opencv_dnn_test工具(需编译时开启BUILD_TESTS=ON):
# 编译 OpenCV 时启用测试 cmake -DBUILD_TESTS=ON .. make -j$(nproc) # 运行 ONNX 验证 ./build/bin/opencv_dnn_test --gtest_filter=*ONNX* --onnx_model=yolov5s.onnx若测试通过,说明模型结构无问题;失败则提示具体 op 不支持。
4.2 推理速度瓶颈分析与加速策略
在 Intel i7-11800H 上,YOLOv5s ONNX CPU 推理耗时约 80~120ms。优化方向:
| 优化项 | 方法 | 效果 | 注意事项 |
|---|---|---|---|
| 输入尺寸 | 降为320x320 | 速度↑2.1x,mAP↓3.2% | 修改preprocess()中input_size,同步更新 anchors stride |
| OpenMP 并行 | export OMP_NUM_THREADS=8 | CPU 利用率↑,单次推理↓15% | 无需改代码,环境变量生效 |
| FP16 推理 | net.setHalfPrecision(true) | 速度↑1.3x(仅 CUDA backend) | CPU backend 不支持,且需模型导出时启用--half |
// 启用 OpenMP(Linux/macOS) #include <omp.h> #pragma omp parallel for for (int i = 0; i < 100; ++i) { // inference loop }注意:OpenCV 4.5.2 的 DNN CPU backend 默认启用 OpenMP,无需手动并行。
OMP_NUM_THREADS设置为物理核心数最佳(非超线程数)。
4.3 多线程部署中的资源竞争规避
当 demo 扩展为视频流处理(cv::VideoCapture)时,cv::dnn::Net实例非线程安全。常见错误是多个线程共用同一net对象调用setInput/forward:
// ❌ 危险:共享 net 实例 std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&net](cv::Mat& frame) { net.setInput(preprocess(frame)); // 竞态:setInput 修改内部状态 net.forward(); }, std::ref(frames[i])); }✅ 正确做法:每个线程持有独立Net实例(内存开销可接受,因权重只加载一次):
std::vector<cv::dnn::Net> nets(4); for (auto& n : nets) { n = cv::dnn::readNetFromONNX("yolov5s.onnx"); // copy constructor is cheap n.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); }cv::dnn::Net的拷贝构造函数不复制权重数据,仅共享底层cv::Ptr<Impl>,内存占用极小。- 若需极致内存控制,可用
std::shared_ptr<cv::dnn::Net>管理单例,但必须加 mutex 保护setInput/forward调用。
5. 实战技巧:用 OpenCV 绘制高质量分割掩膜与动态置信度阈值
5.1 分割掩膜的抗锯齿渲染与透明叠加
原始分割 mask 为二值图(0/255),直接cv::bitwise_and叠加会导致边缘锯齿。高质量渲染需:
- 对 mask 进行
cv::GaussianBlur(kernel=3)柔化边缘; - 用
cv::applyColorMap映射为伪彩色(如COLORMAP_JET); - 用
cv::addWeighted以 alpha=0.5 叠加到原图。
cv::Mat render_mask(const cv::Mat& mask, const cv::Mat& src, const cv::Scalar& color) { cv::Mat colored; cv::applyColorMap(mask, colored, cv::COLORMAP_JET); cv::Mat blurred; cv::GaussianBlur(mask, blurred, cv::Size(3,3), 0); cv::Mat overlay = src.clone(); cv::addWeighted(src, 0.5, colored, 0.5, 0.0, overlay); return overlay; }cv::GaussianBlur的sigmaX=0表示自动计算,Size(3,3)为最小核,平衡质量与速度。cv::addWeighted的 gamma=0.0 确保无偏移叠加。
5.2 动态置信度阈值:根据场景光照自适应调整
固定conf_threshold=0.25在暗光下漏检、强光下误检。可基于输入图像亮度直方图动态计算:
float adaptive_conf_threshold(const cv::Mat& frame) { cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); cv::Scalar mean_val = cv::mean(gray); float brightness = mean_val[0]; // 0~255 // 暗光(<60)提高阈值防误检,亮光(>180)降低阈值防漏检 return brightness < 60 ? 0.4f : (brightness > 180 ? 0.15f : 0.25f); } // 在 run_detection 中调用 float conf_thresh = adaptive_conf_threshold(frame); auto boxes = decode_output(outputs[i], anchors[i], stride, grid_h, grid_w, conf_thresh, 0.45f);cv::mean计算灰度图均值,比cv::sum更鲁棒(不受数据类型影响)。- 阈值范围
0.15~0.4覆盖典型场景,避免极端值(<0.1 易误检,>0.5 漏检严重)。
5.3 模型量化部署:INT8 推理的精度-速度权衡表
OpenCV 4.5.2 支持 ONNX 模型 INT8 量化(需DNN_BACKEND_INFERENCE_ENGINE),但需额外依赖 OpenVINO。若仅用 OpenCV,可手动量化:
| 量化方式 | 工具 | 速度提升 | mAP 损失 | 适用场景 |
|---|---|---|---|---|
| FP16(导出时) | export.py --half | +1.3x | <0.5% | CUDA backend |
| INT8(TensorRT) | trtexec --onnx=x.onnx --int8 | +2.8x | ~1.2% | NVIDIA Jetson |
| OpenCV DNN 伪量化 | 手动cv::convertScaleAbs | +0.0x | 不适用 | 仅用于调试 |
实际项目中,优先用 FP16 + CUDA backend,而非强行 INT8。YOLOv5-v7.0 的 FP16 推理精度损失可忽略,且 OpenCV 对其支持成熟。
本文还有配套的精品资源,点击获取