摘要
在工业视觉领域,Python 是算法训练的“实验室”,而 C# (.NET) 才是产线部署的“主战场”。
很多开发者卡在“模型转不过去”、“推理太慢”、“界面卡死”、“硬件连不上”这四座大山上。

本指南将打通从模型训练到产线部署的全链路,基于 2026 年最新技术栈(.NET 8 + YOLOv8/v10 + ONNX Runtime/TensorRT),手把手教你构建一套高稳定、高性能、易维护的工业级视觉检测系统。

核心路线图

  1. 环境搭建:避坑指南,一次性配齐所有依赖。
  2. 模型转换:从 PyTorch .pt 到工业级 .onnx 的标准化流程。
  3. 推理引擎:C# 调用 ONNX Runtime 与 TensorRT 的终极封装。
  4. 架构设计:MVVM 模式 + 生产者消费者队列,彻底解决 UI 卡顿。
  5. 硬件集成:相机 (Hik/Basler) + PLC (Modbus) 的实战代码。
  6. 部署运维:Docker 容器化与看门狗机制。

第一阶段:环境搭建与避坑 (基础篇)

1. 开发环境选择

  • IDE: Visual Studio 2022 (推荐) 或 Rider。
  • .NET 版本: .NET 8 LTS (性能最强,AOT 编译支持好)。
  • 操作系统: Windows 10/11 (开发), Linux Ubuntu 20.04/22.04 (部署)。

2. 核心 NuGet 包清单

不要手动下载 DLL,全部通过 NuGet 管理:

<ItemGroup>
  <!-- 核心推理 -->
  <PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.17.0" />
  <PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu" Version="1.17.0" /> <!-- 若用 NVIDIA GPU -->
  
  <!-- 图像处理 (OpenCV C# 版) -->
  <PackageReference Include="OpenCvSharp4" Version="4.9.0.20240103" />
  <PackageReference Include="OpenCvSharp4.runtime.win" Version="4.9.0.20240103" /> <!-- Windows 运行时 -->
  <!-- Linux 部署时需安装 libopencv-dev 或使用 OpenCvSharp4.runtime.linux-x64 -->

  <!-- MVVM 框架 (推荐微软官方) -->
  <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />

  <!-- 日志与配置 -->
  <PackageReference Include="Serilog" Version="3.1.1" />
  <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>

3. 常见坑点

  • DLL 地狱OpenCvSharp 需要对应的 C++ 运行时。Windows 下 NuGet 会自动处理;Linux 下需 sudo apt-get install libopencv-dev
  • GPU 驱动:确保安装了与 OnnxRuntime.Gpu 版本匹配的 CUDA Toolkit 和 cuDNN。
  • AVX 指令集:老旧工控机 CPU 不支持 AVX2,需下载 onnxruntime-win-x86 特殊版本,否则报错 Illegal Instruction

第二阶段:模型转换与优化 (核心篇)

工业界不直接加载 .pt 文件,必须转换为通用格式 ONNX

1. Python 端导出 (标准动作)

使用 Ultralytics 库一键导出,关键是参数配置:

from ultralytics import YOLO

model = YOLO('yolov8s.pt')

# 关键参数解释:
# format='onnx': 导出格式
# opset=12: 算子版本,兼容性好
# simplify=True: 简化模型结构,合并卷积,提升速度
# dynamic=False: 工业场景通常固定分辨率 (如 640x640),设为 False 可提速 20%
# half=True: 导出 FP16 权重 (仅 GPU 推理有效,CPU 需用 FP32)
model.export(format='onnx', opset=12, simplify=True, dynamic=False, half=True)

产出yolov8s.onnx

2. 模型验证

在投入 C# 前,先用 Python 跑一遍 ONNX 模型,确保精度无损:

import onnxruntime as ort
import numpy as np

sess = ort.InferenceSession("yolov8s.onnx")
# ... 准备测试图片 ...
outputs = sess.run(None, {"images": input_data})
# 对比原 pt 模型输出,误差应在 1e-5 以内

第三阶段:C# 推理引擎封装 (实战篇)

这是系统的“心脏”。我们需要一个线程安全、高性能的推理类。

1. 推理服务类 (YoloService)

采用单例模式 + 对象池思想,避免频繁创建 Session。

using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;

public class YoloService : IDisposable
{
    private readonly InferenceSession _session;
    private readonly SessionOptions _options;
    private readonly string[] _classNames;
    private readonly int _imgSize = 640;

    public YoloService(string modelPath, bool useGpu = false)
    {
        _options = new SessionOptions();
        _options.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_WARNING;
        
        // 开启多线程 CPU 加速
        _options.IntraOpNumThreads = 4; 
        _options.InterOpNumThreads = 4;

        if (useGpu)
        {
            // 加载 CUDA Provider
            _options.AppendExecutionProvider_CUDA(0);
        }

        _session = new InferenceSession(modelPath, _options);
        _classNames = new[] { "person", "car", "defect_a", "defect_b" }; // 根据实际模型修改
    }

    public List<DetectionResult> Detect(Mat image)
    {
        // 1. 预处理 (Letterbox + Normalize + HWC to CHW)
        var inputTensor = Preprocess(image);

        // 2. 推理
        var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("images", inputTensor) };
        using var results = _session.Run(inputs);
        
        // 3. 后处理 (Decode + NMS)
        var outputTensor = results.First().AsTensor<float>();
        return Postprocess(outputTensor, image.Width, image.Height);
    }

    private Tensor<float> Preprocess(Mat src)
    {
        // 核心:Letterbox 保持宽高比填充
        var resized = LetterBox(src, _imgSize, _imgSize);
        var blob = Cv2.Dnn.BlobFromImage(resized, 1.0 / 255.0, new Size(_imgSize, _imgSize), 
                                         new Scalar(), true, false);
        
        // 将 OpenCvSharp Mat 数据拷贝到 OnnxRuntime Tensor
        var tensorData = new float[1 * 3 * _imgSize * _imgSize];
        Marshal.Copy(blob.Data, tensorData, 0, tensorData.Length);
        
        return new DenseTensor<float>(tensorData, new[] { 1, 3, _imgSize, _imgSize });
    }

    private List<DetectionResult> Postprocess(Tensor<float> output, int origW, int origH)
    {
        // YOLOv8 输出格式:[1, 84, 8400] -> [x, y, w, h, cls_probs...]
        // 需要转置并解码
        // ... (此处省略具体的 NMS 算法实现,约 50 行代码) ...
        // 返回 List<DetectionResult>
        return new List<DetectionResult>(); 
    }

    public void Dispose() => _session.Dispose();
}

public class DetectionResult
{
    public string ClassName { get; set; }
    public float Confidence { get; set; }
    public Rect Box { get; set; }
}

2. 性能优化技巧

  • 输入输出固定:不要在推理时动态改变 Input Shape,这会触发引擎重新优化,极慢。
  • 内存复用Preprocess 中的 tensorData 数组可以做成 ThreadLocal 或对象池,减少 GC 压力。
  • 异步队列:不要让 UI 线程直接调用 Detect,见下一阶段。

第四阶段:工业级架构设计 (进阶篇)

工业软件最怕卡死丢帧。必须采用生产者 - 消费者模型。

1. 架构图

[相机线程] --(原始图像)--> [BlockingCollection 队列] --(取图)--> [推理线程池] --(结果)--> [UI 线程 (通过事件)]

2. 核心代码实现

public class VisionSystem
{
    private readonly BlockingCollection<Mat> _imageQueue = new(new ConcurrentQueue<Mat>(), boundedCapacity: 3);
    private readonly YoloService _yolo;
    private readonly CancellationTokenSource _cts = new();

    public event Action<List<DetectionResult>, Mat> OnDetectionCompleted;

    public VisionSystem(YoloService yolo)
    {
        _yolo = yolo;
        // 启动推理消费者线程
        Task.Run(() => ProcessLoop());
    }

    // 供相机回调调用 (生产者)
    public void EnqueueImage(Mat frame)
    {
        // 如果队列满了,丢弃旧帧或阻塞 (视策略而定),这里演示尝试添加
        if (!_imageQueue.TryAdd(frame, 100)) 
        {
            frame.Dispose(); // 丢弃,防止内存爆炸
        }
    }

    private void ProcessLoop()
    {
        foreach (var frame in _imageQueue.GetConsumingEnumerable(_cts.Token))
        {
            try
            {
                var sw = System.Diagnostics.Stopwatch.StartNew();
                
                // 执行推理
                var results = _yolo.Detect(frame);
                
                sw.Stop();
                Console.WriteLine($"Inference time: {sw.ElapsedMilliseconds}ms");

                // 通知 UI (注意跨线程安全)
                OnDetectionCompleted?.Invoke(results, frame);
                
                // 释放内存 (重要!)
                frame.Dispose();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
                frame.Dispose();
            }
        }
    }

    public void Stop()
    {
        _cts.Cancel();
        _imageQueue.CompleteAdding();
    }
}

3. UI 层 (WPF/WinForm)

  • WPF: 监听 OnDetectionCompleted,使用 Dispatcher.Invoke 更新 Image.Source 和绘制 Canvas。
  • WinForm: 监听事件,使用 pictureBox.Invoke 更新图像。
  • 关键点:UI 只负责画,绝不负责算。

第五阶段:硬件集成与业务闭环 (落地篇)

只有算法是不够的,必须连接物理世界。

1. 相机接入 (以海康威视为例)

使用 MvCameraControl.dll (P/Invoke) 或官方 C# wrapper。

  • 硬触发模式:PLC 给相机信号 -> 相机拍照 -> 相机触发软中断 -> C# 获取图像 -> 送入队列。
  • 软触发模式:C# 循环发送采集命令 (适合低速场景)。

2. PLC 通信 (Modbus TCP)

使用 NModbus4 库。

// 简单的状态机逻辑
if (plc.ReadRegister(TriggerAddress) == 1)
{
    if (!isProcessing)
    {
        isProcessing = true;
        camera.TriggerSoft(); // 或等待硬触发图像
    }
}

// 在 OnDetectionCompleted 中
if (results.Any(r => r.Confidence > threshold && r.ClassName == "NG"))
{
    plc.WriteRegister(ResultAddress, 1); // 写 NG 信号
    // 延时等待 PLC 复位
}
else
{
    plc.WriteRegister(ResultAddress, 0); // 写 OK 信号
}

3. 数据追溯

  • 每张图保存路径:/Data/2026-03-12/Line1/NG_20260312_102030_001.jpg
  • 数据库记录:SQLite (轻量) 或 SQL Server。记录时间、结果、置信度、图片路径。

第六阶段:部署与运维 (交付篇)

1. 发布策略

  • 单文件发布
    dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true
    
    生成一个 .exe,丢到工控机就能跑,无需安装 .NET 环境。
  • Linux 边缘端
    dotnet publish -c Release -r linux-arm64 --self-contained true
    

2. 看门狗 (Watchdog)

工业现场程序可能会因为相机掉线、显存溢出等原因崩溃。

  • 外部看门狗:写一个简单的批处理脚本或独立小进程,每分钟检测主程序是否存在,不存在则重启。
  • 内部看门狗:在代码中监测心跳,若推理线程卡死超过 10 秒,主动记录 Dump 并退出,让外部脚本重启。

3. 调试工具

  • NetMonitor: 监控网络流量。
  • PerfView: 分析 .NET 性能瓶颈。
  • ONNX Runtime Log: 设置 ORT_LOGGING_LEVEL 查看算子执行情况。

总结:成功落地的 Checklist

在交付项目前,请核对以下清单:

  • 模型:是否已转为 ONNX 并在 C# 中验证过精度?
  • 性能:FPS 是否满足产线节拍?(预留 20% 余量)
  • 稳定性:连续运行 24 小时,内存是否平稳?有无泄漏?
  • 异常处理:拔掉相机网线,程序会崩吗?(应报警并重连)
  • 权限:是否去除了管理员运行依赖?
  • 日志:是否有完整的黑匣子日志,便于远程排查?
  • 文档:是否提供了操作手册和故障代码表?

C#+YOLO 的组合,凭借 C# 强大的工程化能力和 YOLO 领先的算法精度,已成为工业视觉检测的黄金标准。掌握这套全流程,你将具备独立交付百万级视觉项目的能力。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐