一、前言

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 版本选择对应的包。

image-20260109233203782

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 环境变量中。

优点

  • 无需复制大量文件
  • 保持应用目录整洁
  • 便于版本管理和部署维护

配置步骤

  1. 设置 CUDA_PATH 环境变量

CUDA_PATH 配置

  1. 设置 PATH 环境变量

将以下路径添加到 PATH:

  • CUDA 的 bin 目录(如 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.6\bin
  • TensorRT 的 lib 目录(如 C:\TensorRT-10.13.0.35\lib

PATH 配置

💡 建议:优先使用环境变量方式配置,避免因文件冗余导致部署复杂。同时注意不同 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!");
        }
    }
}

程序运行结果:

ONNX 转换结果

🔗程序路径链接:完整程序已经上传到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 库中:

trtexec 位置

打开 CMD 并切换到该路径:

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(可选)

trtexec 转换中

(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();
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐