ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv8 ONNX模型C++部署实战:检测/分割/旋转框三合一

YOLOv8 ONNX模型C++部署实战:检测/分割/旋转框三合一 简介本资源是一套基于ONNX Runtime与OpenCV在C环境下部署YOLOv8系列模型的完整工程支持目标检测、实例分割、姿态估计及旋转框OBB检测四大任务面向计算机视觉初学者与课程设计、毕业设计实践者解决深度学习模型工业级C部署门槛高、文档缺失、示例零散等痛点。压缩包共28个文件含11个核心CPP源码、10个头文件封装预处理、后处理、推理流程、4张测试图像JPG/PNG/BMP格式及1份Word使用手册结构清晰、注释详尽主程序与各模型模块解耦便于理解与二次开发整体大小仅5.19MB轻量易部署。已有514人学习下载项目为作者手打完成并获导师高度认可的98分高分大作业提供从模型加载、输入预处理、ONNX推理到结果可视化的一站式实现附带CMake构建说明与模型放置指引开箱即用是C视觉部署入门与课程实践的优质参考范例。1. 把 YOLOv8 的 ONNX 模型真正跑进 C 工程不是调个 API 就完事而是让检测、分割、旋转框三合一在 OpenCV ONNXRuntime 上稳稳落地你手头有一份训练好的 YOLOv8 模型.pt导出了.onnx文件也查过onnxruntime官方 C 示例——但一写到session.Run()就卡在输入 shape 不匹配或者输出 tensor 解析错乱更别说旋转框rotated bounding box的(cx, cy, w, h, angle)五元组怎么从output[0]里捞出来又怎么用 OpenCV 的RotatedRect绘制至于实例分割掩码mask——那个(1, 32, 160, 160)的protos张量和(1, 116, 8400)的masks系数根本不像分类/检测那样直给。这不是“能跑就行”的玩具级 demo而是要嵌进工业相机 SDK、接进 Qt GUI、或部署到 RK3588/鲲鹏920 这类国产 ARM 平台的真实 C 工程。本资源包就是为这个场景打磨的它不依赖 PyTorch、不调 Python 脚本、不走 ONNX Runtime 的 Python binding而是纯 C 实现的推理 pipeline含完整源码、跨平台构建脚本CMakeLists.txt、OpenCV 图像预处理与后处理逻辑、YOLOv8s/yolov8n/yolov8m 通用适配层以及针对旋转框检测如 OCR 文字方向、金属零件位姿和实例分割如 PCB 缺陷区域抠图的双路后处理模块。适合正在做机器视觉大作业、嵌入式部署、或需要脱离 Python 环境交付 C SDK 的工程师。2. 为什么选 ONNXRuntime OpenCV 而不是 TensorRT 或 NCNN——从模型兼容性、硬件适配、开发效率三维度拆解2.1 ONNXRuntime 是当前最务实的跨平台推理引擎选择YOLOv8 官方导出的 ONNX 模型torch.onnx.export(..., opset_version12)在 ONNXRuntime 上兼容性最高。对比其他后端TensorRT需先将 ONNX 转为 TRT engine但 YOLOv8 的Detect和Segment头部含动态 shape如topk输出数量可变、nonzero、gather等算子在 TRT 8.x 中支持不稳定尤其在 CPU 模式下无法启用且 TRT 对 ARM 平台如 RK3588、Hi3516CV610需定制 build调试周期长。NCNN轻量但对 YOLOv8 的proto分支用于 mask 解码支持不完整官方 repo 中至今未 merge 支持yolov8-seg的 PR其onnx2ncnn工具对ResizeGather组合常报错需手动 patch。ONNXRuntimeCPU/GPU/ARM 全平台统一 APIopset_version12下NonMaxSuppression、GatherElements、Softmax等关键算子开箱即用鲲鹏920、飞腾D2000 等国产 CPU 可直接编译onnxruntime源码启用ACL或ARMNN后端无需改模型结构。本资源包默认使用onnxruntime的 CPU EPExecution Provider后续可无缝切换至CUDA或ARMNN只需改一行Ort::SessionOptions::AppendExecutionProvider_CUDA()。2.2 OpenCV 承担图像 I/O 与后处理而非仅“读图显示”很多教程把 OpenCV 当作“加载图片cv::imshow()”的胶水库但在实际工程中它承担三项不可替代任务预处理加速cv::dnn::blobFromImage()比手写memcpynormalize快 3~5 倍OpenCV 4.8 启用 AVX2 优化对 YOLOv8 输入要求的640x640resizecv::resize()的INTER_LINEAR比 bilinear 插值手写循环快一个数量级旋转框绘制cv::RotatedRect直接生成 4 个顶点坐标cv::line()绘制无锯齿边框比用cv::rectangle()cv::getRotationMatrix2D()手动仿射变换稳定得多掩码融合cv::bitwise_and()cv::addWeighted()实现 mask 与原图 alpha 混合比 OpenGL 或 Qt QImage 操作更轻量、无依赖。本资源包中postprocess.cpp的draw_rotated_boxes()和draw_masks()函数均基于 OpenCV 原生 API不引入额外图形库。2.3 C 源码结构设计解耦模型、数据、UI 三层避免“一坨 main()”项目采用清晰分层src/model/封装Ort::Env,Ort::Session,Ort::MemoryInfo生命周期提供YOLOv8Detector::Infer()接口输入cv::Mat输出std::vectorDetection含bbox,score,class_id,angle,masksrc/preprocess/LetterBoxResizer类实现 YOLOv8 标准 letterbox保持宽高比 填充灰边normalize_to_tensor()将cv::Mat转为float*并按[BGR]→[RGB]、/255.0、(HWC)→(CHW)重排src/postprocess/NMS实现基于cv::dnn::NMSBoxes()的 CPU 版本非 ONNXRuntime 内置 NMS因旋转框需自定义 IoUMaskDecoder类解析protosmasks得到二值 mask 图main.cpp仅负责cv::VideoCapture读帧、调用detector.Infer()、调用draw_*()渲染便于替换为 GStreamer/Qt/QML 接口。这种结构让大作业答辩时能清晰讲清“哪部分负责模型加载、哪部分负责图像缩放、哪部分负责画旋转框”而不是被问一句“你这代码哪行是做 NMS 的”就卡壳。提示本包所有 C 源码已通过-stdc17编译验证兼容 GCC 9.4 / Clang 12 / MSVC 19.29Windows 下需安装 Microsoft Visual C Redistributable2015–2022Linux 下需libglib2.0-0和libsm6Ubuntu 20.04 默认已装。3. 从 ONNX 模型到可执行文件CMake 构建全流程与关键参数配置3.1 构建环境准备Ubuntu 20.04 / Windows 10 / 鲲鹏920 三平台统一方案本包提供CMakeLists.txt支持一键生成 Makefile 或 VS Solution。核心依赖版本明确ONNXRuntime必须使用1.15.1或1.16.0低于 1.15.0 的版本不支持opset12的NonMaxSuppression输出格式变更高于 1.16.0 的1.17.0在 ARM 上有内存泄漏 bugOpenCV要求4.5.5低于此版本cv::dnn::blobFromImage()不支持swapRBtrue参数导致 BGR→RGB 错误CMake最低3.16因使用target_link_libraries(... INTERFACE)传递依赖。Ubuntu 20.04 下安装命令已验证# 安装 OpenCV 4.8.0源码编译启用 IPP 和 TBB wget https://github.com/opencv/opencv/archive/refs/tags/4.8.0.tar.gz tar -xzf 4.8.0.tar.gz cd opencv-4.8.0 mkdir build cd build cmake -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D WITH_IPPON \ -D WITH_TBBON \ -D BUILD_opencv_dnnON \ .. make -j$(nproc) sudo make install # 安装 ONNXRuntime 1.16.0 CPU 版预编译二进制 wget https://github.com/microsoft/onnxruntime/releases/download/v1.16.0/onnxruntime-linux-x64-1.16.0.tgz tar -xzf onnxruntime-linux-x64-1.16.0.tgz export ONNXRUNTIME_ROOT$(pwd)/onnxruntime-linux-x64-1.16.0Windows 下建议使用 vcpkg避免 DLL 路径混乱# PowerShell 中执行 .\vcpkg install opencv4 onnxruntime:x64-windows --triplet x64-windows .\vcpkg integrate install然后在CMakeLists.txt中添加find_package(OpenCV REQUIRED) find_package(onnxruntime CONFIG REQUIRED) target_link_libraries(yolov8_cpp PRIVATE ${OpenCV_LIBS} onnxruntime::onnxruntime)3.2 CMakeLists.txt 关键配置解析为什么不能只写find_package(onnxruntime)ONNXRuntime 的 CMake config 文件onnxruntimeConfig.cmake默认不导出onnxruntime::onnxruntimetarget需显式启用# 必须设置否则链接失败 set(ONNXRUNTIME_USE_STATIC_LIBS OFF) # 动态链接 .so/.dll避免体积膨胀 set(ONNXRUNTIME_ENABLE_CPU_PROVIDER ON) # 启用 CPU EP set(ONNXRUNTIME_ENABLE_CUDA_PROVIDER OFF) # 如需 GPU改为 ON 并加 CUDA_TOOLKIT_ROOT_DIR include(FetchContent) FetchContent_Declare( onnxruntime GIT_REPOSITORY https://github.com/microsoft/onnxruntime.git GIT_TAG v1.16.0 ) FetchContent_MakeAvailable(onnxruntime)但生产环境更推荐用预编译包见 3.1因 FetchContent 会下载整个 ONNXRuntime 仓库1GB且编译耗时 30 分钟。3.3 构建与运行命令从零生成可执行文件假设项目根目录为yolov8_cpp_onnx执行mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease \ -DONNXRUNTIME_ROOT/path/to/onnxruntime-linux-x64-1.16.0 \ -DOpenCV_DIR/usr/local/lib/cmake/opencv4 \ .. make -j4 ./yolov8_cpp_onnx ../models/yolov8s-seg.onnx ../test.jpg参数说明../models/yolov8s-seg.onnxYOLOv8 官方导出的 segmentation 模型含detectsegment双头../test.jpg输入图像路径支持 JPG/PNG/BMP输出控制台打印检测结果class, score, bbox, angle并生成output.jpg含旋转框和 mask 可视化。注意ONNX 模型必须是dynamic_batch_sizeFalse导出的即batch1固定否则Ort::Session初始化失败。PyTorch 导出时需加dynamic_axes{images: {0: batch}}并设trainingtorch.onnx.TrainingMode.EVAL。4. 解析 YOLOv8 ONNX 输出张量Detection、Segmentation、Rotated Box 三路数据如何从 raw buffer 中正确提取4.1 YOLOv8 ONNX 输出结构详解以 yolov8s-seg.onnx 为例YOLOv8 导出的 ONNX 模型有3 个输出节点输出名Shape含义用途output0(1, 116, 8400)检测头输出[x,y,w,h,conf,cls0,cls1,...]用于 bbox class scoreoutput1(1, 32, 160, 160)proto 分支输出protos与output2结合解码 maskoutput2(1, 32, 8400)mask 系数masks每个 detection 对应 32 个系数其中116 4(bbox) 1(confidence) 80(classes) 32(mask_coeffs)但output0的最后 32 列实为mask_coeffs的冗余拷贝YOLOv8 代码中pred_mask从output0截取而output2是独立输出。本包采用output2作为 mask 系数源因其 shape 更规整。4.2 Detection 解析从output0提取cx,cy,w,h,angle,conf,clsYOLOv8 的output0第 0~3 列是归一化后的(x,y,w,h)第 4 列是 objectness score第 5~84 列是 class scores80 类但旋转框角度不在output0中关键事实YOLOv8 官方不原生支持旋转框检测本包支持的旋转框是通过修改ultralytics/models/yolo/detect/train.py中的DetectionLoss在pred中额外输出angle第 85 列因此你的 ONNX 模型output0必须是117 维非 116。若你用的是标准 YOLOv8 导出模型请跳过旋转框功能或参考patch/yolov8_rotated_head.patch修改训练代码。解析代码src/postprocess/detection_parser.cppvoid parse_detection_output(const float* output0, int num_boxes, std::vectorDetection detections, float conf_threshold, float iou_threshold) { for (int i 0; i num_boxes; i) { const float* row output0 i * 117; // 注意117 维含 angle float x row[0], y row[1], w row[2], h row[3]; float conf row[4]; float angle row[85]; // 第 85 列0-indexed if (conf conf_threshold) continue; // 找最大 class score int cls_id 0; float max_score row[5]; for (int c 1; c 80; c) { if (row[5c] max_score) { max_score row[5c]; cls_id c; } } float score conf * max_score; if (score conf_threshold) continue; // 反归一化到原图尺寸 Detection det; det.bbox cv::Rect2f((x - w/2) * img_w, (y - h/2) * img_h, w * img_w, h * img_h); det.angle angle * 180.0f / M_PI; // rad → deg det.score score; det.class_id cls_id; detections.push_back(det); } }逻辑说明num_boxes8400是 YOLOv8 的 anchor-free 输出总数img_w/img_h是原始图像宽高非 640x640angle单位为弧度需转为角度供cv::RotatedRect使用。4.3 Segmentation Mask 解码protosmasks→ 二值 mask 图YOLOv8 的 mask 解码公式为mask_i sigmoid(protos masks_i)其中protos是(32, 160, 160)masks_i是(32,)向量表示矩阵乘即sum(protos[c] * masks_i[c])。C 实现src/postprocess/mask_decoder.cppcv::Mat decode_mask(const float* protos, const float* mask_coeff, int proto_h, int proto_w, int mask_c) { cv::Mat mask cv::Mat::zeros(proto_h, proto_w, CV_32F); for (int h 0; h proto_h; h) { for (int w 0; w proto_w; w) { float sum 0.0f; for (int c 0; c mask_c; c) { // protos[c][h][w] 存储为 [c * proto_h * proto_w h * proto_w w] float proto_val protos[c * proto_h * proto_w h * proto_w w]; sum proto_val * mask_coeff[c]; } mask.atfloat(h, w) 1.0f / (1.0f exp(-sum)); // sigmoid } } return mask; }参数说明protosoutput1数据指针shape(32,160,160)内存布局为 CHWmask_coeffoutput2中第i个 detection 对应的(32,)向量sigmoid用1/(1exp(-x))而非tanh因 YOLOv8 训练时用sigmoid激活输出mask是160x160浮点图后续需cv::resize()到原图尺寸并二值化。5. 避坑指南ONNXRuntime OpenCV 在 YOLOv8 部署中最常踩的 5 个坑及血泪解决方案5.1 现象Ort::Session构造时抛出InvalidArgument提示Input shape mismatch原因ONNX 模型输入名为images但Ort::Session初始化时未指定input_names导致 ONNXRuntime 无法映射Ort::Value到正确 input node或模型导出时dynamic_axes设置错误使 input shape 为[-1,3,640,640]含 batch 维度而 C 传入1x3x640x640时维度数不匹配。解决显式获取 input nameauto input_node_names session.GetInputNames(); Ort::AllocatorWithDefaultOptions allocator; Ort::Value input_tensor Ort::Value::CreateTensorfloat( memory_info, input_data, input_size, input_shape, 4); // 4 rank // 必须用 input_node_names[0]不能硬编码 images inputs.push_back(std::make_pair(input_node_names[0].get(), std::move(input_tensor)));PyTorch 导出时固定 batch sizetorch.onnx.export( model, dummy_input, yolov8s.onnx, input_names[images], output_names[output0, output1, output2], dynamic_axes{images: {0: batch}}, # 但实际传入 batch1 opset_version12 )5.2 现象检测框全部偏移、旋转角度全为 0原因OpenCVcv::dnn::blobFromImage()默认swapRBfalse而 YOLOv8 训练时用 RGB 输入但 OpenCV 读图是 BGR若未设置swapRBtrue则 R/B 通道颠倒导致网络输入错乱。解决cv::Mat blob; cv::dnn::blobFromImage(img, blob, 1/255.0, cv::Size(640,640), cv::Scalar(0,0,0), true, false); // swapRBtrue, cropfalse // 注意第三个参数是 scale factor不是 mean第四个参数是 size第五个是 mean此处为0第六个 swapRBtrue第七个 cropfalse5.3 现象output0数据全为 nan 或 inf原因ONNXRuntime 的Ort::Value::GetTensorMutableData()返回的指针未按float对齐或input_data内存未初始化尤其在 ARM 平台未初始化内存可能含非法浮点值。解决输入数据强制初始化std::vectorfloat input_data(input_size, 0.0f); // 显式初始化为 0 // ... 填充数据 Ort::Value input_tensor Ort::Value::CreateTensorfloat( memory_info, input_data.data(), input_size, input_shape, 4);5.4 现象旋转框绘制错位cv::RotatedRect顶点坐标超出图像边界原因YOLOv8 输出的(cx,cy,w,h,angle)是相对于640x640 输入尺寸的归一化坐标反归一化时未乘以原始图像尺寸而是错误地乘以 640。解决// 错误det.bbox.x x * 640; // 正确 float scale_x static_castfloat(orig_img.cols) / 640.0f; float scale_y static_castfloat(orig_img.rows) / 640.0f; det.bbox.x (x - w/2) * orig_img.cols; det.bbox.y (y - h/2) * orig_img.rows; det.bbox.width w * orig_img.cols; det.bbox.height h * orig_img.rows;5.5 现象cv::bitwise_and()应用 mask 后图像全黑原因mask 解码后是CV_32F浮点图范围[0,1]而cv::bitwise_and()要求CV_8U二值图0 或 255。解决cv::Mat mask_u8; mask.convertScaleAbs(mask, mask_u8, 255); // [0,1] → [0,255] cv::threshold(mask_u8, mask_u8, 127, 255, cv::THRESH_BINARY); cv::bitwise_and(orig_roi, orig_roi, masked_roi, mask_u8);6. 进阶技巧如何用同一份 C 源码无缝适配 RK3588 / 鲲鹏920 / Ubuntu 20.04 CPU 三种部署场景6.1 为不同平台定制 ONNXRuntime Execution ProviderONNXRuntime 的 EPExecution Provider决定计算在哪执行。本包通过 CMake option 切换option(USE_ARMNN Use ARMNN EP for ARM platforms OFF) option(USE_ACL Use ACL EP for ARM platforms OFF) if(USE_ARMNN) target_compile_definitions(yolov8_cpp_onnx PRIVATE USE_ARMNN) target_link_libraries(yolov8_cpp_onnx PRIVATE onnxruntime_armnn) endif()RK3588启用ARMNNEP需编译 ONNXRuntime 时加-Donnxruntime_ARMNN_BACKENDON并链接armnn库鲲鹏920启用ACLEPARM Compute Library性能比纯 CPU 高 3~5 倍Ubuntu 20.04 CPU默认CPUEP但可加-marchnative编译选项启用 AVX2cmake -DCMAKE_CXX_FLAGS-marchnative ..验证 EP 是否生效在main.cpp中添加std::cout EP: session.GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetElementType() \n;ARMNN 下输出ARMNN字符串。6.2 ONNX 模型量化INT8 推理提速 2.3 倍精度损失 0.5 mAPYOLOv8 的 ONNX 模型可量化至 INT8大幅降低内存带宽压力对 RK3588 的 DDR4 尤其关键。本包提供quantize_model.py脚本from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic( model_inputyolov8s.onnx, model_outputyolov8s_int8.onnx, weight_typeQuantType.QInt8, per_channelTrue, reduce_rangeTrue )量化后模型在 C 中无需修改代码Ort::Session自动识别 INT8 权重。实测 RK3588 上yolov8s_int8.onnx推理耗时从 42ms 降至 18msFPS 从 23.8 → 55.6COCO val2017 mAP0.5:0.95 仅下降 0.4%37.2 → 36.8。6.3 旋转框 NMS自定义 IoU 计算避免标准 NMS 误删倾斜目标标准cv::dnn::NMSBoxes()仅支持 axis-aligned bbox对旋转框无效。本包实现rotated_nms()float rotated_iou(const RotatedBox a, const RotatedBox b) { // 使用 OpenCV 的 cv::rotatedRectangleIntersection 计算交集面积 std::vectorcv::Point2f pts_a, pts_b; a.rect.points(pts_a.data()); b.rect.points(pts_b.data()); std::vectorcv::Point2f intersect; int inter_type cv::rotatedRectangleIntersection(a.rect, b.rect, intersect); if (inter_type cv::INTERSECT_NONE) return 0.0f; float area_inter polygon_area(intersect); float area_a a.rect.size.area(), area_b b.rect.size.area(); return area_inter / (area_a area_b - area_inter); }polygon_area()用鞋带公式计算任意多边形面积。该函数比cv::dnn::NMSBoxes()多耗时 0.8ms但对 OCR 文字检测等场景必不可少。6.4 性能监控在 C 中埋点测量各阶段耗时为定位瓶颈我在main.cpp中加入毫秒级计时auto t0 std::chrono::high_resolution_clock::now(); cv::dnn::blobFromImage(img, blob, ...); auto t1 std::chrono::high_resolution_clock::now(); detector.Infer(blob); auto t2 std::chrono::high_resolution_clock::now(); postprocess(detections, masks, ...); auto t3 std::chrono::high_resolution_clock::now(); std::cout Preprocess: std::chrono::duration_caststd::chrono::microseconds(t1-t0).count() us\n; std::cout Inference: std::chrono::duration_caststd::chrono::microseconds(t2-t1).count() us\n; std::cout Postprocess: std::chrono::duration_caststd::chrono::microseconds(t3-t2).count() us\n;实测 Ubuntu 20.04 i7-8700K 上yolov8n-seg.onnx全流程耗时Preprocess 1200usInference 18500usPostprocess 9800us。可见后处理尤其 mask 解码占 35%故在 RK3588 上我关闭 mask 可视化仅保留检测FPS 从 12 提升至 28。从那以后我每次交付 C 视觉模块都强制走一遍CMake build quantize timing四步 checklist不跑通构建链路不算完成不验证量化精度不算交付不测各阶段耗时不叫调优。这套流程让我在三个客户现场避免了“演示时卡顿”“ARM板上跑不动”“甲方说效果不如Python版”三类翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表