TensorRtSharp:在 C# 世界中释放 GPU 推理的极致性能
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 的设计理念、核心功能和使用方法,助力大家快速上手使用。
二、什么是 TensorRtSharp
2.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(持续更新中,建议使用最新版本) |
| GitHub | https://github.com/guojin-yan/TensorRT-CSharp-API |
| 接口 NuGet | JYPPX.TensorRT.CSharp.API |
| Runtime NuGet | JYPPX.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+、Linux(Ubuntu 18.04+)、macOS 10.15+ |
| .NET 版本 | .NET 5.0-10.0、.NET Core 3.1、.NET Framework 4.7.1+ |
| GPU | NVIDIA GPU(支持 CUDA 11.x 或 12.x) |
| 依赖 | NVIDIA TensorRT 10.x、CUDA Runtime |
3.3 重要版本说明
⚠️ 重要提醒:NVIDIA TensorRT 必须是 10.x 系列!!
TensorRtSharp 3.0 基于 TensorRT 10.x 开发,不支持 TensorRT 8.x 或 9.x 版本。
为了防止出现兼容性问题,建议使用与博主相同的配置:
配置 1(推荐):
- CUDA 11.6
- cuDNN 9.2.0
- TensorRT 10.13.0.35
配置 2:
- CUDA 12.3
- cuDNN 9.2.0
- TensorRT 10.11.0.33
3.4 配置原生库
TensorRtSharp 依赖 TensorRT 的原生库(nvinfer.dll)和 CUDA 的原生库(cudart64_*.dll 等)。有两种配置方式:
方式一:拷贝 DLL 到应用程序目录(不推荐)
将 TensorRT 和 CUDA 的所有 DLL 文件拷贝到程序可执行目录下。
缺点:
- 会导致程序目录文件庞大
- 不方便管理与部署
- 不推荐使用此方式
方式二:设置系统 PATH(推荐)
将 TensorRT 的 lib 目录和 CUDA 的 bin 目录路径添加到系统 PATH 环境变量中。
优点:
- 无需复制大量文件
- 保持应用目录整洁
- 便于版本管理和部署维护
配置步骤:
- 设置 CUDA_PATH 环境变量

- 设置 PATH 环境变量
将以下路径添加到 PATH:
- CUDA 的 bin 目录(如
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.6\bin) - TensorRT 的 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检查)
五、核心类与 API
5.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 管理单次推理的执行环境,支持异步推理和动态形状。
// 绑定张量地址
Cuda1DMemory<float> input = new Cuda1DMemory<float>(3 * 1024 * 1024);
Cuda1DMemory<float> output = new Cuda1DMemory<float>(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 OnnxParser(ONNX 解析器)
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 Cuda1DMemory<float> input = new Cuda1DMemory<float>(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); // 异步填充
(2)CUDA 流(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();
(3)CUDA 设备(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
示例 2:ONNX 转 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: 日志级别 (1=ERROR, 2=WARNING, 3=INFO, 4=VERBOSE)
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 --onnx=yolov8s-obb.onnx --saveEngine=yolov8s-obb.engine --fp16 --workspace=1024
参数说明:
--onnx=yolov8s-obb.onnx:指定输入的 ONNX 模型文件路径--saveEngine=yolov8s-obb.engine:指定输出的 Engine 文件保存路径--fp16:启用 FP16 精度(可选)--workspace=1024:指定最大工作空间,单位 MB(可选)

(3)动态形状模型转换指令
对于输入形状是动态的情况,转换时要设置形状参数:
trtexec.exe --onnx=yolov8s-obb_b.onnx --saveEngine=yolov8s-obb_b.engine --fp16 --minShapes=images:1x3x1024x1024 --optShapes=images:8x3x1024x1024 --maxShapes=images:24x3x1024x1024
参数说明:
--minShapes=images:1x3x1024x1024:最小输入形状--optShapes=images:8x3x1024x1024:最优输入形状(Engine 会为此形状优化)--maxShapes=images:24x3x1024x1024:最大输入形状

多输入模型转换指令:
trtexec --onnx=model.onnx --minShapes=input1:1x3x224x224,input2:1x256 --optShapes=input1:4x3x224x224,input2:4x256 --maxShapes=input1:8x3x224x224,input2:8x256
示例 3:YOLO 目标检测
下面是一个完整的 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]}");
// 计算所需显存大小
// 输入: Batch=1, Channel=3, Height=640, Width=640
ulong inputSizeInBytes = 1 * 3 * InputSize * InputSize;
// 输出: Batch=1, Channels=CategoryNum+4(box)+1(angle), Num=8400
int outputChannels = CategoryNum + 4; // 4坐标 + N类别
ulong outputSizeInBytes = (ulong)(1 * outputChannels * OutputSize);
Stopwatch sw = new Stopwatch();
// 分配 GPU 显存
using (Cuda1DMemory<float> inputGpuMemory = new Cuda1DMemory<float>(inputSizeInBytes))
using (Cuda1DMemory<float> outputGpuMemory = new Cuda1DMemory<float>(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();
List<DetData> 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();
resizedImg.Dispose();
paddedImg.Dispose();
floatImg.Dispose();
foreach (var c in channels) c.Dispose();
return chwData;
}
/// <summary>
/// 后处理:解析 TensorRT 输出、NMS 过滤
/// </summary>
private static List<DetData> PostProcess(float[] result, float scale, int xOffset, int yOffset)
{
List<Rect> boxes = new List<Rect>();
List<float> confidences = new List<float>();
List<int> classIds = new List<int>();
// 遍历所有预测框 (OutputSize)
// 数据布局: [4(box) + 80(classes)] * OutputSize
// 展平数组中,同一属性的数据是连续存储的,例如所有 cx 在一起,所有 cy 在在一起...
int stride = OutputSize; // 步长,不同属性在数组中的偏移量
for (int i = 0; i < OutputSize; i++)
{
// 查找最大类别概率及其索引
float maxConf = 0;
int maxClassId = -1;
// 遍历类别
for (int c = 0; c < CategoryNum; c++)
{
// 数组索引:(坐标/角度偏移量 + 类别偏移) * 框索引
// 注意:原始代码中 result[outputSize * j + i] 这种访问方式基于 Transposed 数据布局
float conf = result[(4 + c) * stride + i];
if (conf > maxConf)
{
maxConf = conf;
maxClassId = c;
}
}
// 置信度过滤
if (maxConf > ConfThreshold)
{
// 提取坐标 (cx, cy, w, h)
float cx = result[0 * stride + i];
float cy = result[1 * stride + i];
float w = result[2 * stride + i];
float h = result[3 * stride + i];
// 还原坐标到原图尺寸
int rx = (int)((cx - xOffset - 0.5 * w) * scale);
int ry = (int)((cy - yOffset - 0.5 * h) * scale);
int rw = (int)(w * scale);
int rh = (int)(h * scale);
boxes.Add(new Rect(rx, ry, rw, rh));
confidences.Add(maxConf);
classIds.Add(maxClassId);
}
}
// 执行 NMS (旋转框 NMS)
// OpenCV 的 NMSBoxes 支持 RotatedRect
int[] indices;
CvDnn.NMSBoxes(boxes, confidences, ConfThreshold, NmsThreshold, out indices);
List<DetData> finalResults = new List<DetData>();
foreach (int idx in indices)
{
finalResults.Add(new DetData
{
index = classIds[idx],
score = confidences[idx],
box = boxes[idx]
});
}
return finalResults;
}
/// <summary>
/// 绘制检测结果(水平矩形框)
/// </summary>
/// <param name="results">检测结果列表</param>
/// <param name="image">原始图像</param>
/// <returns>绘制后的图像</returns>
public static Mat DrawDetResult(List<DetData> results, Mat image)
{
// 克隆图像以免修改原图
Mat mat = image.Clone();
foreach (var item in results)
{
// 1. 绘制矩形框
// Rect 结构包含 X, Y, Width, Height
Cv2.Rectangle(mat, item.box, new Scalar(0, 255, 0), thickness: 2);
// 2. 准备标签文本 (类别ID - 置信度)
string label = $"{item.index} - {item.score:F2}";
// 3. 计算文本的尺寸,用于绘制背景
int baseLine = 1;
Size textSize = Cv2.GetTextSize(label, HersheyFonts.HersheySimplex, 0.6, 1, out baseLine);
// 4. 绘制标签背景(半透明黑色矩形),防止文字与背景混淆
// 位置:矩形左上角略微上移,或者直接贴着左上角
Point labelPosition = new Point(item.box.X, item.box.Y - (int)textSize.Height - 5);
// 确保标签不画出图像边界
if (labelPosition.Y < 0) labelPosition.Y = item.box.Y + (int)textSize.Height + 5;
Rect labelBgRect = new Rect(labelPosition.X,
labelPosition.Y - (int)textSize.Height, // OpenCV GetTextSize 返回的高度是基线到底部的距离,需调整
(int)textSize.Width,
(int)textSize.Height + (int)baseLine);
// 如果背景框也在图像范围内,则绘制
// 注意:这里简化处理,直接画在框上方
Cv2.Rectangle(mat,
new Point(item.box.X, item.box.Y - textSize.Height - 5),
new Point(item.box.X + textSize.Width, item.box.Y),
new Scalar(0, 255, 0),
thickness: -1); // -1 表示填充
// 5. 绘制文本(白色文字)
Cv2.PutText(mat,
label,
new Point(item.box.X, item.box.Y - 5),
HersheyFonts.HersheySimplex,
0.6,
new Scalar(0, 0, 0),
1);
}
return mat;
}
public class DetData
{
public int index;
public float score;
public Rect box;
}
}
}
程序运行结果:


性能测试结果:
| Batch Size | 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 前处理 (ms) | 9 | 13 | 27 | 38 | 56 | 59 | 63 | 83 | 96 | 105 | 118 | 130 | 144 |
| 模型推理 (ms) | 7 | 15 | 24 | 36 | 48 | 60 | 96 | 84 | 93 | 153 | 120 | 133 | 203 |
| 后处理 (ms) | 25 | 26 | 26 | 26 | 28 | 27 | 27 | 28 | 28 | 28 | 27 | 31 | 29 |
🔗程序路径链接:完整程序已经上传到GitHub,请自行下载,链接为:
https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp3.0/samples/YoloDetInfer同时也提供了YoloOBB模型的推理程序,请自行下载,链接为:
https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp3.0/samples/YoloObbInfer
示例 4:动态形状推理
对于输入尺寸可变的模型,需要根据输入的数据配置动态形状。
核心代码:
using JYPPX.TensorRtSharp.Cuda;
using JYPPX.TensorRtSharp.Nvinfer;
using OpenCvSharp;
using OpenCvSharp.Dnn;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace YoloObbBatchInfer
{
internal class Program
{
// ================= 配置参数 =================
// 模型输入尺寸 (宽=高)
private const int InputSize = 1024;
// 建议根据实际模型动态获取或使用 Netron 查看
private const int OutputSize = 21504;
// 模型类别数 (根据您的具体数据集修改,此处假设为15类)
private const int CategoryNum = 15;
// 置信度阈值
private const float ConfThreshold = 0.25f;
// NMS IOU 阈值
private const float NmsThreshold = 0.3f;
private const int MaxBatchSize = 24;
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-obb_b.engine";
string[] imagePaths = {
"P0006.png" , "P0016.png", "P0456.png", "P0813.png"};
// ================= 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();
runtime.setMaxThreads(10);
// 创建 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]}");
// 计算所需显存大小
// 输入: Batch=1, Channel=3, Height=1024, Width=1024
ulong inputSizeInBytes = MaxBatchSize * 3 * InputSize * InputSize;
// 输出: Batch=1, Channels=CategoryNum+4(box)+1(angle), Num=8400
int outputChannels = CategoryNum + 5; // 4坐标 + 1角度 + N类别
ulong outputSizeInBytes = (ulong)(MaxBatchSize * outputChannels * OutputSize);
Stopwatch sw = new Stopwatch();
// 分配 GPU 显存
using (Cuda1DMemory<float> inputGpuMemory = new Cuda1DMemory<float>(inputSizeInBytes))
using (Cuda1DMemory<float> outputGpuMemory = new Cuda1DMemory<float>(outputSizeInBytes))
{
// 绑定显存地址到 TensorRT 上下文
executionContext.setInputTensorAddress("images", inputGpuMemory.get());
executionContext.setOutputTensorAddress("output0", outputGpuMemory.get());
// 关键一步,修改本次推理的形状
executionContext.setinputShape("images", new Dims(imagePaths.Count(), 3, 1024, 1024));
// 预热推理 (可选,但推荐,尤其是首次推理时)
executionContext.executeV3(cudaStream);
cudaStream.Synchronize();
// ================= 3. 图像预处理 =================
List<Mat> images = new List<Mat>();
foreach (var path in imagePaths)
{
Mat img = Cv2.ImRead(path);
if (img.Empty())
{
Logger.Instance.INFO("Image not found!");
return;
}
images.Add(img);
}
(float[] inputData1, float[] scales1, int[] xOffsets1, int[] yOffsets1) = PreProcessBatch(images);
sw.Start();
(float[] inputData, float[] scales, int[] xOffsets, int[] yOffsets) = PreProcessBatch(images);
sw.Stop();
Logger.Instance.INFO($"Pre-processing time: {sw.ElapsedMilliseconds} ms");
// ================= 4. 推理 =================
// 准备主机内存接收结果
float[] outputData1 = new float[imagePaths.Count() * outputChannels * OutputSize];
// 将数据从主机 拷贝到设备
inputGpuMemory.copyFromHostAsync(inputData, cudaStream);
// 执行推理 (enqueueV3 是异步的)
executionContext.executeV3(cudaStream);
// 等待推理完成
cudaStream.Synchronize();
// 将结果从设备 拷贝回主机
// 这里的拷贝是同步的,会等待 GPU 计算完成
outputGpuMemory.copyToHostAsync(outputData1, cudaStream);
sw.Restart();
// 准备主机内存接收结果
float[] outputData = new float[imagePaths.Count() * outputChannels * OutputSize];
// 将数据从主机 拷贝到设备
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. 后处理 =================
List<List<ObbData>> results1 = PostProcessBatch(outputData, scales, xOffsets, yOffsets);
sw.Restart();
List<List<ObbData>> results = PostProcessBatch(outputData, scales, xOffsets, yOffsets);
sw.Stop();
Logger.Instance.INFO($"Post-processing time: {sw.ElapsedMilliseconds} ms");
// ================= 6. 结果可视化 =================
List<Mat> resultMats = new List<Mat>();
for(int i = 0; i < results.Count; ++i)
{
resultMats.Add(DrawObbResult(results[i], images[i]));
}
Mat putResultImgs = StitchHorizontalWithPadding(resultMats);
Cv2.ImWrite("YOLO11-OBB Result.png", putResultImgs);
Cv2.ImShow("YOLO11-OBB Result", putResultImgs);
Cv2.WaitKey(0);
}
}
}
}
/// <summary>
/// 图像预处理:Letterbox 缩放、归一化、HWC 转 CHW
/// </summary>
private static (float[], float[] , int[] , int[] ) PreProcessBatch(List<Mat> imgs)
{
int dataLen = 3 * InputSize * InputSize;
float[] chwData = new float[imgs.Count * dataLen];
float[] scales = new float[imgs.Count];
int[] xOffsets = new int[imgs.Count];
int[] yOffsets = new int[imgs.Count];
Parallel.For(0, imgs.Count, i =>
{
Mat img = imgs[i];
// 转换颜色空间 BGR -> RGB
Mat rgbImg = new Mat();
Cv2.CvtColor(img, rgbImg, ColorConversionCodes.BGR2RGB);
// 计算 Letterbox 缩放比例
int maxDim = Math.Max(rgbImg.Width, rgbImg.Height);
scales[i] = (float)maxDim / InputSize;
// 计算缩放后的尺寸
int newWidth = (int)(rgbImg.Width / scales[i]);
int newHeight = (int)(rgbImg.Height / scales[i]);
// 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);
// 计算粘贴位置 (居中)
xOffsets[i] = (InputSize - newWidth) / 2;
yOffsets[i] = (InputSize - newHeight) / 2;
// 将图像拷贝到 Canvas 中央
Rect roi = new Rect(xOffsets[i], yOffsets[i], 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);
// 拷贝数据:R通道 -> C通道 -> B通道 (OpenCV Split 出来顺序是 B, G, R,对应索引 0, 1, 2)
int channelSize = InputSize * InputSize;
// 将 R, G, B 依次拷入数组
Marshal.Copy(channels[0].Data, chwData, dataLen * i, channelSize); // R
Marshal.Copy(channels[1].Data, chwData, dataLen * i + channelSize, channelSize); // G
Marshal.Copy(channels[2].Data, chwData, dataLen * i + channelSize * 2, channelSize); // B
// 释放临时 Mat
rgbImg.Dispose();
resizedImg.Dispose();
paddedImg.Dispose();
floatImg.Dispose();
foreach (var c in channels) c.Dispose();
});
return (chwData, scales, xOffsets, yOffsets);
}
/// <summary>
/// 后处理:解析 TensorRT 输出、NMS 过滤
/// </summary>
private static List<List<ObbData>> PostProcessBatch(float[] result, float[] scales, int[] xOffsets, int[] yOffsets)
{
List<ObbData>[] obbDatas = new List<ObbData>[scales.Length];
Parallel.For(0, scales.Length, b =>
{
List<RotatedRect> boxes = new List<RotatedRect>();
List<float> confidences = new List<float>();
List<int> classIds = new List<int>();
// 遍历所有预测框 (OutputSize)
// 数据布局: [4(box) + 15(classes) + 1(angle)] * OutputSize
// 展平数组中,同一属性的数据是连续存储的,例如所有 cx 在一起,所有 cy 在在一起...
int stride = OutputSize; // 步长,不同属性在数组中的偏移量
int resultDataOffset = OutputSize * (CategoryNum + 5) * b;
for (int i = 0; i < OutputSize; i++)
{
// 查找最大类别概率及其索引
float maxConf = 0;
int maxClassId = -1;
// 遍历类别
for (int c = 0; c < CategoryNum; c++)
{
// 数组索引:(坐标/角度偏移量 + 类别偏移) * 框索引
// 注意:原始代码中 result[outputSize * j + i] 这种访问方式基于 Transposed 数据布局
float conf = result[(4 + c) * stride + i + resultDataOffset];
if (conf > maxConf)
{
maxConf = conf;
maxClassId = c;
}
}
// 置信度过滤
if (maxConf > ConfThreshold)
{
// 提取坐标 (cx, cy, w, h)
float cx = result[0 * stride + i + resultDataOffset];
float cy = result[1 * stride + i + resultDataOffset];
float w = result[2 * stride + i + resultDataOffset];
float h = result[3 * stride + i + resultDataOffset];
// 提取角度 (通常在第 5 个位置,即类别之前)
float angleRad = result[(CategoryNum + 4) * stride + i + resultDataOffset];
// 还原坐标到原图尺寸
float rx = (cx - xOffsets[b]) * scales[b];
float ry = (cy - yOffsets[b]) * scales[b];
float rw = w * scales[b];
float rh = h * scales[b];
// 将弧度转换为角度
// Normalize angle to [-π/2, π/2] range
// 将角度归一化到[-π/2, π/2]范围
if (angleRad >= Math.PI && angleRad <= 0.75 * Math.PI)
{
angleRad -= (float)Math.PI;
}
float angleDeg = angleRad * (float)(180f / Math.PI); // Convert to degrees/转换为角度制
boxes.Add(new RotatedRect(new Point2f(rx, ry), new Size2f(rw, rh), angleDeg));
confidences.Add(maxConf);
classIds.Add(maxClassId);
}
}
// 执行 NMS (旋转框 NMS)
// OpenCV 的 NMSBoxes 支持 RotatedRect
int[] indices;
CvDnn.NMSBoxes(boxes, confidences, ConfThreshold, NmsThreshold, out indices);
List<ObbData> finalResults = new List<ObbData>();
foreach (int idx in indices)
{
finalResults.Add(new ObbData
{
index = classIds[idx],
score = confidences[idx],
box = boxes[idx]
});
}
obbDatas[b] = finalResults;
});
return obbDatas.Select(x => x?.ToList() ?? new List<ObbData>()).ToList();
}
/// <summary>
/// 绘制旋转检测结果
/// </summary>
public static Mat DrawObbResult(List<ObbData> results, Mat image)
{
// 克隆图像以免修改原图
Mat mat = image.Clone();
foreach (var item in results)
{
// 获取旋转矩形的四个顶点
Point2f[] points = item.box.Points();
// 绘制多边形框
for (int j = 0; j < 4; j++)
{
Cv2.Line(mat, (Point)points[j], (Point)points[(j + 1) % 4],
new Scalar(0, 255, 0), 2);
}
// 绘制标签 (类别 - 置信度)
string label = $"{item.index} - {item.score:F2}";
Point2f textPos = points[0]; // 左上角
Cv2.PutText(mat, label, (Point)textPos, HersheyFonts.HersheySimplex, 0.8,
new Scalar(255, 0, 0), 2);
}
return mat;
}
public class ObbData
{
public int index;
public float score;
public RotatedRect box;
}
/// <summary>
/// 智能水平拼接:自动处理高度不一致的图片
/// </summary>
/// <param name="images">图片列表</param>
/// <param name="backgroundColor">填充背景颜色,默认为黑色</param>
/// <returns>拼接后的 Mat</returns>
public static Mat StitchHorizontalWithPadding(List<Mat> images, Scalar? backgroundColor = null)
{
if (images == null || images.Count == 0)
return new Mat();
// 1. 找到所有图片中的最大高度
int maxHeight = images.Max(img => img.Rows);
// 计算总宽度
int totalWidth = images.Sum(img => img.Cols);
// 2. 准备结果画布
Mat result = new Mat(maxHeight, totalWidth, images[0].Type(), backgroundColor ?? Scalar.Black);
// 3. 将每一张图片复制到画布的对应位置
int currentX = 0; // 当前 X 轴偏移量
foreach (var img in images)
{
if (img.Empty()) continue;
// 计算当前图片需要垂直偏移多少(底部对齐逻辑)
// 如果想顶部对齐,yOffset = 0
// 如果想居中,yOffset = (maxHeight - img.Rows) / 2
int yOffset = maxHeight - img.Rows;
// 定义 ROI (感兴趣区域)
Rect roi = new Rect(currentX, yOffset, img.Cols, img.Rows);
// 将原图片拷贝到结果图的 ROI 区域
img.CopyTo(new Mat(result, roi));
// 移动 X 轴指针
currentX += img.Cols;
}
return result;
}
}
}
下图为上述程序运行后的输出,模型输入形状为 -1x3x1024x1024,其中Batch Size为动态输入;项目示例使用了四张图片进行同时推理,开启并行处理后,四张图像预处理时间仅用21ms,推理时间为25ms,后处理时间为26ms,累计时间为72ms.

下图为推理结果展示:

性能测试(不同 Batch Size):
为了探究不同Batch Size推理时间差异,此处对不同Batch Size进行了测试,测试结果如下:
| Batch Size | 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 前处理 (ms ) | 9 | 13 | 27 | 38 | 56 | 59 | 63 | 83 | 96 | 105 | 118 | 130 | 144 |
| 模型推理 (ms) | 7 | 15 | 24 | 36 | 48 | 60 | 96 | 84 | 93 | 153 | 120 | 133 | 203 |
| 后处理 (ms) | 25 | 26 | 26 | 26 | 28 | 27 | 27 | 28 | 28 | 28 | 27 | 31 | 29 |
🔗程序路径链接:完整程序已经上传到GitHub,请自行下载,链接为:
https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp3.0/samples/YoloObbBatchInfer
示例 5:并行推理
使用一个 Runtime 创建多执行上下文,实现多并行推理。
核心代码:
using JYPPX.TensorRtSharp.Cuda;
using JYPPX.TensorRtSharp.Nvinfer;
using OpenCvSharp;
using OpenCvSharp.Dnn;
using System.Diagnostics;
using System.Runtime.InteropServices;
using static OpenCvSharp.FileStorage;
namespace YoloDetParallelInfer
{
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";
Mat img = Cv2.ImRead(imagePath);
if (img.Empty())
{
Logger.Instance.INFO("Image not found!");
return;
}
// ================= 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();
runtime.setMaxThreads(6);
// 创建 CudaEngine (此处使用 using 确保推理完成后引擎被销毁)
using (CudaEngine cudaEngine = runtime.deserializeCudaEngineByBlob(engineData, (ulong)engineData.Length))
{
// ================= 2. 初始化推理上下文与显存 =================
Stopwatch totalSw = new Stopwatch();
totalSw.Start();
Parallel.For(0, 24, b =>
{
// 创建执行上下文
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]}");
// 计算所需显存大小
// 输入: Batch=1, Channel=3, Height=640, Width=640
ulong inputSizeInBytes = 1 * 3 * InputSize * InputSize;
// 输出: Batch=1, Channels=CategoryNum+4(box)+1(angle), Num=8400
int outputChannels = CategoryNum + 4; // 4坐标 + N类别
ulong outputSizeInBytes = (ulong)(1 * outputChannels * OutputSize);
Stopwatch sw = new Stopwatch();
// 分配 GPU 显存
using (Cuda1DMemory<float> inputGpuMemory = new Cuda1DMemory<float>(inputSizeInBytes))
using (Cuda1DMemory<float> outputGpuMemory = new Cuda1DMemory<float>(outputSizeInBytes))
{
// 绑定显存地址到 TensorRT 上下文
executionContext.setInputTensorAddress("images", inputGpuMemory.get());
executionContext.setOutputTensorAddress("output0", outputGpuMemory.get());
// 预热推理 (可选,但推荐,尤其是首次推理时)
executionContext.executeV3(cudaStream);
cudaStream.Synchronize();
// ================= 3. 图像预处理 =================
sw.Start();
float[] inputData = PreProcess(img, out float scale, out int xOffset, out int yOffset);
sw.Stop();
Logger.Instance.INFO($"Channel {b}: 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($"Channel {b}: Inference time: {sw.ElapsedMilliseconds} ms");
// ================= 5. 后处理 =================
sw.Restart();
List<DetData> results = PostProcess(outputData, scale, xOffset, yOffset);
sw.Stop();
Logger.Instance.INFO($"Channel {b}: Post-processing time: {sw.ElapsedMilliseconds} ms");
// ================= 6. 结果可视化 =================
//Mat resultImg = DrawDetResult(results, img);
//Cv2.ImShow("YOLO11-DET Result", resultImg);
//Cv2.WaitKey(0);
}
}
});
totalSw.Stop();
Logger.Instance.INFO($"Total time for 8 inferences: {totalSw.ElapsedMilliseconds} ms");
}
}
/// <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();
resizedImg.Dispose();
paddedImg.Dispose();
floatImg.Dispose();
foreach (var c in channels) c.Dispose();
return chwData;
}
/// <summary>
/// 后处理:解析 TensorRT 输出、NMS 过滤
/// </summary>
private static List<DetData> PostProcess(float[] result, float scale, int xOffset, int yOffset)
{
List<Rect> boxes = new List<Rect>();
List<float> confidences = new List<float>();
List<int> classIds = new List<int>();
// 遍历所有预测框 (OutputSize)
// 数据布局: [4(box) + 80(classes)] * OutputSize
// 展平数组中,同一属性的数据是连续存储的,例如所有 cx 在一起,所有 cy 在在一起...
int stride = OutputSize; // 步长,不同属性在数组中的偏移量
for (int i = 0; i < OutputSize; i++)
{
// 查找最大类别概率及其索引
float maxConf = 0;
int maxClassId = -1;
// 遍历类别
for (int c = 0; c < CategoryNum; c++)
{
// 数组索引:(坐标/角度偏移量 + 类别偏移) * 框索引
// 注意:原始代码中 result[outputSize * j + i] 这种访问方式基于 Transposed 数据布局
float conf = result[(4 + c) * stride + i];
if (conf > maxConf)
{
maxConf = conf;
maxClassId = c;
}
}
// 置信度过滤
if (maxConf > ConfThreshold)
{
// 提取坐标 (cx, cy, w, h)
float cx = result[0 * stride + i];
float cy = result[1 * stride + i];
float w = result[2 * stride + i];
float h = result[3 * stride + i];
// 还原坐标到原图尺寸
int rx = (int)((cx - xOffset - 0.5 * w) * scale);
int ry = (int)((cy - yOffset - 0.5 * h) * scale);
int rw = (int)(w * scale);
int rh = (int)(h * scale);
boxes.Add(new Rect(rx, ry, rw, rh));
confidences.Add(maxConf);
classIds.Add(maxClassId);
}
}
// 执行 NMS (旋转框 NMS)
// OpenCV 的 NMSBoxes 支持 RotatedRect
int[] indices;
CvDnn.NMSBoxes(boxes, confidences, ConfThreshold, NmsThreshold, out indices);
List<DetData> finalResults = new List<DetData>();
foreach (int idx in indices)
{
finalResults.Add(new DetData
{
index = classIds[idx],
score = confidences[idx],
box = boxes[idx]
});
}
return finalResults;
}
/// <summary>
/// 绘制检测结果(水平矩形框)
/// </summary>
/// <param name="results">检测结果列表</param>
/// <param name="image">原始图像</param>
/// <returns>绘制后的图像</returns>
public static Mat DrawDetResult(List<DetData> results, Mat image)
{
// 克隆图像以免修改原图
Mat mat = image.Clone();
foreach (var item in results)
{
// 1. 绘制矩形框
// Rect 结构包含 X, Y, Width, Height
Cv2.Rectangle(mat, item.box, new Scalar(0, 255, 0), thickness: 2);
// 2. 准备标签文本 (类别ID - 置信度)
string label = $"{item.index} - {item.score:F2}";
// 3. 计算文本的尺寸,用于绘制背景
int baseLine = 1;
Size textSize = Cv2.GetTextSize(label, HersheyFonts.HersheySimplex, 0.6, 1, out baseLine);
// 4. 绘制标签背景(半透明黑色矩形),防止文字与背景混淆
// 位置:矩形左上角略微上移,或者直接贴着左上角
Point labelPosition = new Point(item.box.X, item.box.Y - (int)textSize.Height - 5);
// 确保标签不画出图像边界
if (labelPosition.Y < 0) labelPosition.Y = item.box.Y + (int)textSize.Height + 5;
Rect labelBgRect = new Rect(labelPosition.X,
labelPosition.Y - (int)textSize.Height, // OpenCV GetTextSize 返回的高度是基线到底部的距离,需调整
(int)textSize.Width,
(int)textSize.Height + (int)baseLine);
// 如果背景框也在图像范围内,则绘制
// 注意:这里简化处理,直接画在框上方
Cv2.Rectangle(mat,
new Point(item.box.X, item.box.Y - textSize.Height - 5),
new Point(item.box.X + textSize.Width, item.box.Y),
new Scalar(0, 255, 0),
thickness: -1); // -1 表示填充
// 5. 绘制文本(白色文字)
Cv2.PutText(mat,
label,
new Point(item.box.X, item.box.Y - 5),
HersheyFonts.HersheySimplex,
0.6,
new Scalar(0, 0, 0),
1);
}
return mat;
}
public class DetData
{
public int index;
public float score;
public Rect box;
}
}
}
为了方便编写代码,上述并行处理即使时间包括了推理上下文的创建、推理预热等步骤,所以实际时间会偏长,上述程序运行后输出如下所示:

并行测试结果:
同时为了比较不同并行数,测试了从1到24不同并行数的情况,推理总时间如下:
| 并行数 | 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 推理总时间 (ms) | 80 | 85 | 95 | 115 | 130 | 155 | 180 | 210 | 230 | 255 | 270 | 285 | 310 |
🔗程序路径链接:完整程序已经上传到GitHub,请自行下载,链接为:
https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp3.0/samples/YoloObbBatchInfer
七、异常处理
TensorRtSharp 提供了完善的异常处理机制。
try
{
Runtime runtime = new Runtime();
byte[] data = File.ReadAllBytes("model.engine");
using CudaEngine engine = runtime.deserializeCudaEngineByBlob(data, (ulong)data.Length);
}
catch (TrtException ex)
{
// TensorRT 特定错误
Console.WriteLine($"TensorRT Error: {ex.ErrMsg}");
Console.WriteLine($"Status: {ex.Status}");
}
catch (CudaException ex)
{
// CUDA 运行时错误
Console.WriteLine($"CUDA Error: {ex.Message}");
Console.WriteLine($"Status: {ex.Status}");
}
catch (InitException ex)
{
// 初始化错误
Console.WriteLine($"Initialization Failed: {ex.Message}");
Console.WriteLine($"Status: {ex.Status}");
}
异常类型说明:
| 异常类型 | 说明 |
|---|---|
TrtException |
TensorRT API 错误(20+ 错误码) |
CudaException |
CUDA 运行时错误(40+ 错误码) |
InitException |
库初始化错误 |
八、日志系统
TensorRtSharp 提供了单例日志系统。
// 获取日志实例
Logger logger = Logger.Instance;
// 设置日志级别
logger.SetThreshold(LoggerSeverity.kINFO); // INFO、WARNING、ERROR
// 设置自定义回调
logger.SetCallback((message) =>
{
Console.WriteLine($"[TensorRT] {message}");
});
// 记录日志
logger.INFO("Engine building started...");
logger.WARNING("FP16 not supported, falling back to FP32");
logger.ERROR("Failed to parse ONNX model");
// 静默模式
logger.SetThreshold(LoggerSeverity.kINTERNAL_ERROR); // 仅严重错误
九、与其他库的对比
| 特性 | TensorRtSharp | ML.NET | ONNX Runtime |
|---|---|---|---|
| 编程语言 | C# | C# | C++/Python |
| API 类型 | 托管封装 | 托管库 | 原生绑定 |
| 性能 | 原生速度 | 中等 | 原生速度 |
| 易用性 | 高 | 高 | 中等 |
| TensorRT 支持 | 完整 | 无 | 有限 |
| 自定义算子 | 支持 | 困难 | 支持 |
| 动态形状 | 支持 | 有限 | 支持 |
| 多 GPU | 支持 | 有限 | 支持 |
十、常见问题
问题一:找不到 DLL 模块
错误信息:
Unable to load DLL 'TensorRT-C-API' or one of its dependencies: 找不到指定的模块。
解决方案:
- 检查是否安装了对应版本的 Runtime NuGet 包
- 确认系统 PATH 环境变量中包含 TensorRT 的 lib 目录和 CUDA 的 bin 目录
- 确认 TensorRT 版本为 10.x 系列
错误截图:


问题二:SEHException 异常
错误信息:
System.Runtime.InteropServices.SEHException: "External component has thrown an exception."
可能原因:
- TensorRT 版本不匹配(必须使用 10.x)
- CUDA 版本不兼容
- 模型文件损坏
解决方案:
- 确认 TensorRT 版本为 10.x
- 检查 CUDA 版本是否匹配
- 重新生成 Engine 文件
错误截图:

问题三:System.ExecutionEngineException 异常
错误信息:
System.ExecutionEngineException
可能原因:
- 模型文件与设备不匹配
解决方案:
- 在当前设备上重新生成模型文件
错误截图:


十一、总结
TensorRtSharp 是一个功能完整、设计精良的 TensorRT C# 封装库,它填补了 .NET 生态在高性能深度学习推理方面的空白。通过提供类型安全的 API、自动资源管理和完善的异常处理,TensorRtSharp 让 C# 开发者能够充分发挥 GPU 的计算能力,而无需面对复杂的原生代码。
核心优势
✅ 完整的 API 覆盖:支持 TensorRT 核心功能
✅ 类型安全:强类型系统,编译时错误检查
✅ 自动资源管理:RAII + Dispose 模式
✅ 高性能:异步执行、多流并行
✅ 易用性:直观的 API、详细注释
✅ 跨平台:支持 Windows/Linux
✅ 开箱即用:NuGet 包含所有依赖
适用场景
无论您是构建以下类型的应用,TensorRtSharp 都是您的理想选择:
- 🎯 实时视觉应用:目标检测、图像分割、姿态估计
- 🎤 语音处理:语音识别、语音合成
- 🚀 边缘计算:嵌入式设备推理
立即开始
安装命令:
dotnet add package JYPPX.TensorRT.CSharp.API
dotnet add package JYPPX.TensorRT.CSharp.API.runtime.win-x64.cuda12
GitHub 仓库:
https://github.com/guojin-yan/TensorRT-CSharp-API
立即安装并体验 C# 世界中的 GPU 推理极致性能吧!
技术支持
如有问题或建议,欢迎通过以下方式交流:
- 📧 GitHub Issues:在项目仓库提 Issue 或 Pull Request
- 💬 QQ 交流群:加入 945057948,回复更方便更快哦

作者:Guojin Yan
版本:0.0.5
最后更新:2026年1月
【文章声明】
本文主要内容基于作者的研究与实践,部分表述借助AI工具进行了辅助优化。由于技术局限性,文中可能存在错误或疏漏之处,恳请各位读者批评指正。如果内容无意中侵犯了您的权益,请及时通过公众号后台与我们联系,我们将第一时间核实并妥善处理。感谢您的理解与支持!

TensorRtSharp 3.0 是一个为 C# 开发者打造的 TensorRT 封装库,通过 NuGet 一键安装,提供完整的 GPU 推理加速功能。该库基于 TensorRT 10.x 开发,支持 CUDA 11/12,具备类型安全、自动资源管理等特性,显著提升 .NET 环境下的深度学习推理性能(速度提升 2-10 倍,显存降低 50%+)。安装简单,只需添加两个 NuGet 包,并通过环境变量配置原生库路径即可使用。推荐配置为 CUDA 11.6 + TensorRT 10.13.0.35,支持
浙公网安备 33010602011771号