C# 实现 YOLO 算法:从模型加载到实时目标检测的完整指南

1次阅读
没有评论

共计 4455 个字符,预计需要花费 12 分钟才能阅读完成。

image.webp

1. YOLO 算法与工业应用

YOLO(You Only Look Once)作为单阶段目标检测算法的代表,其核心优势在于将目标检测转化为回归问题。与传统的 R -CNN 系列算法相比,YOLO 通过单个卷积神经网络直接预测边界框和类别概率,实现了端到端的检测流程。典型的 YOLOv5s 模型在 COCO 数据集上能达到每秒 140 帧 (FPS) 的推理速度,这使得它在以下场景中表现突出:

C# 实现 YOLO 算法:从模型加载到实时目标检测的完整指南

  • 生产线产品质量实时检测
  • 交通监控系统中的车辆行人识别
  • 无人机航拍图像分析
  • 智能零售中的顾客行为分析

2. 环境准备与工具选型

ONNX Runtime vs ML.NET

在.NET 生态中,我们主要考虑两种模型推理方案:

  • ONNX Runtime:微软开源的高性能推理引擎,支持跨平台部署和硬件加速
  • 优势:支持 GPU 加速、模型量化、多线程推理
  • 劣势:需要额外学习 ONNX 模型格式

  • ML.NET:微软官方的机器学习框架

  • 优势:与.NET 生态深度集成
  • 劣势:对计算机视觉任务支持较弱

推荐使用 ONNX Runtime 进行 YOLO 模型部署,以下是必需的 NuGet 包:

<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.13.1" />
<PackageReference Include="OpenCvSharp4" Version="4.7.0.20230115" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.7.0.20230115" />

3. 核心实现流程

3.1 图像预处理

YOLO 模型通常需要固定的输入尺寸(如 640×640),使用 OpenCVSharp 进行预处理:

using OpenCvSharp;

Mat PreprocessImage(Mat srcImage, Size targetSize)
{
    // 保持长宽比的缩放
    var scale = Math.Min((double)targetSize.Width / srcImage.Width, 
                        (double)targetSize.Height / srcImage.Height);
    var resized = new Mat();
    Cv2.Resize(srcImage, resized, Size.Zero, scale, scale);

    // 填充到目标尺寸
    var padded = new Mat(targetSize.Height, targetSize.Width, MatType.CV_8UC3, 
                        new Scalar(114, 114, 114));
    resized.CopyTo(new Mat(padded, new Rect(0, 0, resized.Width, resized.Height)));

    // 归一化到 0 - 1 范围
    padded.ConvertTo(padded, MatType.CV_32FC3, 1.0 / 255);
    return padded;
}

3.2 模型加载与推理

推荐从 Ultralytics 官方获取预训练的 ONNX 模型:

using Microsoft.ML.OnnxRuntime;

class YoloDetector : IDisposable
{
    private InferenceSession _session;

    public YoloDetector(string modelPath)
    {var options = new SessionOptions()
        {
            GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL,
            EnableCpuMemArena = true
        };

        // 启用 CUDA 加速(如可用)if (OrtEnv.Instance.IsCudaAvailable())
            options.AppendExecutionProvider_CUDA();

        _session = new InferenceSession(modelPath, options);
    }

    public IList<Prediction> Detect(Mat image)
    {
        // 创建输入 Tensor
        var inputTensor = new DenseTensor<float>(new Memory<float>(image.Data, 0, image.Total() * image.Channels()),
            new[] { 1, 3, image.Height, image.Width});

        // 准备输入输出
        var inputs = new List<NamedOnnxValue>
        {NamedOnnxValue.CreateFromTensor("images", inputTensor)
        };

        // 执行推理
        using var results = _session.Run(inputs);
        var output = results.First().AsTensor<float>();

        // 后处理(NMS 等)return ProcessOutput(output, image.Size());
    }

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

4. 关键算法实现

4.1 非极大值抑制(NMS)

List<Prediction> SuppressNonMaximum(IList<Prediction> predictions, float iouThreshold = 0.5f)
{var sorted = predictions.OrderByDescending(p => p.Confidence).ToList();
    var selected = new List<Prediction>();

    while (sorted.Count > 0)
    {var current = sorted[0];
        selected.Add(current);

        sorted.RemoveAt(0);

        for (int i = sorted.Count - 1; i >= 0; i--)
        {if (CalculateIoU(current.Box, sorted[i].Box) > iouThreshold)
                sorted.RemoveAt(i);
        }
    }

    return selected;
}

float CalculateIoU(Rect a, Rect b)
{
    var intersection = a & b;
    var union = a | b;
    return (float)(intersection.Area / (double)union.Area);
}

4.2 异步推理管道

public class AsyncDetectionPipeline : IDisposable
{
    private readonly YoloDetector _detector;
    private readonly Channel<Mat> _inputChannel;
    private readonly Channel<List<Prediction>> _outputChannel;

    public AsyncDetectionPipeline(string modelPath, int workerCount = 2)
    {_detector = new YoloDetector(modelPath);
        _inputChannel = Channel.CreateUnbounded<Mat>();
        _outputChannel = Channel.CreateUnbounded<List<Prediction>>();

        // 启动工作线程
        for (int i = 0; i < workerCount; i++)
            Task.Run(ProcessImagesAsync);
    }

    private async Task ProcessImagesAsync()
    {await foreach (var image in _inputChannel.Reader.ReadAllAsync())
        {var results = _detector.Detect(image);
            await _outputChannel.Writer.WriteAsync(results.ToList());
            image.Dispose();}
    }

    public ValueTask SubmitImageAsync(Mat image) => 
        _inputChannel.Writer.WriteAsync(image);

    public IAsyncEnumerable<List<Prediction>> GetResultsAsync() => 
        _outputChannel.Reader.ReadAllAsync();

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

5. 性能优化实战

5.1 模型量化对比

模型类型 精度(mAP) 推理时间(ms) 模型大小(MB)
FP32 0.856 45 244
FP16 0.854 32 122
INT8(动态量化) 0.848 28 61

5.2 GPU 加速配置

var options = SessionOptions.MakeSessionOptionWithCudaProvider(
    deviceId: 0, 
    cudnnConvAlgoSearch: OrtCudnnConvAlgoSearch.Exhaustive,
    arenaExtendStrategy: 0);

// 显存分配策略(根据应用场景调整)options.AddSessionConfigEntry("gpu_mem_limit", "2147483648"); // 限制 2GB 显存

6. 常见问题排查

6.1 输入输出格式问题

YOLOv5/v8 的输入输出张量格式差异:

版本 输入形状 输出形状
v5 [1,3,640,640] [1,25200,85]
v8 [1,3,640,640] [1,84,8400]

6.2 内存泄漏检测

使用 dotnet-counters 工具监控:

dotnet-counters monitor --process-id [PID] \
    --counters System.Runtime,Microsoft.ML.OnnxRuntime

7. 流程时序图

sequenceDiagram
    participant Client as 客户端
    participant Pipeline as 异步管道
    participant Detector as YOLO 检测器

    Client->>Pipeline: 提交图像
    Pipeline->>Detector: 异步调用
    Detector->>Pipeline: 返回检测结果
    Pipeline->>Client: 推送结果

8. 思考与展望

  1. 如何设计动态模型切换机制,在运行时根据设备性能自动选择 FP32/FP16/INT8 模型?
  2. 在多摄像头场景下,怎样的任务调度策略能最大化 GPU 利用率?
  3. 针对特定领域(如工业缺陷检测),如何设计有效的模型微调流程?

通过本文介绍的核心方法,开发者可以构建起基础的 YOLO 检测系统。实际部署时还需要考虑模型版本管理、异常恢复等工程化问题,这些将是后续优化的方向。

正文完
 0
评论(没有评论)