
简介这份资源面向具备一定C#基础、希望在Windows平台落地YOLOv8目标检测的开发者重点解决如何借助OpenVINO与TensorRT两大推理框架完成模型部署的问题。包内提供完整的C#工程源码涵盖TensorRTSharp、OpenVinoSharp、CommonSharp、ResultSharp等模块并配有C外部依赖、模型转换与推理结果处理文档以及检测与分类标签文件方便读者对照理解推理流程与后处理逻辑。资源共63个文件以cs源码、csproj工程文件、cpp与h头文件为主辅以md说明文档、jpg示例图片及少量Python脚本压缩包约3.01MB结构清晰便于按模块查阅。目前已有421人学习下载适合想打通C#调用OpenVINO与TensorRT推理链路、研究YOLOv8部署细节的中高级开发者参考借鉴。1. 从 C# 上位机到 OpenVINO/TensorRTYolov8 部署到底在解决什么问题产线上跑着 C# 写的上位机相机采图、界面刷新、PLC 通信都稳唯独检测环节卡在 Python 进程里——这是很多做视觉落地的团队遇到的真实局面。基于 C# 在 OpenVINO 以及 TensorRT 平台部署 Yolov8说的就是把这套检测能力从 Python 脚本里搬出来用 C# 直接调用推理引擎在 Intel CPU/核显上走 OpenVINO在 NVIDIA 显卡上走 TensorRT让整条链路回到一个进程里。它解决的是跨语言通信开销、部署环境依赖重、产线机器装不上完整 Python 生态这三类问题。适合有 C# 上位机基础、手里已经有 Yolov8 权重、需要把检测塞进现有工控软件的开发者。下面按「模型怎么转 → 两个平台各自怎么跑 → 坑在哪 → 怎么验证」推一遍。2. 模型准备从 Yolov8 权重到两个平台能吃的中间格式2.1 为什么不能直接拿 .pt 文件给 C# 用Yolov8 训练完默认给的是 PyTorch 的.pt权重这个格式只有 PyTorch 运行时能读。C# 侧无论是 OpenVINO 的 Inference Engine 还是 TensorRT 的运行时都不认这个格式。所以第一步永远是导出成中间表示OpenVINO 吃的是 IR 格式.xml.binTensorRT 吃的是.onnx再在目标机上构建 engine。这里有个容易忽略的点——导出时的输入尺寸、是否动态 batch、是否带后处理直接决定后面 C# 代码怎么写。我一般固定成静态输入比如1x3x640x640产线单帧检测够用也省掉动态 shape 带来的额外分支。导出 ONNX 用 Ultralytics 官方命令即可注意opset别太低11 以上对后续转换友好# 导出 ONNX固定输入尺寸 640opset 12 yolo export modelyolov8n.pt formatonnx imgsz640 opset12 simplifyTruesimplifyTrue会调用 onnx-simplifier 做一次图简化能去掉不少冗余节点对 TensorRT 构建速度和 OpenVINO 转换成功率都有帮助。imgsz640要和训练时一致否则精度会掉。导出后在同目录得到yolov8n.onnx这是两个平台共同的起点。2.2 OpenVINO IR 转换一条命令和三个必看参数OpenVINO 侧用mo工具把 ONNX 转成 IR。装好 OpenVINO 开发包后命令行大致是这样# 将 ONNX 转为 OpenVINO IR指定输入形状和输出目录 mo --input_model yolov8n.onnx \ --input_shape [1,3,640,640] \ --output_dir ./ov_model \ --compress_to_fp16 True--input_shape必须和导出 ONNX 时一致写成[1,3,640,640]表示 batch1、3 通道、640 见方。--compress_to_fp16 True会把权重压成 FP16模型体积减半Intel 核显上通常还能提速但如果你的场景对小目标召回敏感建议先对比 FP32 和 FP16 的检测结果再决定。转换完得到yolov8n.xml和yolov8n.bin两个文件C# 里加载时两个都要给路径。2.3 TensorRT engine 构建为什么必须在目标机上做TensorRT 的.engine文件和显卡架构、驱动版本、TensorRT 版本强绑定。在 A 机器上构建的 engine 拿到 B 机器上大概率直接报错或者性能暴跌。所以正确做法是把 ONNX 拷到目标机在目标机上用trtexec构建# 在目标 NVIDIA 机器上构建 FP16 engine trtexec --onnxyolov8n.onnx \ --saveEngineyolov8n_fp16.engine \ --fp16 \ --workspace4096--fp16开启半精度--workspace4096给 4GB 显存做构建时临时空间构建阶段比推理阶段吃显存给小了会失败。构建完成后可以用--loadEngineyolov8n_fp16.engine --shapesinput:1x3x640x640跑一次 benchmark看吞吐和延迟是否达标。这一步别省构建成功不等于推理正确后面 C# 里出问题再回头查会很被动。3. C# 调 OpenVINO输入张量怎么建、输出怎么解3.1 用 OpenVINO C# API 加载 IR 并创建推理请求OpenVINO 官方提供了 .NET 绑定NuGet 上装OpenVinoSharp或官方OpenVINO.Runtime这类包即可。核心流程是读模型 → 编译到指定设备 → 创建推理请求 → 填输入 → 跑 → 取输出。下面是一段最小可跑的结构using OpenVinoSharp; // 加载 IR 模型CPU 设备也可以换成 GPU var core new Core(); var model core.read_model(ov_model/yolov8n.xml); var compiled core.compile_model(model, CPU); var request compiled.create_infer_request(); // 构造输入张量1x3x640x640 的 float 数组 float[] inputData Preprocess(image); // 归一化 HWC 转 CHW var inputTensor new Tensor(inputData, new Shape(1, 3, 640, 640)); request.set_input_tensor(inputTensor); request.infer(); // 取输出Yolov8 导出后通常是 [1,84,8400] var outputTensor request.get_output_tensor(); float[] output outputTensor.get_datafloat();compile_model的第二个参数是设备名CPU、GPU、AUTO都行AUTO会让 OpenVINO 自己挑产线上我一般显式写死避免行为漂移。Preprocess里要做的事BGR 转 RGB、除以 255、按 CHW 排布这三步顺序错了检测框会整体偏移。3.2 输入张量的内存布局c#创建openvino输入张量最容易翻车的地方Yolov8 期望的输入是 NCHW也就是先通道后高宽。C# 里从Bitmap拿到的像素是 HWC 排列直接塞进去必错。正确做法是三重循环按[c][h][w]填float[] data new float[3 * 640 * 640]; for (int y 0; y 640; y) { for (int x 0; x 640; x) { var px bmp.GetPixel(x, y); int idx y * 640 x; data[0 * 640 * 640 idx] px.R / 255f; // R 通道 data[1 * 640 * 640 idx] px.G / 255f; // G 通道 data[2 * 640 * 640 idx] px.B / 255f; // B 通道 } }GetPixel在产线速度下偏慢实际项目里用LockBits拿IntPtr再按行拷贝能快一个数量级。归一化系数 255 要和训练时一致Yolov8 默认就是除以 255别自作主张改成 127.5。3.3 输出解析84 行里哪几行是框、哪几行是分数Yolov8 导出后的输出形状是[1, 84, 8400]84 4 个框坐标 80 个类别分数8400 是候选框数量。解析时按列遍历每列取类别分数最大值超过阈值就还原坐标int numClasses 80; int numBoxes 8400; for (int i 0; i numBoxes; i) { float maxScore 0; int maxIdx 0; for (int c 0; c numClasses; c) { float s output[(4 c) * numBoxes i]; if (s maxScore) { maxScore s; maxIdx c; } } if (maxScore 0.25f) continue; // 置信度阈值 float cx output[0 * numBoxes i]; float cy output[1 * numBoxes i]; float w output[2 * numBoxes i]; float h output[3 * numBoxes i]; // 还原到原图坐标再做 NMS }阈值 0.25 是常见起点漏检多就降到 0.15误检多就升到 0.4。NMS 的 IoU 阈值一般 0.45重叠目标多的场景调到 0.5 以上。这两组数没有万能值得拿你自己的图跑一批看效果。4. C# 调 TensorRTengine 加载与显存管理4.1 用 TensorRT C# 绑定加载 engine 的正确姿势TensorRT 官方没有一等公民的 C# API常见做法是用TensorRT.NET这类社区绑定或者自己 P/Invokenvinfer.dll。加载 engine 的核心步骤是反序列化、创建执行上下文、绑定输入输出 bufferusing TensorRtSharp; var engine new Engine(yolov8n_fp16.engine); var context engine.createExecutionContext(); // 分配输入输出显存 context.setInputShape(images, new Dims(1, 3, 640, 640)); context.setTensorAddress(images, inputDevicePtr); context.setTensorAddress(output0, outputDevicePtr); context.execute(1);setInputShape里的名字要和导出 ONNX 时的输入名一致Yolov8 默认叫images输出叫output0。名字对不上会直接抛异常别凭记忆写用trtexec --onnx... --dumpLayerInfo看一眼确认。4.2 显存拷贝cudaMemcpy 的同步与异步选择C# 侧数据在主机内存TensorRT 要的是设备显存中间必须拷贝。同步拷贝写起来简单但会阻塞异步拷贝要配 stream 和事件代码复杂但吞吐高。产线单路检测用同步就够// 主机到设备同步拷贝 cudaMemcpy(inputDevicePtr, inputHostPtr, inputBytes, cudaMemcpyKind.HostToDevice); context.execute(1); // 设备到主机 cudaMemcpy(outputHostPtr, outputDevicePtr, outputBytes, cudaMemcpyKind.DeviceToHost);inputBytes是1*3*640*640*4float 占 4 字节。多路并发时同步拷贝会成为瓶颈这时候再上异步别一上来就搞复杂。4.3 多路视频下的吞吐估算t4 1080p25帧每秒用tensorrt yolo 640分辨率检测可以支持多少路这是被问得最多的问题之一。T4 上跑 Yolov8n FP16、640 输入单帧推理延迟大约 3 到 5 毫秒理论吞吐 200 到 300 FPS。1080p25 帧每秒一路就是 25 FPS纯算力看能撑 8 到 12 路。但实际落地要打折解码占一部分、预处理占一部分、显存拷贝占一部分我一般按理论值的 50% 到 60% 估也就是 4 到 6 路比较稳。想再往上走要么换 Yolov8n 更小的输入尺寸要么上 batch 推理把多路拼成一个 batch但 batch 会拉高单帧延迟实时性要求高的场景要权衡。5. 避坑与排查部署 Yolov8 时最常翻车的五件事5.1 检测框整体偏移或缩放错位现象框能出来但位置系统性偏左偏上或者框大小只有实际的一半。原因预处理里 resize 用了拉伸而不是 letterbox或者坐标还原时忘了乘回缩放比例。解决统一用 letterbox 保持宽高比记录 padding 偏移和缩放系数后处理时先减 padding 再除缩放系数。5.2 OpenVINO 加载 IR 报版本不匹配现象C# 里read_model抛异常提示 IR version 不支持。原因转换用的 OpenVINO 版本比运行时新或者反过来。解决转换和运行用同一大版本产线机器上装哪个版本开发机就装哪个版本别图新。5.3 TensorRT engine 换机器后直接崩现象开发机构建好的 engine 拷到产线机加载时报错或输出全零。原因engine 和 GPU 架构、驱动、TensorRT 版本绑定。解决engine 只在目标机构建ONNX 作为分发格式产线机首次启动时构建一次并缓存。5.4 输出解析后类别全错现象框位置对但类别标签和实际对不上。原因训练时类别顺序和解析时用的顺序不一致或者 COCO 80 类的索引映射写错。解决把训练时的names字典导出成配置文件C# 里读同一份别硬编码。5.5 长时间运行内存持续上涨现象跑几小时后内存占用越来越高最终 OOM。原因每次推理都 new 了 Tensor 或 Mat 没释放或者 OpenVINO 的 InferRequest 反复创建。解决推理请求和输入输出 buffer 在初始化时创建一次循环里复用C# 侧注意IDisposable的释放。6. 验证部署是否真的成功三个可量化的检查点部署完别只看「能出框」要拿数据说话。第一个检查点是数值一致性同一张图Python 原版推理和 C# 部署推理的输出张量逐元素对比最大绝对误差控制在 1e-2 以内算合格FP16 可以放宽到 5e-2。第二个检查点是端到端延迟从图像进内存到框坐标出来用Stopwatch打点单帧稳定在预期范围内且跑一万帧不漂移。第三个检查点是精度回归准备 50 到 100 张带标注的图对比部署前后的 mAP掉点超过 2 个百分点就要回头查预处理和后处理。// 端到端延迟打点示例 var sw Stopwatch.StartNew(); for (int i 0; i 10000; i) { Preprocess(bmp, inputBuffer); request.infer(); ParseOutput(outputBuffer, results); } sw.Stop(); Console.WriteLine($平均单帧: {sw.ElapsedMilliseconds / 10000.0:F2} ms);跑这个循环时把界面刷新关掉否则测的是 UI 线程不是推理。我自己的习惯是每次换模型、换机器、换驱动这三个检查点都重跑一遍宁可多花半小时也别等产线停了再回头找。这套流程从 OpenVINO 到 TensorRT 我都踩过一遍最深的教训是别信「转换成功就等于部署成功」中间隔着一整个预处理和后处理的鸿沟。希望帮到你。本文还有配套的精品资源点击获取