TensorRtSharp:在 C# 世界中释放 GPU 推理的极致性能

张开发
2026/6/2 2:48:34 15 分钟阅读
TensorRtSharp:在 C# 世界中释放 GPU 推理的极致性能
一、前言1.1 为什么需要 TensorRtSharp在深度学习模型部署领域NVIDIA TensorRT 凭借其卓越的推理性能已成为 GPU 加速的事实标准。根据 NVIDIA 官方数据使用 TensorRT 进行模型优化和推理加速通常可以获得推理速度提升 2-10 倍相比原生框架显存占用降低 50% 以上通过精度优化和层融合⚡延迟降低至毫秒级满足实时应用需求然而TensorRT 官方仅提供 C 和 Python API这让广大 .NET 开发者面临一个两难的选择放弃熟悉的 C# 生态转向 C 或 Python通过复杂的互操作层进行调用开发效率低下TensorRtSharp应运而生 —— 这是一个纯 C# 编写的 TensorRT 完整封装库为 .NET 开发者提供了✅类型安全的 API 接口- 强类型系统编译时错误检查✅易于使用且性能卓越- 直观的 API 设计零性能损失✅完整的 TensorRT 功能覆盖- 支持所有核心功能✅自动资源管理- 基于 RAII 和 Dispose 模式无需担心内存泄漏✅开箱即用- NuGet 一键安装无需复杂配置✅完善的文档和示例- 丰富的代码示例和详细的使用说明1.2 TensorRtSharp 的核心优势1. 原生 C# 体验// 简洁直观的 API 设计 using Runtime runtime new Runtime(); using CudaEngine engine runtime.deserializeCudaEngineByBlob(data, size); using ExecutionContext context engine.createExecutionContext(); context.executeV3(stream);2. 完整功能覆盖✅ 模型构建ONNX → Engine✅ 推理执行同步/异步✅ 动态形状支持✅ 多精度推理FP32/FP16/INT8✅ 多 GPU 并行推理1.3 TensorRtSharp 3.0 的重大改进在前期开发的 TensorRtSharp 1.0 和 2.0 中使用者需要下载源码编译才能使用过程繁琐且容易出错。在最新的 3.0 版本中我们进行了重大改进✅一键安装- 直接将编译好的原生库与托管代码打包至 NuGet 包中✅开箱即用- 无需配置复杂的构建环境✅版本一致- 降低因环境差异导致的潜在错误开发者仅需通过 Visual Studio 的 NuGet 包管理器安装即可直接使用显著提升了开发效率与部署便捷性本文将全面介绍 TensorRtSharp 的设计理念、核心功能和使用方法助力大家快速上手使用。二、什么是 TensorRtSharp2.1 项目简介TensorRtSharp 3.0是作者对 NVIDIA TensorRT 官方库的完整 C# 接口封装。通过 P/Invoke 技术它将 TensorRT 的原生 C API 映射为符合 .NET 设计规范的托管代码让 C# 开发者能够无缝使用 TensorRT 的全部功能。2.2 核心特性特性说明完整的 API 覆盖支持 TensorRT 核心功能包括模型构建、推理执行、动态形状等类型安全强类型系统编译时错误检查避免运行时类型错误自动资源管理基于 RAII 和 Dispose 模式的资源管理防止内存泄漏跨平台支持支持 Windows、Linux兼容 .NET 5.0-10.0、.NET Core 3.1、.NET Framework 4.7.1-4.8.1高性能异步执行支持 CUDA Stream、多执行上下文并行推理开箱即用NuGet 包含所有依赖无需复杂配置2.3 项目信息项目信息版本目前最新 NuGet 版本为 0.0.5持续更新中建议使用最新版本GitHubhttps://github.com/guojin-yan/TensorRT-CSharp-API接口 NuGetJYPPX.TensorRT.CSharp.APIRuntime NuGetJYPPX.TensorRT.CSharp.API.runtime.win-x64.cuda12或JYPPX.TensorRT.CSharp.API.runtime.win-x64.cuda11编程语言C# 10三、安装与配置3.1 通过 NuGet 安装安装 TensorRtSharp 非常简单只需安装两个 NuGet 包# 安装接口包 dotnet add package JYPPX.TensorRT.CSharp.API # 安装运行时包根据您的 CUDA 版本选择 # CUDA 12.x 版本 dotnet add package JYPPX.TensorRT.CSharp.API.runtime.win-x64.cuda12 # 或 CUDA 11.x 版本 dotnet add package JYPPX.TensorRT.CSharp.API.runtime.win-x64.cuda11 小贴士Runtime 包与 CUDA 版本相关请根据您设备上安装的 CUDA 版本选择对应的包。3.2 系统要求要求说明操作系统Windows 10、LinuxUbuntu 18.04、macOS 10.15.NET 版本.NET 5.0-10.0、.NET Core 3.1、.NET Framework 4.7.1GPUNVIDIA GPU支持 CUDA 11.x 或 12.x依赖NVIDIA TensorRT 10.x、CUDA Runtime3.3 重要版本说明⚠️ 重要提醒NVIDIA TensorRT 必须是 10.x 系列TensorRtSharp 3.0 基于 TensorRT 10.x 开发不支持 TensorRT 8.x 或 9.x 版本。为了防止出现兼容性问题建议使用与博主相同的配置配置 1推荐CUDA 11.6cuDNN 9.2.0TensorRT 10.13.0.35配置 2CUDA 12.3cuDNN 9.2.0TensorRT 10.11.0.333.4 配置原生库TensorRtSharp 依赖 TensorRT 的原生库nvinfer.dll和 CUDA 的原生库cudart64_*.dll等。有两种配置方式方式一拷贝 DLL 到应用程序目录不推荐将 TensorRT 和 CUDA 的所有 DLL 文件拷贝到程序可执行目录下。缺点会导致程序目录文件庞大不方便管理与部署不推荐使用此方式方式二设置系统 PATH推荐将 TensorRT 的 lib 目录和 CUDA 的 bin 目录路径添加到系统 PATH 环境变量中。优点无需复制大量文件保持应用目录整洁便于版本管理和部署维护配置步骤设置 CUDA_PATH 环境变量设置 PATH 环境变量将以下路径添加到 PATHCUDA 的 bin 目录如C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.6\binTensorRT 的 lib 目录如C:\TensorRT-10.13.0.35\lib 建议优先使用环境变量方式配置避免因文件冗余导致部署复杂。同时注意不同 CUDA 版本间的兼容性问题。四、核心架构设计4.1 三层架构TensorRtSharp 采用清晰的三层架构设计┌─────────────────────────────────────────────────────────┐ │ 业务 API 层 (High-Level API) │ │ Runtime, Builder, CudaEngine, ExecutionContext │ └─────────────────────────────────────────────────────────┘ ▲ │ ┌─────────────────────────────────────────────────────────┐ │ 资源管理层 (Resource Management) │ │ DisposableTrtObject, DisposableObject, IOvPtrHolder │ └─────────────────────────────────────────────────────────┘ ▲ │ ┌─────────────────────────────────────────────────────────┐ │ P/Invoke 层 (Native Interop) │ │ NativeMethodsTensorRt*, NativeMethodsCuda* │ └─────────────────────────────────────────────────────────┘4.2 自动资源管理TensorRtSharp 实现了完善的资源管理机制所有 TensorRT 对象都继承自DisposableTrtObject// 所有 TensorRT 对象继承自 DisposableTrtObject public abstract class DisposableTrtObject : DisposableObject { protected IntPtr ptr; // 原生对象指针 public bool IsDisposed { get; protected set; } // 安全访问原生指针自动检查释放状态 public IntPtr TrtPtr { get { ThrowIfDisposed(); return ptr; } } // 释放非托管资源 protected override void DisposeUnmanaged() { if (ptr ! IntPtr.Zero) { // 调用原生释放函数 NativeDestroy(ptr); ptr IntPtr.Zero; } } } // 使用 using 语句自动释放资源 using Runtime runtime new Runtime(); using CudaEngine engine runtime.deserializeCudaEngineByBlob(data, size); // 离开作用域时自动释放设计亮点✅ 采用标准 Dispose 模式确保资源正确释放✅ 线程安全的资源释放机制使用Interlocked.Exchange✅ 自动内存压力通知GC.AddMemoryPressure✅ 指针安全访问ThrowIfDisposed检查五、核心类与 API5.1 命名空间在使用 TensorRtSharp 之前首先引入必要的命名空间using JYPPX.TensorRtSharp.Cuda; // CUDA 接口的程序集命名空间 using JYPPX.TensorRtSharp.Nvinfer; // TensorRT 接口的程序集命名空间5.2 Runtime推理运行时Runtime 是 TensorRT 推理的入口点负责从序列化的引擎文件创建推理引擎。// 创建 Runtime 实例 Runtime runtime new Runtime(); string filePath yolov8s-obb.engine; // 从字节数组反序列化引擎 byte[] data File.ReadAllBytes(filePath); using CudaEngine cudaEngine runtime.deserializeCudaEngineByBlob(data, (ulong)data.Length); // 从文件流反序列化 using var reader new FileStreamReader(); reader.open(filePath); using CudaEngine cudaEngine runtime.deserializeCudaEngineByFileStreamReader(reader); // 配置 DLA深度学习加速器 runtime.setDLACore(0); // 使用 DLA 核心 0 int dlaCores runtime.getNbDLACores(); // 设置最大线程数 runtime.setMaxThreads(4);主要用途反序列化 TensorRT 引擎文件配置 DLA 加速器加载插件库5.3 Builder模型构建器Builder 用于从 ONNX 模型构建 TensorRT 引擎。using Builder builder new Builder(); // 查询平台能力 bool hasFP16 builder.platformHasFastFp16(); // 是否支持 FP16 bool hasINT8 builder.platformHasFastInt8(); // 是否支持 INT8 int maxDLABatch builder.maxDLABatchSize(); // DLA 最大批大小 // 创建网络定义显式批处理模式 using NetworkDefinition network builder.createNetworkV2( TrtNetworkDefinitionCreationFlag.kEXPLICIT_BATCH); // 创建构建器配置 using BuilderConfig config builder.createBuilderConfig(); // 创建优化配置文件用于动态形状 using OptimizationProfile profile builder.createOptimizationProfile(); // 构建序列化网络 using HostMemory serialized builder.buildSerializedNetwork(network, config); // 保存引擎文件 using (FileStream fs new FileStream(model.engine, FileMode.Create, FileAccess.Write)) { fs.Write(serialized.getByteData(), 0, (int)serialized.Size); }主要用途创建网络定义和构建配置查询硬件能力FP16、INT8、DLA构建 TensorRT 引擎注册自定义插件5.4 CudaEngine推理引擎CudaEngine 是推理的核心对象包含优化后的模型计算图。// 获取张量信息 int numTensors engine.getNbIOTensors(); string inputName engine.getIOTensorName(0); // 输入张量名称 string outputName engine.getIOTensorName(1); // 输出张量名称 Dims inputShape engine.getTensorShape(inputName); TrtDataType inputType engine.getTensorDataType(inputName); // 创建执行上下文 using ExecutionContext context engine.createExecutionContext(); using ExecutionContext contextStatic engine.createExecutionContext( TrtExecutionContextAllocationStrategy.kSTATIC); // 序列化引擎 using HostMemory memory engine.serialize(); // 查询引擎属性 int numLayers engine.getNbLayers(); string name engine.getName(); long deviceMemory engine.getDeviceMemorySize();主要用途查询模型输入输出信息创建执行上下文序列化引擎性能分析5.5 ExecutionContext执行上下文ExecutionContext 管理单次推理的执行环境支持异步推理和动态形状。// 绑定张量地址 Cuda1DMemoryfloat input new Cuda1DMemoryfloat(3 * 1024 * 1024); Cuda1DMemoryfloat output new Cuda1DMemoryfloat(1 * 20 * 21504); context.setInputTensorAddress(images, input.get()); context.setOutputTensorAddress(output0, output.get()); // 设置动态形状 context.setinputShape(images, new Dims(1, 3, 1024, 1024)); Dims shape context.getTensorShape(images); // 执行推理异步使用 CUDA Stream using CudaStream stream new CudaStream(); context.executeV3(stream); stream.Synchronize(); // 等待完成 // 设置优化配置文件动态形状 context.setOptimizationProfileAsync(0, stream); // 调试功能 context.setDebugSync(true);主要用途绑定输入输出张量设置动态形状执行推理异步性能分析和调试5.6 OnnxParserONNX 解析器OnnxParser 将 ONNX 模型转换为 TensorRT 网络定义。// 解析 ONNX 文件 using NetworkDefinition network build.createNetworkV2(TrtNetworkDefinitionCreationFlag.kEXPLICIT_BATCH); using OnnxParser parser new OnnxParser(network); bool success parser.parseFromFile(yolov8s-obb.onnx, verbosity: 2); // 检查算子支持 bool supportsConv parser.supportsOperator(Conv); // 子图支持 long numSubgraphs parser.getNbSubgraphs(); bool supported parser.isSubgraphSupported(0); long[] nodes parser.getSubgraphNodes(0); // 设置解析器标志 parser.setFlag(TrtOnnxParserFlag.kNATIVE_INSTANCENORM);主要用途解析 ONNX 模型检查算子支持处理子图5.7 CUDA 内存管理1设备内存Cuda1DMemory// 创建设备内存 using Cuda1DMemoryfloat input new Cuda1DMemoryfloat(1000); ulong numElements input.SizeElements; ulong numBytes input.SizeBytes; IntPtr ptr input.DevicePointer; // 同步数据传输 float[] hostData new float[1000]; input.copyFromHost(hostData); // 主机 → 设备 input.copyToHost(hostData); // 设备 → 主机 // 异步数据传输 using CudaStream stream new CudaStream(); input.copyFromHostAsync(hostData, stream); input.copyToHostAsync(hostData, stream); // 内存操作 input.memset(0); // 填充为 0 input.memsetAsync(0, stream); // 异步填充2CUDA 流CudaStream// 创建流带优先级 using CudaStream stream new CudaStream(); using CudaStream streamHigh new CudaStream(0, -1); // 高优先级 // 同步操作 stream.Synchronize(); // 等待流完成 bool isComplete stream.Query(); // 查询是否完成 // 事件依赖 using CudaEvent cudaEvent new CudaEvent(); stream.WaitEvent(cudaEvent); // 等待事件 // 添加回调 stream.AddCallback((streamPtr, statue, userData) { Console.WriteLine(Stream callback executed); }, IntPtr.Zero, 0); // CUDA Graph 捕获 stream.BeginCapture(CudaStreamCaptureMode.Global); // ... 执行操作 ... CudaGraph_t graph stream.EndCapture();3CUDA 设备CudaDevice// 获取系统中启用的 CUDA 兼容设备的数量 int nbDevices CudaDevice.GetDeviceCount(); // 获取指定设备的属性 CudaDeviceProp properties CudaDevice.GetDeviceProperties(deviceIdx); // 设置执行设备 CudaDevice.SetDevice(device); // 获取有关设备的请求信息 int clockRate CudaDevice.GetAttribute(CudaDeviceAttr.ClockRate, device);六、完整使用示例示例 1获取和设置设备信息下面的代码可以获取当前设备的相关信息同时可以设置推理设备。using JYPPX.TensorRtSharp.Cuda; using JYPPX.TensorRtSharp.Nvinfer; namespace TestDemo { internal class Program { static void Main(string[] args) { // 指定默认使用的 GPU 设备索引 // 在多 GPU 环境下可以通过修改此变量来选择特定的显卡 int device 0; // 记录日志标记设备信息查询的开始 Logger.Instance.INFO( Device Information ); // 获取当前系统中可见的 NVIDIA GPU 数量 int nbDevices CudaDevice.GetDeviceCount(); // 检查系统中是否存在可用的 GPU 设备 if (nbDevices 0) { Logger.Instance.ERROR(Cannot find any available devices (GPUs)!); Environment.Exit(0); } // 打印所有可用设备的列表 Logger.Instance.INFO(Available Devices: ); // 遍历系统中的每一个 GPU for (int deviceIdx 0; deviceIdx nbDevices; deviceIdx) { // 获取索引为 deviceIdx 的 GPU 的详细属性 CudaDeviceProp tempProperties CudaDevice.GetDeviceProperties(deviceIdx); // 打印设备 ID、设备名称以及 UUID (唯一标识符) Logger.Instance.INFO($ Device {deviceIdx}: \{tempProperties.Name}\ UUID: {GetUuidString(tempProperties.Uuid)}); // 如果当前遍历到的设备 ID 是我们想要使用的目标设备 // 则将该设备的属性保存下来供后续使用 if (deviceIdx device) { properties tempProperties; } } // 安全检查确保请求的目标设备 ID 在有效范围内 if (device 0 || device nbDevices) { Logger.Instance.ERROR($Cannot find device ID {device}!); Environment.Exit(0); } // 将 CUDA 上下文设置到指定的 GPU 设备上 CudaDevice.SetDevice(device); // 打印选定设备的详细信息 Logger.Instance.INFO($Selected Device: {properties.Name}); Logger.Instance.INFO($Selected Device ID: {device}); Logger.Instance.INFO($Selected Device UUID: {GetUuidString(properties.Uuid)}); Logger.Instance.INFO($Compute Capability: {properties.Major}.{properties.Minor}); Logger.Instance.INFO($SMs: {properties.MultiProcessorCount}); Logger.Instance.INFO($Device Global Memory: {(properties.TotalGlobalMem 20)} MiB); Logger.Instance.INFO($Shared Memory per SM: {(properties.SharedMemPerMultiprocessor 10)} KiB); Logger.Instance.INFO($Memory Bus Width: {properties.MemoryBusWidth} bits (ECC {(properties.ECCEnabled ! 0 ? enabled : disabled)})); // 获取并打印 GPU 核心时钟频率和显存时钟频率 int clockRate CudaDevice.GetAttribute(CudaDeviceAttr.ClockRate, device); int memoryClockRate CudaDevice.GetAttribute(CudaDeviceAttr.MemoryClockRate, device); Logger.Instance.INFO($Application Compute Clock Rate: {clockRate / 1000000.0F} GHz); Logger.Instance.INFO($Application Memory Clock Rate: {memoryClockRate / 1000000.0F} GHz); } /// summary /// 辅助方法将 CudaUUID 结构体转换为格式化的 GPU UUID 字符串 /// 格式通常为GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /// /summary public static string GetUuidString(CudaUUID uuid) { int kUUID_SIZE uuid.Bytes.Length; var ss new System.Text.StringBuilder(); // 定义 UUID 的分段点用于插入连字符 - int[] splits { 0, 4, 6, 8, 10, kUUID_SIZE }; // 添加固定的 GPU 前缀 ss.Append(GPU); // 遍历分段定义格式化每一部分的字节 for (int splitIdx 0; splitIdx splits.Length - 1; splitIdx) { ss.Append(-); for (int byteIdx splits[splitIdx]; byteIdx splits[splitIdx 1]; byteIdx) { ss.AppendFormat({0:x2}, uuid.Bytes[byteIdx]); } } return ss.ToString(); } } }程序运行结果 注意不同的设备输出会有不同以具体设备输出为准。程序路径链接完整程序已经上传到GitHub请自行下载链接为https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp3.0/samples/SetCudaDeviceInfo示例 2ONNX 转 Engine 模型下面是按照官方模型转换代码编写的一个简单的转换代码using JYPPX.TensorRtSharp.Cuda; using JYPPX.TensorRtSharp.Nvinfer; namespace OnnxToEngine { internal class Program { static void Main(string[] args) { // 配置 TensorRT 日志回调 // 定义一个委托用于处理 TensorRT 内部产生的日志消息 LogCallbackFunction _callbackDelegate (message) { Console.WriteLine(message); }; // 将自定义的回调函数注册给 TensorRT 的全局 Logger 实例 Logger.Instance.SetCallback(_callbackDelegate); // 设置日志的严重性级别阈值 // LoggerSeverity.kINFO: 打印信息、警告和错误 Logger.Instance.SetThreshold(LoggerSeverity.kINFO); // 1. 创建 TensorRT Builder (构建器) Builder build new Builder(); // 2. 创建网络定义 (Network Definition) // 显式批处理 标志表示网络定义中显式包含批处理维度 NetworkDefinition networkDefinition build.createNetworkV2(TrtNetworkDefinitionCreationFlag.kEXPLICIT_BATCH); // 3. 创建构建器配置 BuilderConfig builderConfig build.createBuilderConfig(); // 4. 创建 ONNX 解析器 OnnxParser onnxParser new OnnxParser(networkDefinition); // 指定待转换的 ONNX 模型文件路径 string modelpath yolo11s-obb.onnx; // 5. 解析 ONNX 模型文件 // 参数 2: 日志级别 (1ERROR, 2WARNING, 3INFO, 4VERBOSE) if (onnxParser.parseFromFile(modelpath, 2) false) { Console.WriteLine($parse onnx model failed); return; } // 6. 设置构建精度标志 // kFP16: 启用半精度 (FP16) 推理模式 builderConfig.setFlag(TrtBuilderFlag.kFP16); // 7. 创建 CUDA 流 CudaStream cudaStream new CudaStream(); // 8. 设置优化配置文件的流 builderConfig.setProfileStream(cudaStream); // 9. 构建并序列化网络 // 这是一个耗时较长的过程因为 TensorRT 会进行内核自动调优、层融合等优化 HostMemory hostMemory build.buildSerializedNetwork(networkDefinition, builderConfig); // 10. 保存 Engine 到磁盘 string filePath yolo11s-obb.engine; using (FileStream fs new FileStream(filePath, FileMode.Create, FileAccess.Write)) { fs.Write(hostMemory.getByteData(), 0, (int)hostMemory.Size); } Console.WriteLine(Engine saved successfully!); } } }程序运行结果程序路径链接完整程序已经上传到GitHub请自行下载链接为https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp3.0/samples/OnnxToEngine 使用 trtexec 工具转换模型推荐当前 ONNX 转 Engine 代码由于没有进行优化转换速度会较慢。建议使用 TensorRT SDK 自带的trtexec.exe工具转换模型。trtexec 使用方式1使用 CMD 切换到工具目录该工具存放在下载的 TensorRT 库中打开 CMD 并切换到该路径2固定形状模型转换指令对于形状固定的模型直接输入常规指令转换即可trtexec.exe --onnxyolov8s-obb.onnx --saveEngineyolov8s-obb.engine --fp16 --workspace1024参数说明--onnxyolov8s-obb.onnx指定输入的 ONNX 模型文件路径--saveEngineyolov8s-obb.engine指定输出的 Engine 文件保存路径--fp16启用 FP16 精度可选--workspace1024指定最大工作空间单位 MB可选3动态形状模型转换指令对于输入形状是动态的情况转换时要设置形状参数trtexec.exe --onnxyolov8s-obb_b.onnx --saveEngineyolov8s-obb_b.engine --fp16 --minShapesimages:1x3x1024x1024 --optShapesimages:8x3x1024x1024 --maxShapesimages:24x3x1024x1024参数说明--minShapesimages:1x3x1024x1024最小输入形状--optShapesimages:8x3x1024x1024最优输入形状Engine 会为此形状优化--maxShapesimages:24x3x1024x1024最大输入形状多输入模型转换指令trtexec --onnxmodel.onnx --minShapesinput1:1x3x224x224,input2:1x256 --optShapesinput1:4x3x224x224,input2:4x256 --maxShapesinput1:8x3x224x224,input2:8x256示例 3YOLO 目标检测下面是一个完整的 YOLO 目标检测示例展示从模型构建到推理的全流程。⚠️ 由于代码较长此处仅展示核心思路。完整代码请参考项目示例。using JYPPX.TensorRtSharp.Cuda; using JYPPX.TensorRtSharp.Nvinfer; using OpenCvSharp; using OpenCvSharp.Dnn; using System.Diagnostics; using System.Runtime.InteropServices; namespace YoloDetInfer { internal class Program { // 配置参数 // 模型输入尺寸 (宽高) private const int InputSize 640; // 建议根据实际模型动态获取或使用 Netron 查看 private const int OutputSize 8400; // 模型类别数 (根据您的具体数据集修改此处假设为15类) private const int CategoryNum 80; // 置信度阈值 private const float ConfThreshold 0.25f; // NMS IOU 阈值 private const float NmsThreshold 0.3f; static void Main(string[] args) { // 配置 TensorRT 日志回调 // 定义一个委托用于处理 TensorRT 内部产生的日志消息。 // 这允许我们将 C 层面的日志输出到 C# 的控制台。 LogCallbackFunction _callbackDelegate (message) { Console.WriteLine(message); }; // 将自定义的回调函数注册给 TensorRT 的全局 Logger 实例。 Logger.Instance.SetCallback(_callbackDelegate); // 设置日志的严重性级别阈值。 // LoggerSeverity.kINFO: 打印信息、警告和错误。 // 开发调试阶段通常设为 kINFO 或 kVERBOSE生产环境可设为 kWARNING 或 kERROR 以减少输出。 Logger.Instance.SetThreshold(LoggerSeverity.kINFO); string enginePath yolov8s.engine; string imagePath bus.jpg; // 1. 加载 TensorRT Engine // 使用 using 语句确保文件流正确关闭 byte[] engineData; using (FileStream fs new FileStream(enginePath, FileMode.Open, FileAccess.Read)) using (BinaryReader br new BinaryReader(fs)) { engineData br.ReadBytes((int)fs.Length); } // 反序列化 Engine // Runtime 必须在 Engine 生命周期内保持存活通常建议设为全局或静态或者确保它最后释放 Runtime runtime new Runtime(); // 创建 CudaEngine (此处使用 using 确保推理完成后引擎被销毁) using (CudaEngine cudaEngine runtime.deserializeCudaEngineByBlob(engineData, (ulong)engineData.Length)) { // 2. 初始化推理上下文与显存 // 创建执行上下文 using (JYPPX.TensorRtSharp.Nvinfer.ExecutionContext executionContext cudaEngine.createExecutionContext(TrtExecutionContextAllocationStrategy.kSTATIC)) using (CudaStream cudaStream new CudaStream()) // 创建 CUDA 流用于异步执行 { // 获取输入维度信息 (用于校验) Dims inputDims executionContext.getTensorShape(images); Logger.Instance.INFO($Input Shape: {inputDims.d[0]}x{inputDims.d[1]}x{inputDims.d[2]}x{inputDims.d[3]}); // 计算所需显存大小 // 输入: Batch1, Channel3, Height640, Width640 ulong inputSizeInBytes 1 * 3 * InputSize * InputSize; // 输出: Batch1, ChannelsCategoryNum4(box)1(angle), Num8400 int outputChannels CategoryNum 4; // 4坐标 N类别 ulong outputSizeInBytes (ulong)(1 * outputChannels * OutputSize); Stopwatch sw new Stopwatch(); // 分配 GPU 显存 using (Cuda1DMemoryfloat inputGpuMemory new Cuda1DMemoryfloat(inputSizeInBytes)) using (Cuda1DMemoryfloat outputGpuMemory new Cuda1DMemoryfloat(outputSizeInBytes)) { // 绑定显存地址到 TensorRT 上下文 executionContext.setInputTensorAddress(images, inputGpuMemory.get()); executionContext.setOutputTensorAddress(output0, outputGpuMemory.get()); // 预热推理 (可选但推荐尤其是首次推理时) executionContext.executeV3(cudaStream); cudaStream.Synchronize(); // 3. 图像预处理 Mat img Cv2.ImRead(imagePath); if (img.Empty()) { Logger.Instance.INFO(Image not found!); return; } sw.Start(); float[] inputData PreProcess(img, out float scale, out int xOffset, out int yOffset); sw.Stop(); Logger.Instance.INFO($Pre-processing time: {sw.ElapsedMilliseconds} ms); // 4. 推理 // 准备主机内存接收结果 float[] outputData new float[outputChannels * OutputSize]; sw.Restart(); // 将数据从主机 拷贝到设备 inputGpuMemory.copyFromHostAsync(inputData, cudaStream); // 执行推理 (enqueueV3 是异步的) executionContext.executeV3(cudaStream); // 等待推理完成 cudaStream.Synchronize(); // 将结果从设备 拷贝回主机 // 这里的拷贝是同步的会等待 GPU 计算完成 outputGpuMemory.copyToHostAsync(outputData, cudaStream); sw.Stop(); Logger.Instance.INFO($Inference time: {sw.ElapsedMilliseconds} ms); // 5. 后处理 sw.Restart(); ListDetData results PostProcess(outputData, scale, xOffset, yOffset); sw.Stop(); Logger.Instance.INFO($Post-processing time: {sw.ElapsedMilliseconds} ms); // 6. 结果可视化 Mat resultImg DrawDetResult(results, img); Cv2.ImShow(YOLO11-DET Result, resultImg); Cv2.WaitKey(0); } } } } /// summary /// 图像预处理Letterbox 缩放、归一化、HWC 转 CHW /// /summary private static float[] PreProcess(Mat img, out float scale, out int xOffset, out int yOffset) { // 转换颜色空间 BGR - RGB Mat rgbImg new Mat(); Cv2.CvtColor(img, rgbImg, ColorConversionCodes.BGR2RGB); // 计算 Letterbox 缩放比例 int maxDim Math.Max(rgbImg.Width, rgbImg.Height); scale (float)maxDim / InputSize; // 计算缩放后的尺寸 int newWidth (int)(rgbImg.Width / scale); int newHeight (int)(rgbImg.Height / scale); // Resize 图像 Mat resizedImg new Mat(); Cv2.Resize(rgbImg, resizedImg, new Size(newWidth, newHeight)); // 创建黑色背景 Canvas (InputSize x InputSize) Mat paddedImg Mat.Zeros(InputSize, InputSize, MatType.CV_8UC3); // 计算粘贴位置 (居中) xOffset (InputSize - newWidth) / 2; yOffset (InputSize - newHeight) / 2; // 将图像拷贝到 Canvas 中央 Rect roi new Rect(xOffset, yOffset, newWidth, newHeight); resizedImg.CopyTo(new Mat(paddedImg, roi)); // 归一化 (0-255 - 0-1) 并转为 float 类型 Mat floatImg new Mat(); paddedImg.ConvertTo(floatImg, MatType.CV_32FC3, 1.0 / 255.0); // HWC 转 CHW 并展平为一维数组 Mat[] channels Cv2.Split(floatImg); float[] chwData new float[3 * InputSize * InputSize]; // 拷贝数据R通道 - C通道 - B通道 (OpenCV Split 出来顺序是 B, G, R对应索引 0, 1, 2) int channelSize InputSize * InputSize; // 将 R, G, B 依次拷入数组 Marshal.Copy(channels[0].Data, chwData, 0, channelSize); // R Marshal.Copy(channels[1].Data, chwData, channelSize, channelSize); // G Marshal.Copy(channels[2].Data, chwData, channelSize * 2, channelSize); // B // 释放临时 Mat rgbImg.Dispose();

更多文章