C# 使用 TensorRtSharp4.0 运行 YOLOv8n Pose

本文从一个空的控制台项目开始,演示如何获取官方 YOLOv8n Pose 权重、转换 ONNX、生成独立参考、引用 TensorRtSharp4.0 的公开 managed/bridge 包,并在 TensorRT 10.11 上完成人体姿态估计。最终结果包含 17 个 COCO 关键点、人体骨架、原图坐标叠加、JSON 报告和严格参考输出比较。

本文只执行本地构建和验证。没有创建版本、Release 或上传包;CUDA、cuDNN 和 TensorRT 均由使用者自行安装。

本文使用的项目与库

TensorRtSharp4.0 将本次流程拆成三个职责清晰的包:

包 职责
JYPPX.TensorRT.CSharp.API TensorRT/CUDA 托管接口、ONNX 加载、binding 和执行。
applications/YoloVision 图像预处理、YOLOv8 Pose 解码、NMS、JSON 和 SVG 可视化。
JYPPX.TensorRT.CSharp.API.Runtime.win-x64.trt10.11.cuda12.9.cudnn9.22.Bridge 只携带项目自己的 jyppxtrtbridge.dll,不携带 NVIDIA 运行库。

示例程序使用 .NET 8。验证环境为 NVIDIA GeForce RTX 3060 Laptop GPU、驱动 576.02、TensorRT 10.11.0、CUDA Toolkit 12.9 和 .NET SDK 10.0.301。包名称中的环境矩阵必须与本机安装匹配。

模型获取与许可证

权重来自 Ultralytics 官方 Release:

https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8n-pose.pt

项目将来源固定到 ultralytics v8.3.0 和源码提交 6e43d1e1e5db72afbf686dee6745669bcb124b0a。权重长度为 6,832,633 字节,SHA256 为:

c6fa93dd1ee4a2c18c900a45c1d864a1c6f7aba75d84f91648a30b7fb641d212

权重采用 AGPL-3.0-only。本文不把 .pt 或 ONNX 上传到 Git;正式使用前应根据业务分发方式自行完成许可证审查。仓库内的获取脚本会校验 URL、长度和 SHA256:

$repoRoot = Resolve-Path .
$workspaceRoot = Split-Path $repoRoot -Parent
$assetRoot = Join-Path $workspaceRoot 'downloads/yolov8n-pose-ultralytics-v8.3.0'

pwsh -NoProfile -ExecutionPolicy Bypass `
  -File (Join-Path $repoRoot 'eng/Acquire-YoloV8PoseOfficialAssets.ps1') `
  -OutputRoot $assetRoot `
  -PythonPath $env:JYPPX_YOLO_PYTHON

文章配图使用 Wikimedia Commons 的 Liverpool Street Bus station 2025,许可证为 CC0 1.0。下载 JPEG 的 SHA256 为 52b889d4fc9baea772ba2d9bbdfdef8b70710f993d7f27d193a965b11e708bcb,转换后 PPM 的 SHA256 为 80715af66669147b049fec9386152bd45505f4e494a65079fdc55404d4589b8e。

ONNX 转换与暂存

在独立 Python 环境安装固定版 Ultralytics、ONNX 和 ONNX Runtime,然后执行官方导出器:

$Weights = Join-Path $assetRoot 'source/yolov8n-pose.pt'
$modelRoot = Join-Path $workspaceRoot 'models/YoloVision/Pose/yolov8n-pose-ultralytics-v8.3.0'
New-Item -ItemType Directory -Force $modelRoot | Out-Null

yolo export model=$Weights format=onnx imgsz=640 opset=17 simplify=True dynamic=False batch=1 device=cpu
Move-Item (Join-Path (Split-Path $Weights) 'yolov8n-pose.onnx') $modelRoot -Force

转换模型暂存在外层 models/YoloVision/Pose/yolov8n-pose-ultralytics-v8.3.0/yolov8n-pose.onnx,由 .gitignore 排除。文件长度为 13,514,570 字节,SHA256 为:

ed1e8d2d2aeb8a2c66e642a16295a72a2990393e3a3843325537da7e11c8a899

静态 ONNX 合同是:

images: float32[1,3,640,640]
output0: float32[1,56,8400]

56 个通道由 4 个 box 通道、1 个 person 分类通道和 17 * 3 个关键点通道组成,没有独立 objectness。通道起点固定为 4 + 1 = 5。

独立参考必须读取 C# 实际生成的 tensor,而不是另做一套图片预处理:

$artifactRoot = Join-Path $workspaceRoot 'downloads/article-assets/yolovision-yolov8n-pose'
$model = Join-Path $modelRoot 'yolov8n-pose.onnx'
$tensor = Join-Path $artifactRoot 'pose-csharp-input.fp32.bin'
$image = Join-Path $workspaceRoot 'downloads/article-assets/pose-input.jpg'

& $env:JYPPX_YOLO_PYTHON `
  (Join-Path $repoRoot 'eng/Invoke-YoloVisionPoseReference.py') `
  --onnx-model $model --weights $Weights --input-tensor $tensor `
  --image $image --output-directory (Join-Path $artifactRoot 'reference')

脚本分别输出 ONNX Runtime CPU 的 470,400 值 raw reference,以及 Ultralytics/PyTorch 的 box、score 和 17 个关键点参考。

使用公开包准备应用

applications/YoloVision 是完整应用并设置为 IsPackable=false。它通过共享 props 引用已发布的 JYPPX.TensorRT.CSharp.API 4 系列包,以及作者维护的 OpenCV-CSharp-API。应用本身不发布 YoloVision 案例 NuGet 包。

新建仓库外项目时,可以让 NuGet 获取当前公开预览版,而不在文章中写死具体版本:

dotnet add package JYPPX.TensorRT.CSharp.API --version "4.0.0-*"
dotnet add package JYPPX.OpenCV.CSharp.API --prerelease
dotnet add package JYPPX.OpenCV.runtime.win-x64 --prerelease
dotnet add package JYPPX.TensorRT.CSharp.API.Runtime.win-x64.trt10.11.cuda12.9.cudnn9.22.Bridge --version "4.0.0-*"

4.0.0-* 只跟随当前 4.0.0 预览线,避免误选 API 不兼容的历史 4.0.6170。最后一个包 ID 必须按目标机器环境替换。它只包含项目自有 bridge;CUDA、cuDNN、TensorRT 和 NVRTC 继续由用户安装。仓库中的 YoloVision 项目直接运行当前源码,但 TensorRT/CUDA API 来自公开 NuGet 包。

编写程序入口

YoloVision 的命令入口已经封装参数解析、预处理、TensorRT 执行、Pose 后处理和结果写出,因此 Program.cs 只需两行:

using YoloVisionSample;

return YoloVisionCommand.Run(args);

YoloVisionCommand 不把 native 指针暴露给消费程序。关键配置均通过命令行显式声明,特别是 class-count=1、keypoint-count=17 和 aux-channel-start=5,避免依靠文件名猜测输出合同。

编译并运行

还原并编译使用公开包的 YoloVision 应用:

dotnet restore ./applications/YoloVision/YoloVision.csproj
dotnet build ./applications/YoloVision/YoloVision.csproj -c Release --no-restore

设置用户已经安装的 TensorRT 路径,再运行完整流程:

$env:JYPPX_TENSORRT_ROOT = $env:TENSORRT_PATH
$reference = Join-Path $artifactRoot 'reference/output0.reference.json'
$resultJson = Join-Path $artifactRoot 'pose-output.json'
$resultSvg = Join-Path $artifactRoot 'pose-annotated.svg'

dotnet run --project ./applications/YoloVision -c Release --no-build -- `
  --model $model --labels $labels --input-shape 1x3x640x640 `
  --tensor-rt-line 10 --family v8 --task pose `
  --layout channels-first --has-objectness false --class-count 1 `
  --confidence 0.25 --iou-threshold 0.45 --top-k 10 `
  --keypoint-count 17 --aux-channel-start 5 `
  --image $inputPpm --preprocessed-output $tensor `
  --reference-outputs "output0:$reference" `
  --reference-abs-tolerance 1.25 --reference-rel-tolerance 0.05 `
  --reference-nan-policy reject --reference-infinity-policy exact --noTF32 `
  --output $resultJson --visualization $resultSvg `
  --visualization-background $inputJpeg

其中 labels、inputPpm 和 inputJpeg 都由调用方通过变量指向资产目录。正文不依赖作者机器上的绝对路径。

已验证结果

这次运行生成的原图叠加结果如下。两个 person 都绘制了检测框、可见关键点和 COCO 骨架:

YOLOv8n Pose 原图姿态结果

程序运行页面如下。终端截图来自本次真实运行的 stdout,只对工作区路径进行了脱敏排版,数值、tensor 合同和验证结论保持原样:

YOLOv8n Pose TensorRT 运行页面

两张图都来自同一次真实 TensorRT 执行:第一张由该次执行写出的 SVG 渲染,第二张来自该次执行的 stdout。关键结果为:

项目 实测值
TensorRT 退出码 0
输入 tensor 1,228,800 个 float32,SHA256 050935ebf471ec32ab4327d9f5643f0fe1a203289088895205e732e448a8d225
输出 tensor output0:[1,56,8400]
比较值 470,400
mismatch / first mismatch 0 / -1
最大绝对误差 0.001373291
TensorRT 推理耗时 10.494 ms
姿态数量 2
两个目标分数 0.467716、0.441625
每个目标关键点 17
SVG 骨架边 27 条可见边

独立 PyTorch 后处理使用完全相同的 C# tensor。两个匹配框的 IoU 分别为 0.999999 和 0.999998;可见关键点最大坐标误差分别为 0.000095 和 0.000126 像素,比较结果为通过。

受控负例把 raw reference 第 0 个值增加 100,程序必须返回 1。实测为 Mismatches=1、FirstMismatch=0、OutputValidated=False 和 YoloVision Passed=False,证明参考输出不匹配会阻断成功状态。

常见问题

能生成 engine,但 Poses=0

先检查输出是否确实为 [1,56,8400],并确认 --class-count 1 --has-objectness false --aux-channel-start 5。错误的类别数会把关键点通道当作分类分数。

框正确,关键点属于另一个人

NMS 后必须通过 YoloDetection.SourceIndex 回到原始候选行选择关键点,不能使用 NMS 结果数组下标。YoloVision 当前实现保留了这条绑定关系。

点位整体偏移

可视化需要同时传入 --image 与 --visualization-background。程序使用预处理记录中的 resize、padding 和 scale 把模型坐标逆变换到原图;背景图尺寸必须与预处理源图一致。

bridge 已复制但找不到 TensorRT DLL

bridge-only 包不携带 CUDA、cuDNN 或 TensorRT。请安装与 bridge 包键一致的 NVIDIA 运行环境,并让 JYPPX_TENSORRT_ROOT 或系统搜索路径指向 TensorRT 安装目录。

复查与边界

发布前 local-feed 证据复核

本文已经证明:

  • 官方 YOLOv8n Pose 权重可以按固定版本获取并转换为静态 ONNX;
  • 转换模型存放在外层 models,不进入 Git;
  • 仓库外项目可以通过公开 managed/bridge 包完成 restore、build 和真实 GPU 推理;
  • C# 预处理、TensorRT 全量 raw 输出、独立 PyTorch 后处理、JSON 和原图骨架结果能够相互追溯;
  • 受控 reference 变更会非零退出。

本文不表示包已发布到 NuGet 或 GitHub Packages,也不表示模型获得项目再分发授权。它不是 public-package、post-publish、Owner acceptance 或正式 Release 证明。模型、reference、tensor、日志和中间 SVG 继续保留在 Git 仓库外;Git 只保存技术文章、可公开再分发的两张 PNG 和不含模型本体的哈希记录。