ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# + TensorRT + 工业相机:部署YOLO目标检测模型

C# + TensorRT + 工业相机:部署YOLO目标检测模型 在工业视觉检测场景中YOLO系列模型凭借速度与精度的平衡成为缺陷检测、工件定位、字符识别的主流方案。传统Python部署链路难以无缝集成C#工业上位机系统且端到端延迟较高。本文基于MVVM工业相机架构引入TensorRT推理加速实现采集、预处理、推理、结果叠加全链路C#原生落地满足工业现场实时性与稳定性要求。技术选型与环境准备核心技术栈工业视觉推理部署需要兼顾硬件兼容性、开发效率与运行性能技术栈选型如下UI与架构WPF CommunityToolkit.Mvvm沿用分层架构保证可维护性相机采集兼容Basler pylon、海康威视MVS等主流工业相机SDK推理引擎ONNX Runtime TensorRT Execution Provider微软官方维护C#原生集成无需手动封装原生API图像处理OpenCvSharp4负责图像格式转换、预处理与后处理模型支持YOLOv5/YOLOv8/YOLOv11导出ONNX格式后接入TensorRT优化工程化组件Serilog结构化日志、Microsoft.Extensions.DependencyInjection依赖注入环境版本匹配TensorRT对版本兼容性要求严格推荐使用以下匹配组合CUDA Toolkit 12.2TensorRT 10.0ONNX Runtime 1.19显卡驱动 ≥ 535.xYOLO导出ONNX opset17C#部署方案对比方案开发成本性能可维护性适用场景ONNX Runtime TensorRT EP低高好快速落地、多型号兼容原生TensorRT P/Invoke封装高极高一般极致性能定制OpenCv DNN CUDA低中好轻量模型、无TensorRT环境本文采用ONNX Runtime TensorRT EP方案在开发效率与性能之间取得最佳平衡适合工业项目快速落地。整体架构与数据流整体架构在原有MVVM相机采集框架基础上扩展推理链路采用显示与推理双队列解耦避免推理耗时影响画面实时性。核心数据流采集链路相机SDK回调输出原始帧写入帧缓冲队列显示链路UI线程从队列取最新帧直接刷新WriteableBitmap推理链路后台推理线程独立从队列取帧经预处理、TensorRT推理、后处理输出检测结果结果叠加检测结果调度至UI线程转换坐标后叠加在图像上显示与推理完全解耦推理延迟不会造成画面卡顿同时支持按需开启/关闭检测功能。核心功能分步实现1. 模型导出与环境配置首先将YOLO模型导出为ONNX格式推荐开启简化与内置NMS减少C#端后处理工作量。# YOLOv8导出ONNX示例 yolo export modelyolov8s.pt formatonnx opset17 simplifyTrue安装核心NuGet包Install-Package Microsoft.ML.OnnxRuntime.Gpu Install-Package OpenCvSharp4 Install-Package OpenCvSharp4.runtime.win Install-Package CommunityToolkit.Mvvm2. Model层TensorRT推理服务封装定义统一检测接口屏蔽底层推理引擎差异便于后续切换不同模型或推理框架。数据结构定义/// summary /// 检测结果 /// /summary public class DetectionResult { public int ClassId { get; set; } public string Label { get; set; } public float Confidence { get; set; } public float X { get; set; } public float Y { get; set; } public float Width { get; set; } public float Height { get; set; } } /// summary /// 检测服务接口 /// /summary public interface IObjectDetector : IDisposable { Task InitializeAsync(string modelPath); ListDetectionResult Detect(ImageFrame frame, float confThreshold 0.5f); }YOLO TensorRT推理实现核心包含引擎初始化、图像预处理、推理执行、结果后处理四部分。public class YoloTensorRtDetector : IObjectDetector { private InferenceSession _session; private readonly string[] _labels; private const int InputSize 640; public YoloTensorRtDetector(string[] labels) { _labels labels; } public Task InitializeAsync(string modelPath) { // 配置TensorRT执行提供程序 var options new SessionOptions(); options.AppendExecutionProvider_TensorRT(new TensorRTProviderOptions { DeviceId 0, Precision FP16, EngineCachePath ./engine_cache, TrtMaxWorkspaceSize 1 30 // 1GB工作空间 }); options.LogSeverityLevel OrtLoggingLevel.ORT_LOGGING_LEVEL_WARNING; _session new InferenceSession(modelPath, options); return Task.CompletedTask; } public ListDetectionResult Detect(ImageFrame frame, float confThreshold 0.5f) { // 1. 预处理Letterbox缩放 归一化 通道转换 using var mat new Mat(frame.Height, frame.Width, MatType.CV_8UC3, frame.PixelData); var ratio Math.Min((float)InputSize / frame.Width, (float)InputSize / frame.Height); var newWidth (int)(frame.Width * ratio); var newHeight (int)(frame.Height * ratio); var padX (InputSize - newWidth) / 2; var padY (InputSize - newHeight) / 2; using var resized new Mat(); Cv2.Resize(mat, resized, new Size(newWidth, newHeight)); using var padded new Mat(new Size(InputSize, InputSize), MatType.CV_8UC3, Scalar.Gray); resized.CopyTo(padded[new Rect(padX, padY, newWidth, newHeight)]); // 转换为RGB 归一化到[0,1] NCHW格式 var inputData new float[3 * InputSize * InputSize]; padded.ConvertTo(padded, MatType.CV_32FC3, 1.0 / 255); Cv2.CvtColor(padded, padded, ColorConversionCodes.BGR2RGB); padded.GetArray(out float[] pixelData); for (int c 0; c 3; c) { for (int y 0; y InputSize; y) { for (int x 0; x InputSize; x) { inputData[c * InputSize * InputSize y * InputSize x] pixelData[(y * InputSize x) * 3 c]; } } } // 2. 执行推理 var inputTensor new DenseTensorfloat(inputData, new[] { 1, 3, InputSize, InputSize }); using var results _session.Run(new ListNamedOnnxValue { NamedOnnxValue.CreateFromTensor(images, inputTensor) }); // 3. 后处理解析输出 坐标映射回原图 var output results.First().AsTensorfloat(); var detections new ListDetectionResult(); // YOLOv8带NMS输出格式[1, N, 6] for (int i 0; i output.Dimensions[1]; i) { var conf output[0, i, 4]; if (conf confThreshold) continue; var classId (int)output[0, i, 5]; var x1 (output[0, i, 0] - output[0, i, 2] / 2 - padX) / ratio; var y1 (output[0, i, 1] - output[0, i, 3] / 2 - padY) / ratio; var w output[0, i, 2] / ratio; var h output[0, i, 3] / ratio; detections.Add(new DetectionResult { ClassId classId, Label _labels[classId], Confidence conf, X x1, Y y1, Width w, Height h }); } return detections; } public void Dispose() { _session?.Dispose(); } }3. ViewModel层推理调度与状态管理在原有主视图模型基础上扩展检测功能采用独立后台线程处理推理避免阻塞采集与UI线程。public partial class MainViewModel { private readonly IObjectDetector _detector; private Task _detectionTask; private CancellationTokenSource _detectionCts; private bool _enableDetection; /// summary /// 检测结果集合UI绑定 /// /summary public ObservableCollectionDetectionResult DetectionResults { get; } new(); public bool EnableDetection { get _enableDetection; set { if (SetProperty(ref _enableDetection, value)) { if (value) StartDetection(); else StopDetection(); } } } public IRelayCommand ToggleDetectionCommand { get; } // 构造函数注入检测服务 public MainViewModel(ICamera camera, IObjectDetector detector, ILoggerMainViewModel logger) : base(logger) { _detector detector; ToggleDetectionCommand new RelayCommand(() EnableDetection !EnableDetection); } private void StartDetection() { _detectionCts new CancellationTokenSource(); _detectionTask Task.Run(async () { while (!_detectionCts.IsCancellationRequested) { if (_frameQueue.TryDequeue(out var frame)) { try { var results _detector.Detect(frame); // 调度到UI线程更新结果 await Application.Current.Dispatcher.InvokeAsync(() { DetectionResults.Clear(); foreach (var res in results) { DetectionResults.Add(res); } }, DispatcherPriority.Background); } catch (Exception ex) { Logger.Error(ex, 推理异常); } finally { frame.Dispose(); } } else { await Task.Delay(5, _detectionCts.Token); } } }, _detectionCts.Token); } private void StopDetection() { _detectionCts?.Cancel(); _detectionCts?.Dispose(); DetectionResults.Clear(); } }4. View层检测结果叠加显示使用Canvas叠加层实现检测框绘制通过数据绑定自动更新位置与样式。Grid !-- 底层图像显示 -- Image Source{Binding CameraDisplay} StretchUniform x:NameCameraImage/ !-- 顶层检测框叠加 -- Canvas ItemsControl ItemsSource{Binding DetectionResults} ItemsControl.ItemsPanel ItemsPanelTemplate Canvas/ /ItemsPanelTemplate /ItemsControl.ItemsPanel ItemsControl.ItemContainerStyle Style TargetTypeContentPresenter Setter PropertyCanvas.Left Value{Binding X}/ Setter PropertyCanvas.Top Value{Binding Y}/ /Style /ItemsControl.ItemContainerStyle ItemsControl.ItemTemplate DataTemplate Border BorderBrushLimeGreen BorderThickness2 Width{Binding Width} Height{Binding Height} TextBlock Text{Binding Label} ForegroundWhite BackgroundLimeGreen FontSize12 Padding2 0 VerticalAlignmentTop HorizontalAlignmentLeft/ /Border /DataTemplate /ItemsControl.ItemTemplate /ItemsControl /Canvas /Grid实际项目中需增加坐标转换逻辑将原始图像像素坐标映射为控件实际显示坐标适配图像拉伸与居中模式。5. 性能优化要点跳帧推理根据推理能力设置推理间隔如30帧采集对应10帧推理每3帧取1帧检测预处理加速使用OpenCvSharp CUDA模块完成缩放、格式转换降低CPU占用Tensor复用复用输入输出Tensor内存避免每次推理重新分配FP16量化TensorRT默认开启FP16在损失极小精度的前提下提升近一倍推理速度帧队列隔离显示与推理使用独立队列避免推理慢导致显示丢帧常见问题排查1. TensorRT引擎初始化失败现象启动时报错“EP initialization failed”常见原因CUDA/TensorRT版本与ONNX Runtime不匹配显卡算力低于模型要求工作空间不足解决方案严格对照官方版本兼容表降低ONNX opset版本增大TrtMaxWorkspaceSize配置2. 推理结果与Python端不一致现象同一张图检测结果偏差大、漏检或误检常见原因预处理逻辑不一致如BGR/RGB通道顺序颠倒、归一化系数错误、Letterbox填充方式不同解决方案单步对齐预处理每一步输出确保与Python端像素值完全一致使用带内置NMS的ONNX模型3. 开启检测后画面卡顿现象开启推理后显示帧率明显下降常见原因在采集回调线程中同步执行推理阻塞了采集与显示解决方案严格使用独立线程执行推理通过帧队列解耦采集与推理使用低优先级调度UI结果更新4. 检测框位置偏移现象检测框与目标实际位置不重合常见原因坐标映射未考虑图像填充、显示缩放比例解决方案计算图像在控件中的实际显示区域与偏移量建立完整的“模型坐标→原图坐标→显示坐标”映射链5. 长时间运行内存上涨现象连续运行数小时后内存持续增长常见原因Mat对象未释放、Tensor频繁创建、帧队列积压解决方案所有Mat对象使用using释放复用输入输出缓冲区设置帧队列最大长度满员自动丢弃旧帧工程化落地建议引擎缓存管理首次运行生成的TensorRT引擎文件持久化保存后续启动直接加载缩短初始化时间多模型切换支持不同检测场景加载对应模型通过工厂模式管理多个推理实例结果持久化检测异常时自动保存原图与检测结果支持问题溯源与数据回流异常降级机制推理服务异常时自动关闭检测功能保证基础采集显示正常运行性能监控埋点统计采集帧率、推理帧率、端到端延迟写入日志支持性能分析总结C# TensorRT 工业相机的YOLO部署方案既保留了WPF MVVM架构的可维护性又通过TensorRT实现了GPU级推理加速全链路原生C#实现无需跨语言调用非常适合工业上位机视觉检测项目落地。在实际项目中可根据业务需求进一步扩展ROI区域检测、多相机多路推理、PLC结果联动、数据上传MES等功能分层架构能够很好地支撑功能迭代与扩展。
返回列表