C#中使用YOLO的常用方式的详细指南

 更新时间:2025年05月15日 09:18:12   作者:LeonDL168  
在C#中使用YOLO(You Only Look Once)目标检测算法,通常有几种不同的实现方式,例如OpenCV,ONNX Runtime等,下面我们来看看具体的操作方法吧

在C#中使用YOLO(You Only Look Once)目标检测算法,通常有几种不同的实现方式,包括使用OpenCV、ONNX Runtime或ML.NET等框架。下面为你介绍两种常见的实现方法:

方法一:使用ONNX Runtime部署YOLOv5模型

这种方法的优势在于实现简单,且性能优化较好。

具体步骤

1.准备YOLOv5模型

首先,从YOLOv5官方仓库(https://github.com/ultralytics/yolov5 )下载预训练模型。

然后,将PyTorch模型转换为ONNX格式:

python export.py --weights yolov5s.pt --include onnx

2.创建C#项目并安装NuGet包

新建一个.NET Core控制台应用程序。

通过NuGet包管理器安装以下组件:

  • Microsoft.ML.OnnxRuntime
  • System.Drawing.Common(用于图像处理)

实现代码

下面是一个完整的示例代码

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;

namespace YoloV5OnnxRuntime
{
    class Program
    {
        // 检测类别名称
        private static readonly string[] classNames = new[] {
            "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
            "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
            "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
            "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
            "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
            "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
            "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
            "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
            "hair drier", "toothbrush"
        };

        static void Main(string[] args)
        {
            // 模型路径和输入图像路径
            string modelPath = "yolov5s.onnx";
            string imagePath = "test.jpg";
            
            // 加载模型
            using var session = new InferenceSession(modelPath);
            
            // 加载并预处理图像
            var (inputTensor, originalWidth, originalHeight) = LoadImage(imagePath);
            
            // 准备输入参数
            var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("images", inputTensor) };
            
            // 运行模型推理
            using var results = session.Run(inputs);
            
            // 处理输出结果
            var outputTensor = results.First().AsTensor<float>();
            var predictions = ProcessOutput(outputTensor, originalWidth, originalHeight);
            
            // 绘制检测结果
            DrawPredictions(imagePath, predictions, "output.jpg");
            
            Console.WriteLine("检测完成,结果已保存至 output.jpg");
        }

        // 加载并预处理图像
        private static (DenseTensor<float> tensor, int width, int height) LoadImage(string imagePath)
        {
            using var image = Image.FromFile(imagePath);
            int originalWidth = image.Width;
            int originalHeight = image.Height;
            
            // 调整图像大小为模型输入尺寸(通常为640x640)
            var resizedImage = ResizeImage(image, 640, 640);
            
            // 图像转张量
            var tensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });
            var mean = new[] { 0.0f, 0.0f, 0.0f }; // 均值
            var std = new[] { 1.0f, 1.0f, 1.0f };  // 标准差
            
            using (var bitmap = new Bitmap(resizedImage))
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        var pixel = bitmap.GetPixel(x, y);
                        
                        // 注意:图像通道顺序为BGR(OpenCV默认),而模型可能需要RGB
                        tensor[0, 0, y, x] = (pixel.R / 255.0f - mean[0]) / std[0];
                        tensor[0, 1, y, x] = (pixel.G / 255.0f - mean[1]) / std[1];
                        tensor[0, 2, y, x] = (pixel.B / 255.0f - mean[2]) / std[2];
                    }
                }
            }
            
            return (tensor, originalWidth, originalHeight);
        }

        // 调整图像大小
        private static Image ResizeImage(Image image, int width, int height)
        {
            var destRect = new Rectangle(0, 0, width, height);
            var destImage = new Bitmap(width, height);
            
            destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
            
            using (var graphics = Graphics.FromImage(destImage))
            {
                graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                
                using (var wrapMode = new System.Drawing.Imaging.ImageAttributes())
                {
                    wrapMode.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);
                    graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
                }
            }
            
            return destImage;
        }

        // 处理模型输出
        private static List<Prediction> ProcessOutput(Tensor<float> output, int originalWidth, int originalHeight)
        {
            const float confidenceThreshold = 0.5f;
            const float nmsThreshold = 0.4f;
            
            var predictions = new List<Prediction>();
            
            // 解析模型输出
            for (int i = 0; i < output.Length / 85; i++)
            {
                float confidence = output[0, i, 4];
                
                if (confidence >= confidenceThreshold)
                {
                    // 找到概率最高的类别
                    int classId = 0;
                    float maxClassProbs = 0;
                    
                    for (int c = 0; c < 80; c++)
                    {
                        float classProbs = output[0, i, 5 + c];
                        if (classProbs > maxClassProbs)
                        {
                            maxClassProbs = classProbs;
                            classId = c;
                        }
                    }
                    
                    float score = confidence * maxClassProbs;
                    
                    if (score >= confidenceThreshold)
                    {
                        // 获取边界框坐标
                        float cx = output[0, i, 0];
                        float cy = output[0, i, 1];
                        float w = output[0, i, 2];
                        float h = output[0, i, 3];
                        
                        // 转换为左上角和右下角坐标
                        float x1 = (cx - w / 2) / 640.0f * originalWidth;
                        float y1 = (cy - h / 2) / 640.0f * originalHeight;
                        float x2 = (cx + w / 2) / 640.0f * originalWidth;
                        float y2 = (cy + h / 2) / 640.0f * originalHeight;
                        
                        predictions.Add(new Prediction
                        {
                            ClassId = classId,
                            Score = score,
                            BBox = new RectangleF(x1, y1, x2 - x1, y2 - y1)
                        });
                    }
                }
            }
            
            // 非极大值抑制
            return NonMaxSuppression(predictions, nmsThreshold);
        }

        // 非极大值抑制
        private static List<Prediction> NonMaxSuppression(List<Prediction> predictions, float threshold)
        {
            var result = new List<Prediction>();
            
            // 按置信度降序排序
            predictions = predictions.OrderByDescending(p => p.Score).ToList();
            
            while (predictions.Count > 0)
            {
                var best = predictions[0];
                result.Add(best);
                predictions.RemoveAt(0);
                
                // 移除重叠度高的边界框
                predictions = predictions.Where(p => IoU(best.BBox, p.BBox) < threshold).ToList();
            }
            
            return result;
        }

        // 计算交并比
        private static float IoU(RectangleF a, RectangleF b)
        {
            float areaA = a.Width * a.Height;
            if (areaA <= 0) return 0;
            
            float areaB = b.Width * b.Height;
            if (areaB <= 0) return 0;
            
            float minX = Math.Max(a.Left, b.Left);
            float minY = Math.Max(a.Top, b.Top);
            float maxX = Math.Min(a.Right, b.Right);
            float maxY = Math.Min(a.Bottom, b.Bottom);
            
            float intersectionWidth = maxX - minX;
            float intersectionHeight = maxY - minY;
            
            if (intersectionWidth <= 0 || intersectionHeight <= 0)
                return 0;
                
            float intersectionArea = intersectionWidth * intersectionHeight;
            return intersectionArea / (areaA + areaB - intersectionArea);
        }

        // 绘制预测结果
        private static void DrawPredictions(string inputImagePath, List<Prediction> predictions, string outputImagePath)
        {
            using var image = Image.FromFile(inputImagePath);
            using var graphics = Graphics.FromImage(image);
            
            // 设置绘图质量
            graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
            
            // 绘制边界框和标签
            foreach (var prediction in predictions)
            {
                var bbox = prediction.BBox;
                var label = $"{classNames[prediction.ClassId]}: {prediction.Score:F2}";
                
                // 绘制边界框
                using var pen = new Pen(Color.FromArgb(255, 255, 0, 0), 2);
                graphics.DrawRectangle(pen, bbox.X, bbox.Y, bbox.Width, bbox.Height);
                
                // 绘制标签背景
                using var font = new Font("Arial", 10, FontStyle.Bold);
                using var brush = new SolidBrush(Color.FromArgb(255, 255, 0, 0));
                using var textBrush = new SolidBrush(Color.White);
                
                var textSize = graphics.MeasureString(label, font);
                var textBackground = new RectangleF(bbox.X, bbox.Y - textSize.Height, textSize.Width, textSize.Height);
                
                graphics.FillRectangle(brush, textBackground);
                graphics.DrawString(label, font, textBrush, bbox.X, bbox.Y - textSize.Height);
            }
            
            // 保存结果图像
            image.Save(outputImagePath, ImageFormat.Jpeg);
        }
    }

    // 预测结果类
    public class Prediction
    {
        public int ClassId { get; set; }
        public float Score { get; set; }
        public RectangleF BBox { get; set; }
    }
}

方法二:使用ML.NET部署YOLOv5模型

ML.NET是微软的跨平台机器学习框架,也可用于部署YOLO模型。

具体步骤

1.准备工作与方法一相同

下载并转换YOLOv5模型。

创建C#项目并安装必要的NuGet包:

  • Microsoft.ML
  • Microsoft.ML.OnnxTransformer

实现代码

下面是使用ML.NET的示例代码:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.OnnxTransformer;
using Microsoft.ML.Trainers;

namespace YoloV5MLNet
{
    class Program
    {
        // 检测类别名称(同上)
        private static readonly string[] classNames = new[] { ... }; // 同上,省略
        
        static void Main(string[] args)
        {
            string modelPath = "yolov5s.onnx";
            string imagePath = "test.jpg";
            
            // 创建MLContext
            var mlContext = new MLContext();
            
            // 定义模型输入输出架构
            var data = new List<ImageInputData> { new ImageInputData { ImagePath = imagePath } };
            var dataView = mlContext.Data.LoadFromEnumerable(data);
            
            // 定义数据转换管道
            var pipeline = mlContext.Transforms.LoadImages(outputColumnName: "image", imageFolder: "", inputColumnName: nameof(ImageInputData.ImagePath))
                .Append(mlContext.Transforms.ResizeImages(outputColumnName: "image", imageWidth: 640, imageHeight: 640, inputColumnName: "image"))
                .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "images", inputColumnName: "image", interleavePixelColors: true, offsetImage: 0))
                .Append(mlContext.Transforms.ApplyOnnxModel(
                    shapeDictionary: new Dictionary<string, int[]>()
                    {
                        { "images", new[] { 1, 3, 640, 640 } },
                        { "output", new[] { 1, 25200, 85 } }
                    },
                    inputColumnNames: new[] { "images" },
                    outputColumnNames: new[] { "output" },
                    modelFile: modelPath));
            
            // 训练管道(这里只是为了创建预测引擎)
            var model = pipeline.Fit(dataView);
            
            // 创建预测引擎
            var predictor = mlContext.Model.CreatePredictionEngine<ImageInputData, ImagePrediction>(model);
            
            // 进行预测
            var prediction = predictor.Predict(new ImageInputData { ImagePath = imagePath });
            
            // 处理输出结果
            var originalImage = Image.FromFile(imagePath);
            var predictions = ProcessOutput(prediction.Output, originalImage.Width, originalImage.Height);
            
            // 绘制检测结果
            DrawPredictions(imagePath, predictions, "output.jpg");
            
            Console.WriteLine("检测完成,结果已保存至 output.jpg");
        }

        // 图像处理和结果解析方法(与ONNX Runtime版本相同)
        private static List<Prediction> ProcessOutput(float[] output, int originalWidth, int originalHeight)
        {
            // 与ONNX Runtime版本中的ProcessOutput方法相同
            // ...
        }

        // 非极大值抑制和IoU计算方法(同上)
        private static List<Prediction> NonMaxSuppression(List<Prediction> predictions, float threshold)
        {
            // ...
        }
        
        private static float IoU(RectangleF a, RectangleF b)
        {
            // ...
        }

        // 绘制预测结果(同上)
        private static void DrawPredictions(string inputImagePath, List<Prediction> predictions, string outputImagePath)
        {
            // ...
        }
    }

    // 数据模型类
    public class ImageInputData
    {
        [LoadColumn(0)]
        public string ImagePath { get; set; }
    }

    public class ImagePrediction
    {
        [ColumnName("output")]
        public float[] Output { get; set; }
    }

    // 预测结果类(同上)
    public class Prediction
    {
        public int ClassId { get; set; }
        public float Score { get; set; }
        public RectangleF BBox { get; set; }
    }
}

性能优化建议

使用GPU加速:若要提升检测速度,可安装CUDA版本的ONNX Runtime,这样就能利用GPU进行推理。

模型量化:可以将FP32模型转换为INT8量化模型,以此减小模型体积并提高推理速度。

批量处理:如果需要处理大量图像,可以考虑使用批量输入来提高吞吐量。

异步推理:对于实时应用场景,建议使用异步API来避免阻塞主线程。

通过上述方法,你可以在C#环境中实现YOLO目标检测功能。ONNX Runtime方法更为直接,而ML.NET方法则能更好地与微软生态系统集成。你可以根据自己的具体需求选择合适的实现方式。

到此这篇关于C#中使用YOLO的常用方式的详细指南的文章就介绍到这了,更多相关C#使用YOLO内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#中CompareTo的用法小结

    C#中CompareTo的用法小结

    CompareTo方法通常用于比较当前对象与另一个对象的顺序,本文主要介绍了C#中CompareTo的用法小结,具有一定的参考价值,感兴趣的可以了解一下
    2025-04-04
  • C#利用System.Uri转URL为绝对地址的方法

    C#利用System.Uri转URL为绝对地址的方法

    这篇文章主要介绍了C#利用System.Uri转URL为绝对地址的方法,涉及C#操作URL的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-02-02
  • C#实现启动,关闭与查找进程的方法

    C#实现启动,关闭与查找进程的方法

    这篇文章主要介绍了C#实现启动,关闭与查找进程的方法,通过简单实例形式分析了C#针对进程的启动,关闭与查找的相关实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-11-11
  • C# 基础数据类型之字符串类型详解

    C# 基础数据类型之字符串类型详解

    C#字符串作为引用类型却常被当作值类型使用,探讨其不可变性、字符串驻留及常用操作,本文给大家介绍C# 基础数据类型之字符串类型,感兴趣的朋友一起看看吧
    2026-05-05
  • C#实现绑定Combobox的方法

    C#实现绑定Combobox的方法

    这篇文章主要介绍了C#实现绑定Combobox的方法,涉及Combobox参数设置的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-08-08
  • C#优化if...else代码的方案总结

    C#优化if...else代码的方案总结

    在编写代码实现业务需求过程中,会使用到大量的if...else 判断语句,随业务复杂程度不同,导致判断语句出现多层嵌套、多分支等情况,导致代码可读性变差、增加维护难度,本文介绍了C# 如何优化 if...else 让代码优雅起来,需要的朋友可以参考下
    2024-06-06
  • C# 读取指定路径配置文件的方法

    C# 读取指定路径配置文件的方法

    为了实现多个C#程序共用一个config文件,需要程序读取指定路径的config文件。代码如下:
    2013-03-03
  • 基于Silverlight打印的使用详解,是否为微软的Bug问题

    基于Silverlight打印的使用详解,是否为微软的Bug问题

    本篇文章对Silverlight打印的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C#使用正则表达式实现常见的格式验证

    C#使用正则表达式实现常见的格式验证

    这篇文章主要为大家详细介绍了C#如何使用正则表达式实现常见的格式验证,例如:电话号码、密码、邮编等,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-01-01
  • C#中使用DevExpress中的ChartControl实现极坐标图的案例详解

    C#中使用DevExpress中的ChartControl实现极坐标图的案例详解

    这篇文章主要介绍了在C#中使用DevExpress中的ChartControl实现极坐标图,本案例是使用的是DevExpress 18.1.3版本,之前在14版本上也试过,但是有一个弊端就是实现极坐标图的时候,第一个点和最后一个点总是自动多一条闭合线,会形成一个闭合的多边形,因此升级了一下版
    2022-02-02

最新评论