资讯详情

YOLOv8 LibTorch C++ 推理实战:从 TorchScript 导出到 C++ 端到端部署

📅 2026/9/16 17:12:26 | 华诺云谱 👁 阅读
YOLOv8 LibTorch C++ 推理实战:从 TorchScript 导出到 C++ 端到端部署
YOLOv8 LibTorch C 推理实战从 TorchScript 导出到 C 端到端部署【免费下载链接】yolov10YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/yo/yolov10导读本篇文章基于当前仓库中examples/YOLOv8-LibTorch-CPP-Inference示例完整讲解如何绕过 Python 运行时直接在 C 工程中借助 PyTorch 官方 C 库 LibTorch 加载并运行 YOLOv8 检测模型。你将掌握完整的部署链路用yolo export将权重导出为 TorchScript、使用 CMake 搭建 LibTorch OpenCV 编译环境、实现 letterbox 预处理、坐标格式转换、NMS 后处理与结果映射最终在终端输出检测框坐标、置信度与类别。这套流程同样适用于当前仓库主打的 YOLOv10 等后续模型导出与加载机制一致可作为将 YOLO 系列模型嵌入 C 服务端或嵌入式应用的标准范式。一、示例概览与核心思路examples/YOLOv8-LibTorch-CPP-Inference目录下的示例演示了使用LibTorch API 在 C 中对 YOLOv8 模型执行推理的完整流程。与 Python 端ultralytics库相比C 部署具有更低的推理开销、更少的运行时依赖也更适合集成进既有 C 代码库或边缘设备。示例的组成非常精简main.cc推理主程序包含图像预处理、模型加载、推理、后处理与结果输出全部逻辑CMakeLists.txt基于 CMake 的构建脚本负责链接 LibTorch 与 OpenCVREADME.md环境依赖、编译与导出说明。从代码结构看整个推理链路刻意与 ultralytics/engine/predictor.py 的 Python 实现保持一致letterbox 缩放、xyxy/xywh转换、NMS、坐标还原因此理解本示例等同于掌握 YOLOv8 Python 推理的全部关键环节方便在两套语言间互相印证与迁移。二、环境依赖与版本要求构建该示例需要以下依赖版本要求见 README 原文依赖项版本要求OpenCV 4.0.0C Standard 17CMake 3.18LibTorch 1.12.1其中 LibTorch 即 PyTorch 的 C 发行版可从官方渠道按目标平台CPU / CUDA下载解压OpenCV 需提供 cmake 配置包OpenCVConfig.cmake。若使用 CUDA 版本的 LibTorch还需要匹配的 NVIDIA 驱动与 CUDA 工具链。示例默认在 CPU/GPU 上均可运行main.cc中会自动探测设备torch::Device device(torch::cuda::is_available() ? torch::kCUDA : torch::kCPU);即检测到 CUDA 时使用 GPU 推理否则回退到 CPU无需改动代码。三、导出 TorchScript 模型C 侧无法直接加载.pt权重需要先把模型导出为TorchScript格式。README 给出的导出命令为yolo export modelyolov8s.pt imgsz640 formattorchscript命令执行后会在当前目录生成yolov8s.torchscript文件即后续 C 程序加载的对象。官方文档 Model Export with Ultralytics YOLO 中的导出格式表格显示TorchScript 格式支持imgsz与optimize两个导出参数。几点值得展开的细节imgsz640输入图像尺寸默认即 640也可以传[高, 宽]列表使用非正方形输入default.yaml 中注明imgsz在 predict 与 export 模式下支持list[w,h]。导出时的imgsz必须与 C 侧 letterbox 的目标尺寸一致本示例固定为{640, 640}formattorchscript由于导出格式的默认值就是torchscript见 export.md该参数可省略但显式写出更清晰optimize若需要移动端部署可加optimizeTrue导出时会走optimize_for_mobile并保存为 Lite Interpreter 格式。从源码层面看TorchScript 导出实现在 exporter.py 的export_torchscript方法中ts torch.jit.trace(self.model, self.im, strictFalse) extra_files {config.txt: json.dumps(self.metadata)} if self.args.optimize: # https://pytorch.org/tutorials/recipes/mobile_interpreter.html from torch.utils.mobile_optimizer import optimize_for_mobile optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_filesextra_files) else: ts.save(str(f), _extra_filesextra_files)可见导出采用torch.jit.tracetrace 模式而非 scripting因此输入张量的形状在 trace 时被固定同时模型元数据类名列表等会被写入config.txt的 extra file 中这与 autobackend.py 中 Python 侧torch.jit.load(w, _extra_filesextra_files)的加载方式相互对应。这也是为什么 C 侧需要自行准备 80 个 COCO 类名数组见下文——trace 产物并不强制携带可读的类别信息。另外需要注意导出命令中的yolov8s.pt是预训练权重名。当前仓库为 YOLOv10 项目若想用本示例加载仓库内的 YOLOv10 模型可将yolov8s.pt替换为对应的yolov10s.pt其余命令与 C 侧流程完全一致。四、CMake 构建工程构建脚本 CMakeLists.txt 的核心逻辑如下cmake_minimum_required(VERSION 3.18 FATAL_ERROR) project(yolov8_libtorch_example) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # -------------- OpenCV -------------- set(OpenCV_DIR /path/to/opencv/lib/cmake/opencv4) find_package(OpenCV REQUIRED) # -------------- libtorch -------------- list(APPEND CMAKE_PREFIX_PATH /path/to/libtorch) set(Torch_DIR /path/to/libtorch/share/cmake/Torch) find_package(Torch REQUIRED) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}) add_executable(yolov8_libtorch_inference ${CMAKE_CURRENT_SOURCE_DIR}/main.cc) target_link_libraries(yolov8_libtorch_inference ${TORCH_LIBRARIES} ${OpenCV_LIBS}) set_property(TARGET yolov8_libtorch_inference PROPERTY CXX_STANDARD 17)要点说明版本门槛cmake_minimum_required(VERSION 3.18 FATAL_ERROR)对应 README 中的 CMake 3.18 要求CXX_STANDARD 17对应 C17 要求路径适配OpenCV_DIR、CMAKE_PREFIX_PATH、Torch_DIR三处都是占位路径/path/to/...必须替换为本机实际安装目录。其中CMAKE_PREFIX_PATH追加 LibTorch 根目录后find_package(Torch REQUIRED)会自动发现 Torch 的 cmake 配置编译标志必须拼接TORCH_CXX_FLAGS否则可能出现 ABI 不匹配例如_GLIBCXX_USE_CXX11_ABI不一致导致的链接错误Windows 注意点脚本内注释提到在 Windows/MSVC 下建议把 LibTorch 的 DLL 拷贝到可执行文件目录以避免内存错误对应 PyTorch issue #25457 中描述的 DLL 加载问题# if (MSVC) # file(GLOB TORCH_DLLS ${TORCH_INSTALL_PREFIX}/lib/*.dll) # add_custom_command(TARGET yolov8_libtorch_example POST_BUILD # COMMAND ${CMAKE_COMMAND} -E copy_if_different # ${TORCH_DLLS} $TARGET_FILE_DIR:yolov8_libtorch_example) # endif (MSVC)可执行文件命名最终生成的可执行文件是yolov8_libtorch_inference目标名与源文件main.cc绑定。完整编译与运行步骤README 原文流程git clone ultralytics cd ultralytics pip install . cd examples/YOLOv8-LibTorch-CPP-Inference mkdir build cd build cmake .. make ./yolov8_libtorch_inference其中pip install .用于安装 ultralytics 包以提供yolo命令完成模型导出。注意构建前必须先在main.cc中把model_path与图片路径替换为实际路径见第六节。五、图像预处理letterbox 与张量转换main.cc的预处理严格复刻了 Python 侧的 letterbox 逻辑包含两个函数generate_scale计算等比缩放系数float generate_scale(cv::Mat image, const std::vectorint target_size) { int origin_w image.cols; int origin_h image.rows; int target_h target_size[0]; int target_w target_size[1]; float ratio_h static_castfloat(target_h) / static_castfloat(origin_h); float ratio_w static_castfloat(target_w) / static_castfloat(origin_w); float resize_scale std::min(ratio_h, ratio_w); return resize_scale; }取宽、高缩放比例中的较小值保证图像等比缩放后完整放入目标尺寸、不发生形变。letterbox执行“缩放 居中填充”float letterbox(cv::Mat input_image, cv::Mat output_image, const std::vectorint target_size) { // 尺寸已匹配则直接返回 if (input_image.cols target_size[1] input_image.rows target_size[0]) { if (input_image.data output_image.data) { return 1.; } else { output_image input_image.clone(); return 1.; } } float resize_scale generate_scale(input_image, target_size); int new_shape_w std::round(input_image.cols * resize_scale); int new_shape_h std::round(input_image.rows * resize_scale); float padw (target_size[1] - new_shape_w) / 2.; float padh (target_size[0] - new_shape_h) / 2.; int top std::round(padh - 0.1); int bottom std::round(padh 0.1); int left std::round(padw - 0.1); int right std::round(padw 0.1); cv::resize(input_image, output_image, cv::Size(new_shape_w, new_shape_h), 0, 0, cv::INTER_AREA); cv::copyMakeBorder(output_image, output_image, top, bottom, left, right, cv::BORDER_CONSTANT, cv::Scalar(114.)); return resize_scale; }实现细节与 Python 端一致缩放使用INTER_AREA插值缩小图像时抗混叠效果更好填充值固定为114灰色与 YOLOv8 训练时使用的填充值相同保证推理分布一致上下、左右填充各做±0.1取整修正用于消除浮点误差导致的 1 像素偏差返回值resize_scale后续用于把检测框坐标还原到原图。预处理后main()中把cv::Mat转成模型输入张量torch::Tensor image_tensor torch::from_blob(input_image.data, {input_image.rows, input_image.cols, 3}, torch::kByte).to(device); image_tensor image_tensor.toType(torch::kFloat32).div(255); // 归一化到 [0, 1] image_tensor image_tensor.permute({2, 0, 1}); // HWC - CHW image_tensor image_tensor.unsqueeze(0); // 增加 batch 维 - [1, 3, 640, 640] std::vectortorch::jit::IValue inputs {image_tensor};注意torch::from_blob直接复用 OpenCV 的连续内存不产生拷贝因此要求input_image是contiguous的letterbox 的输出满足该条件。六、模型加载与推理main()中模型加载与推理的关键代码// Load the model (e.g. yolov8s.torchscript) std::string model_path /path/to/yolov8s.torchscript; torch::jit::script::Module yolo_model; yolo_model torch::jit::load(model_path); yolo_model.eval(); yolo_model.to(device, torch::kFloat32); // Load image and preprocess cv::Mat image cv::imread(/path/to/bus.jpg); cv::Mat input_image; letterbox(image, input_image, {640, 640}); // ...张量转换略 // Inference torch::Tensor output yolo_model.forward(inputs).toTensor().cpu();要点torch::jit::load加载 TorchScript 文件后需调用eval()切换到推理模式再.to(device, torch::kFloat32)明确设备与精度运行前必须把model_path和图片路径两处/path/to/...占位符改为真实路径前向输出形状为[1, 84, 8400]检测模型第 1 维为 batch84 4框坐标 xywh 80COCO 类别数8400 640×640 特征图三个尺度下 anchor 点总数80×80 40×40 20×20推理结束后cpu()把张量搬回主机内存便于后续后处理与逐元素访问整个流程被try/catch (const c10::Error e)包裹模型加载失败等 LibTorch 异常会打印到标准输出便于排查。类名数组在main()中以硬编码方式给出 80 个 COCO 类别person、bicycle、car……直至toothbrush对应 COCO 数据集标准类别顺序后处理时用检测结果中的类别索引取出字符串。若使用自定义类别模型需同步替换该数组。七、后处理从原始输出到最终检测框模型原始输出是 8400 个 anchor 上的(cx, cy, w, h, cls_0..cls_79)预测必须经过“坐标转换 → 置信度筛选 → NMS → 坐标还原”四步才能得到最终框。main.cc用四个函数实现了完整的 YOLOv8 后处理逻辑与 Python 侧non_max_suppression一致。1. 坐标格式转换xywh2xyxy把中心宽高格式转成左上右下角点格式torch::Tensor xywh2xyxy(const torch::Tensor x) { auto y torch::empty_like(x); auto dw x.index({..., 2}).div(2); auto dh x.index({..., 3}).div(2); y.index_put_({..., 0}, x.index({..., 0}) - dw); y.index_put_({..., 1}, x.index({..., 1}) - dh); y.index_put_({..., 2}, x.index({..., 0}) dw); y.index_put_({..., 3}, x.index({..., 1}) dh); return y; }xyxy2xywh为反向转换示例中一并提供备用。index_put_与index({..., n})分别对应 Python 的索引赋值与切片取列。2. 置信度筛选与类别融合non_max_supperession函数名沿用了示例源码中的拼写即 NMS 后处理主入口中auto xc prediction.index({Slice(), Slice(4, mi)}).amax(1) conf_thres; // 找出所有类别中最大得分 阈值的 anchor ... auto [conf, j] cls.max(1, true); // 取每个 anchor 的最高类别得分及索引 x torch::cat({box, conf, j.toType(torch::kFloat), mask}, 1); // 重组为 [box, conf, cls_idx, mask] x x.index({conf.view(-1) conf_thres}); // 二次按置信度过滤最终每行重组为[x1, y1, x2, y2, conf, class_idx]。其中nc prediction.size(1) - 4计算类别数检测模型为 80nm为掩码维度分割模型为 32检测模型为 0这段代码同时对检测与分割模型做了兼容默认阈值conf_thres 0.25、iou_thres 0.45、max_det 300与 Python 端推理默认值保持一致default.yaml 中conf默认 0.25、iou默认 0.7、max_det默认 300本示例将 IoU 收紧为 0.45可按需调整。3. NMS 实现nms函数是自实现的 IoU 阈值抑制源码注释标明参考了 PyTorch Vision 的nms_kernel.cpp// Reference: https://github.com/pytorch/vision/blob/main/torchvision/csrc/ops/cpu/nms_kernel.cpp torch::Tensor nms(const torch::Tensor bboxes, const torch::Tensor scores, float iou_threshold) { ... }算法为经典的贪心 NMS按得分降序遍历保留当前最高分框抑制与其 IoU 超过阈值的其余框。实现要点先按分数做稳定降序排序得到order用suppressed_tuint8标记被抑制的框、keep_t记录保留索引通过data_ptrT()直接操作底层指针逐元素计算交并比ovr inter / (iarea areas[j] - inter)避免张量级运算开销这也是 C 实现相对 Python 的主要性能优势。4. 类别偏移与坐标还原NMS 前有一个容易忽略的细节auto c x.index({Slice(), Slice{5, 6}}) * 7680; // 类别索引 × 7680 auto boxes x.index({Slice(), Slice(None, 4)}) c;即把类别索引乘以 7680略大于 640×640409600 的上限…实际为偏移量加到坐标上使不同类别的框在坐标空间上分离从而对不同类别分别做 NMS避免同类别的重复抑制逻辑错乱——这是 YOLOv8 Python 实现中“class-aware NMS”技巧的移植。NMS 之后scale_boxes负责把 640×640 输入图上的框坐标还原回原始图像auto gain (std::min)((float)img1_shape[0] / img0_shape[0], (float)img1_shape[1] / img0_shape[1]); auto pad0 std::round((float)(img1_shape[1] - img0_shape[1] * gain) / 2. - 0.1); auto pad1 std::round((float)(img1_shape[0] - img0_shape[0] * gain) / 2. - 0.1); boxes.index_put_({..., 0}, boxes.index({..., 0}) - pad0); // ...依次减去 pad 并除以 gain逻辑与预处理时的 letterbox 参数严格对应先减去左右/上下填充量再除以缩放增益gain得到原图坐标系下的(x1, y1, x2, y2)。clip_boxes函数则把越界坐标裁剪到图像范围内示例中一并提供。八、结果输出与运行验证主循环逐框打印检测结果for (int i 0; i keep.size(0); i) { int x1 keep[i][0].item().toFloat(); int y1 keep[i][1].item().toFloat(); int x2 keep[i][2].item().toFloat(); int y2 keep[i][3].item().toFloat(); float conf keep[i][4].item().toFloat(); int cls keep[i][5].item().toInt(); std::cout Rect: [ x1 , y1 , x2 , y2 ] Conf: conf Class: classes[cls] std::endl; }运行./yolov8_libtorch_inference后终端会输出类似Rect: [262,210,348,328] Conf: 0.87 Class: person的检测结果行。示例仅做终端打印若需可视化可在此基础上用 OpenCVrectangle在原图上绘制。仓库内其他 C 示例可作横向参考YOLOv8-CPP-Inference使用纯 OpenCV DNN 加载 ONNXYOLOv8-ONNXRuntime-CPP 使用 ONNX Runtime 会话与本示例LibTorch共同覆盖了三种主流 C 推理后端。三者预处理与后处理逻辑同源可互相印证。九、部署注意事项与常见问题排查基于源码与配置整理以下实际部署要点导出与加载的尺寸必须一致yolo export的imgsz、letterbox的目标尺寸、模型输入张量形状三者需统一示例固定 640×640否则前向会报 shape 不匹配错误TORCH_CXX_FLAGS不能省略这是 LibTorch 与 gcc 的 ABI 兼容关键漏掉可能出现链接失败或运行时崩溃路径占位符CMakeLists.txt中的 OpenCV/LibTorch 路径与main.cc中的模型/图片路径均需替换为真实路径后再编译Windows 需拷贝 DLL按 CMake 脚本中的注释启用 MSVC 分支把 LibTorch DLL 复制到可执行文件目录自定义类别模型替换main()中硬编码的 80 个 COCO 类名数组并确认模型类别数nc与数组长度一致后处理通过prediction.size(1) - 4自动推导nc因此只要数组正确即可阈值调整置信度/ IoU 阈值在non_max_supperession函数签名处修改conf_thres 0.25、iou_thres 0.45、max_det 300对应 Python 端 predict 参数conf、iou、max_det从 YOLOv8 迁移到 YOLOv10YOLOv10 采用 NMS-free 架构推理输出通道结构不同直接套用本示例的 NMS 后处理前需对照 ultralytics/models/yolov10 的推理实现调整输出解析逻辑。十、小结本示例完整覆盖了“TorchScript 导出 → CMake 工程搭建 → 预处理 → 推理 → NMS 后处理 → 结果输出”的 C 部署全链路其中 letterbox、坐标转换、class-aware NMS、坐标还原等关键算法均可在 main.cc 中逐行对照学习导出机制可在 exporter.py 中溯源默认阈值与超参可在 default.yaml 中确认。对需要把 YOLO 系列模型集成进 C 生产环境的开发者来说这是一份可直接编译运行、且便于按需裁剪的参考实现。【免费下载链接】yolov10YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/yo/yolov10创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。