首页/新闻资讯/正文详情

OpenCV DNN 算子兼容性踩坑后,我改用 ONNXRuntime 部署 yolov5-lite

发布时间:2026/9/23 14:06:53 来源:云帆数科 栏目:资讯中心
OpenCV DNN 算子兼容性踩坑后,我改用 ONNXRuntime 部署 yolov5-lite
简介这份资源面向需要在边缘设备或算力受限环境中落地目标检测的开发者提供使用ONNXRuntime部署轻量级YOLOv5-lite模型的完整示例。针对OpenCV的dnn模块读取.onnx文件出错的问题作者改用ONNXRuntime作为推理引擎并同时给出C与Python两套实现便于不同技术栈的读者对照移植。压缩包共14个文件、约41.35MB包含cpp与py源码、多个onnx模型文件、coco.names类别标签、若干jpg与png测试图片以及README说明文档和资源内容、标签等txt文本覆盖从模型加载、图像预处理到结果输出的关键环节。目前已有71人学习下载。读者可借此快速理解ONNXRuntime的调用流程与YOLOv5-lite的部署要点参考现成代码完成模型加载、输入处理与检测结果解析并借助示例图片验证推理效果为在边缘计算场景中构建高性能目标检测应用提供可直接复用的工程模板。1. 为什么我放弃了 OpenCV DNN转投 ONNXRuntime 部署 yolov5-lite前阵子帮朋友调一个边缘盒子上的目标检测模型选的是 yolov5-lite理由很直接v5lite-s 的 onnx 才几兆v5lite-g 也就十几兆比原版 yolov5s 小一圈精度掉得还能接受。结果用 OpenCV 的readNetFromONNX加载直接报错换了好几个 OpenCV 版本都不行网上一搜发现是 DNN 模块对某些算子支持不全的老毛病。折腾半天没结果索性换成 ONNXRuntime一次就跑通了。这个压缩包就是那次的产物一份 C 和 Python 双版本的 yolov5-lite ONNXRuntime 部署示例模型文件、测试图、类别名都配齐了拿来就能验证。如果你也在边缘设备上跑目标检测或者被 OpenCV DNN 的算子兼容性坑过这份东西能帮你省掉重新搭轮子的时间。2. 拆开压缩包文件清单与 ONNXRuntime 推理链路2.1 目录里到底有什么先把包里的东西过一遍心里有个数再动手。整个压缩包的结构不复杂但每个文件都有明确用途缺一个都跑不起来。文件/目录用途备注main.pyPython 版本推理入口依赖 onnxruntime、opencv-python、numpymain.cppC 版本推理入口依赖 onnxruntime 动态库、OpenCVonnxmodel/存放 ONNX 模型含 v5lite-c、v5lite-s、v5lite-g 三个imgs/测试图片street、person、dog、zidane、bus 等coco.namesCOCO 80 类类别名每行一个类别README.md项目说明安装与使用指引标签.txt/资源内容.txt辅助信息类别标签与资源列表三个模型的区别值得说清楚v5lite-c 最小适合算力极紧的场景v5lite-s 是精度和速度的平衡点我一般默认用它v5lite-g 精度最高但体积也最大。选哪个不是拍脑袋得看你设备的算力和对 mAP 的容忍度。2.2 ONNXRuntime 推理链路是怎么走的不管 C 还是 PythonONNXRuntime 跑 yolov5-lite 的流程是一致的理解这条链路比记 API 重要得多。第一步是加载模型创建InferenceSession这一步会把 onnx 图解析成可执行的计算图。第二步是准备输入yolov5-lite 的输入是1x3x640x640的 float32 张量需要把原图 resize、归一化到 0~1、再转成 NCHW 布局。第三步是session.run()执行推理拿到输出。yolov5-lite 的输出通常是1x25200x85其中 85 4 个框坐标 1 个置信度 80 个类别分数。第四步是后处理做置信度过滤和 NMS把冗余框去掉。这里有个容易忽略的点yolov5-lite 的输出坐标是相对于 640x640 输入图的映射回原图时要按比例缩放而且要考虑 letterbox 填充的偏移。很多人第一次跑出来框位置偏了八成是这一步没处理对。2.3 Python 版本从加载到出框的完整代码Python 版本适合快速验证模型效果改起来也方便。下面这段是核心逻辑我按实际能跑通的写法整理import cv2 import numpy as np import onnxruntime as ort # 加载模型providers 按优先级排列CPU 环境只留 CPUExecutionProvider session ort.InferenceSession(onnxmodel/v5lite-s.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name def letterbox(img, new_shape640): # 等比缩放并填充到 640x640保持长宽比填充值 114 shape img.shape[:2] r min(new_shape / shape[0], new_shape / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) dw, dh new_shape - new_unpad[0], new_shape - new_unpad[1] dw, dh dw // 2, dh // 2 img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) top, bottom dh, new_shape - new_unpad[1] - dh left, right dw, new_shape - new_unpad[0] - dw img cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value(114, 114, 114)) return img, r, (left, top) def preprocess(img): # BGR-RGB归一化HWC-CHW加 batch 维 img, r, pad letterbox(img) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1))[None, ...] return np.ascontiguousarray(img), r, pad def nms(boxes, scores, iou_thres0.45): # 标准 NMS按置信度降序逐个保留 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]) w np.maximum(0, xx2 - xx1) h np.maximum(0, yy2 - yy1) inter w * h area_i (boxes[i, 2] - boxes[i, 0]) * (boxes[i, 3] - boxes[i, 1]) area_j (boxes[idxs[1:], 2] - boxes[idxs[1:], 0]) * \ (boxes[idxs[1:], 3] - boxes[idxs[1:], 1]) iou inter / (area_i area_j - inter 1e-6) idxs idxs[1:][iou iou_thres] return keep def postprocess(output, r, pad, conf_thres0.25): # output: 1x25200x85前 4 列是 cx,cy,w,h第 5 列是 obj 置信度 pred output[0] scores pred[:, 4] mask scores conf_thres pred pred[mask] if pred.shape[0] 0: return [] boxes pred[:, :4] cls_scores pred[:, 5:] cls_ids cls_scores.argmax(axis1) cls_conf cls_scores.max(axis1) final_scores scores[mask] * cls_conf # cx,cy,w,h - x1,y1,x2,y2 xy np.zeros_like(boxes) xy[:, 0] boxes[:, 0] - boxes[:, 2] / 2 xy[:, 1] boxes[:, 1] - boxes[:, 3] / 2 xy[:, 2] boxes[:, 0] boxes[:, 2] / 2 xy[:, 3] boxes[:, 1] boxes[:, 3] / 2 # 去掉 letterbox 填充并还原到原图尺度 xy[:, [0, 2]] (xy[:, [0, 2]] - pad[0]) / r xy[:, [1, 3]] (xy[:, [1, 3]] - pad[1]) / r keep nms(xy, final_scores) return [(xy[i], final_scores[i], cls_ids[i]) for i in keep] img cv2.imread(imgs/bus.jpg) inp, r, pad preprocess(img) out session.run(None, {input_name: inp})[0] results postprocess(out, r, pad) for box, score, cid in results: print(fclass{cid} score{score:.3f} box{box.astype(int)})逻辑说明letterbox保证长宽比不变避免直接 resize 导致目标变形preprocess里的np.ascontiguousarray不能省ONNXRuntime 对非连续内存会报错或性能下降。postprocess里先按 obj 置信度粗筛再乘类别分数做最终分数最后 NMS。参数方面conf_thres默认 0.25 是常用起点漏检多就降到 0.1误检多就提到 0.4iou_thres0.45 适合大多数场景密集目标可以调到 0.5。2.4 C 版本动态库链接与推理主循环C 版本适合最终部署到生产环境性能比 Python 好但编译配置是第一个坎。核心代码结构如下#include onnxruntime_cxx_api.h #include opencv2/opencv.hpp int main() { // 1. 创建环境与 session线程数按 CPU 核数设 Ort::Env env(ORT_LOGGING_LEVEL_WARNING, yolov5lite); Ort::SessionOptions opts; opts.SetIntraOpNumThreads(4); opts.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); Ort::Session session(env, onnxmodel/v5lite-s.onnx, opts); // 2. 查询输入输出名称避免硬编码 Ort::AllocatorWithDefaultOptions allocator; auto input_name session.GetInputNameAllocated(0, allocator); auto output_name session.GetOutputNameAllocated(0, allocator); // 3. 读取图像并做 letterbox 归一化逻辑同 Python cv::Mat img cv::imread(imgs/bus.jpg); cv::Mat blob preprocess(img); // 返回 1x3x640x640 float32 // 4. 构造输入张量 std::vectorint64_t input_shape {1, 3, 640, 640}; auto memory_info Ort::MemoryInfo::CreateCpu( OrtArenaAllocator, OrtMemTypeDefault); Ort::Value input_tensor Ort::Value::CreateTensorfloat( memory_info, (float*)blob.data, blob.total(), input_shape.data(), input_shape.size()); // 5. 执行推理 const char* input_names[] {input_name.get()}; const char* output_names[] {output_name.get()}; auto outputs session.Run(Ort::RunOptions{nullptr}, input_names, input_tensor, 1, output_names, 1); // 6. 取输出并做后处理输出形状 1x25200x85 float* out outputs[0].GetTensorMutableDatafloat(); postprocess(out, img); // 内部做阈值过滤、NMS、坐标还原 return 0; }逻辑说明SetIntraOpNumThreads控制算子内并行线程数边缘设备上设成物理核数比较稳设太大反而因调度开销掉速。GetInputNameAllocated拿到的名称是智能指针管理的别用裸指针接。输入张量的内存必须连续OpenCV 的Mat如果是 ROI 切片可能不连续preprocess里要clone()一下。编译时链接onnxruntime动态库Windows 下还要把onnxruntime.dll放到可执行文件同目录否则运行时报找不到库。3. 避坑排查从模型加载到后处理的五个翻车点3.1 模型加载报错 Failed to load model现象InferenceSession初始化直接抛异常提示模型加载失败。原因通常是 onnx 文件路径含中文或空格或者文件本身下载不完整。解决把模型放到纯英文路径下用os.path.exists先确认文件存在且大小正常v5lite-s 的 onnx 应该在 7MB 左右明显偏小就是没下完。3.2 输出形状对不上拿到 1x85x25200现象后处理时按1x25200x85解析结果框全乱。原因不同导出脚本产出的 onnx 输出布局可能不同有的是1x25200x85有的是1x85x25200。解决先打印session.get_outputs()[0].shape确认如果是后者在 postprocess 里先np.transpose(output[0], (1, 0))再处理。3.3 检测框位置整体偏移现象框能出来但位置偏上或偏左。原因letterbox 的填充偏移pad没在坐标还原时减掉或者减的顺序反了。解决还原公式是(x - pad_x) / r注意 x 方向减lefty 方向减top别搞混。我一般会在原图上画个中心十字跑一张图确认映射对不对。3.4 C 编译时报找不到 onnxruntime_cxx_api.h现象编译阶段头文件找不到。原因include 路径没配或者下载的是预编译包但没设ONNXRUNTIME_ROOT。解决编译命令里加-I${ONNXRUNTIME_ROOT}/include链接加-L${ONNXRUNTIME_ROOT}/lib -lonnxruntime。Windows 下用 CMake 的话find_package(onnxruntime)不一定有直接手动指定路径更省事。3.5 推理速度比预期慢很多现象单张图推理要几百毫秒。原因一是没用ORT_ENABLE_ALL图优化二是输入张量内存不连续触发拷贝三是线程数设得不合理。解决确认SetGraphOptimizationLevel开了输入用ascontiguousarray或clone()保证连续线程数从 4 开始试用time.time()或std::chrono打点看瓶颈在哪。4. 进阶技巧多模型切换与批量推理的实用改法跑通单张之后实际用起来还有两个需求一是根据设备算力动态选模型二是批量处理图片时别一张张串行跑。前者简单把模型路径做成配置项启动时读环境变量或配置文件决定加载哪个。我一般会写个model_zoo字典把三个模型的路径和输入尺寸对应起来切换只改一个 key。批量推理稍微麻烦点。ONNXRuntime 支持动态 batch但 yolov5-lite 导出时如果固定了 batch1就得改模型或循环跑。循环跑的话把 session 创建放在循环外别每次推理都重建 session那个开销比推理本身还大。下面是个批量处理的骨架import glob import time session ort.InferenceSession(onnxmodel/v5lite-s.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name for path in glob.glob(imgs/*.jpg): img cv2.imread(path) inp, r, pad preprocess(img) t0 time.time() out session.run(None, {input_name: inp})[0] dt (time.time() - t0) * 1000 results postprocess(out, r, pad) print(f{path}: {len(results)} objects, {dt:.1f}ms)这里的关键是 session 复用和计时打点。计时能帮你判断瓶颈在预处理、推理还是后处理。我实测 v5lite-s 在普通 x86 CPU 上单张 640x640 大概 30~50ms如果超过 100ms 就得查线程数和图优化了。还有个验证技巧拿imgs/zidane.jpg这种多人图跑看能不能把两个人框都出来且不重叠。如果 NMS 后框重叠严重说明 iou_thres 设高了如果同一个人出两个框说明 conf_thres 太低。调参没有银弹就是拿几张典型图反复试把参数记下来。从那以后我每次部署新模型都强制先用一张已知结果的图跑通全流程确认坐标映射和 NMS 没问题再上批量数据。这个习惯帮我省了不少返工。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

Koopman算子加速非线性MPC:从数据驱动建模到QP求解的工程实践
Koopman算子加速非线性MPC:从数据驱动建模到QP求解的工程实践

简介:这份资源围绕Koopman算子与模型预测控制(MPC)的结合展开,面向具备一定控制理论基础、希望深入非线性系统控制的研究生、工程师及科研人员。其核心思路是在高维提升空间中借助Koopman算子的线性特性来刻画并控制非线性动态系统… · 2026/9/23 14:06:52

Word 2007工具栏消失怎么办?从最小化到注册表重置的完整恢复指南
Word 2007工具栏消失怎么办?从最小化到注册表重置的完整恢复指南

先问你一个情景:正用Word 2007排版,突然发现上面那排“开始、插入、页面布局”的选项卡只剩下光秃秃的名字,或者干脆连名字都找不到,整个工具栏区域凭空消失了;再严重一点,连左上角那些“保存、撤销”的小图… · 2026/9/23 14:06:46

中医五味手写实现,面试必问的5个坑点全解析
中医五味手写实现,面试必问的5个坑点全解析

中医五味手写实现,面试必问的5个坑点全解析 复制来的中医五味算法代码,跑起来全是乱码,报错信息根本看不懂,这种“复制即崩”的绝望感,相信不少刚入行的同学都经历过。更扎心的是,当面试官甩出一句“请手写一个五味相生相克的状态机”时,你只能尴尬地… · 2026/9/23 14:06:46

三维地图制作性能优化一文搞懂:解决API变动后的卡顿难题
三维地图制作性能优化一文搞懂:解决API变动后的卡顿难题

三维地图制作性能优化一文搞懂:解决API变动后的卡顿难题 版本升级后 API 全变了,你的三维地图还在掉帧吗?别急着骂娘,先看看是不是渲染逻辑没跟上。很多开发者在 Cesium 或 Three.js… · 2026/9/23 14:55:32

35资料网拆解:搞定高频面试题的源码逻辑
35资料网拆解:搞定高频面试题的源码逻辑

35资料网拆解:搞定高频面试题的源码逻辑 配置环境就卡半天,是不是常态? 别急着骂娘,大概率是依赖版本没对齐。 今天聊点硬核的,结合【35资料网】上的实战案例,拆解一个经典的高频面试题:并发场景下的状态同步。 这问题看似简单,实则坑多。… · 2026/9/23 14:55:25

C++ MFC跳棋游戏源码解析:从VC6工程到现代编译器的避坑指南
C++ MFC跳棋游戏源码解析:从VC6工程到现代编译器的避坑指南

简介:跳棋游戏源码压缩包基于 VC/MFC 实现经典中国跳棋玩法,面向正在学习 Windows 桌面开发、游戏逻辑与 AI 算法的编程爱好者。包内共 43 个文件,涵盖 .cpp 源代码、.h 头文件、.rc 资源脚本,以及 .bmp 棋盘素材、.ico 图标、.cu… · 2026/9/23 14:55:17

Vega 参数类型(Parameter Types)权威参考:从 Literal 到 Value Reference 的完整类型体系
Vega 参数类型(Parameter Types)权威参考:从 Literal 到 Value Reference 的完整类型体系

数据可视化 【免费下载链接】vega A visualization grammar. 项目地址: https://gitcode.com/gh_mirrors/ve/vega 点击查看 免费下载 本文是 Vega 可视化语法规范(vega 仓库)中 docs/docs/types.md 的深度技术指南,系统梳理 Vega… · 2026/9/23 14:55:01

NBA 15-18赛季数据包实战:Python数据分析与Elo等级分计算
NBA 15-18赛季数据包实战:Python数据分析与Elo等级分计算

简介:这份资源面向具备一定Python基础、希望上手真实数据分析项目的高校学生与数据爱好者,围绕NBA比赛数据展开,提供从数据采集到可视化呈现的完整实践素材。压缩包共14个文件,约245KB,以11个CSV数据表为主&#xff0c… · 2026/9/23 14:54:54

搞定httpwww:3个性能优化点让你代码跑通
搞定httpwww:3个性能优化点让你代码跑通

搞定httpwww:3个性能优化点让你代码跑通 复制来的 httpwww 相关代码,是不是经常报错?别急,这通常是环境配置或底层原理没搞懂。 面试中被问到 HTTP 性能优化,很多人只会背“加缓存”,其实细节才决定成败。 今天拆解… · 2026/9/23 14:54:48

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码