C#中使用YOLO的常用方式的詳細(xì)指南
在C#中使用YOLO(You Only Look Once)目標(biāo)檢測(cè)算法,通常有幾種不同的實(shí)現(xiàn)方式,包括使用OpenCV、ONNX Runtime或ML.NET等框架。下面為你介紹兩種常見(jiàn)的實(shí)現(xiàn)方法:
方法一:使用ONNX Runtime部署YOLOv5模型
這種方法的優(yōu)勢(shì)在于實(shí)現(xiàn)簡(jiǎn)單,且性能優(yōu)化較好。
具體步驟
1.準(zhǔn)備YOLOv5模型
首先,從YOLOv5官方倉(cāng)庫(kù)(https://github.com/ultralytics/yolov5 )下載預(yù)訓(xùn)練模型。
然后,將PyTorch模型轉(zhuǎn)換為ONNX格式:
python export.py --weights yolov5s.pt --include onnx
2.創(chuàng)建C#項(xiàng)目并安裝NuGet包
新建一個(gè).NET Core控制臺(tái)應(yīng)用程序。
通過(guò)NuGet包管理器安裝以下組件:
- Microsoft.ML.OnnxRuntime
- System.Drawing.Common(用于圖像處理)
實(shí)現(xiàn)代碼
下面是一個(gè)完整的示例代碼
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 { // 檢測(cè)類別名稱 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); // 加載并預(yù)處理圖像 var (inputTensor, originalWidth, originalHeight) = LoadImage(imagePath); // 準(zhǔn)備輸入?yún)?shù) var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("images", inputTensor) }; // 運(yùn)行模型推理 using var results = session.Run(inputs); // 處理輸出結(jié)果 var outputTensor = results.First().AsTensor<float>(); var predictions = ProcessOutput(outputTensor, originalWidth, originalHeight); // 繪制檢測(cè)結(jié)果 DrawPredictions(imagePath, predictions, "output.jpg"); Console.WriteLine("檢測(cè)完成,結(jié)果已保存至 output.jpg"); } // 加載并預(yù)處理圖像 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; // 調(diào)整圖像大小為模型輸入尺寸(通常為640x640) var resizedImage = ResizeImage(image, 640, 640); // 圖像轉(zhuǎn)張量 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 }; // 標(biāo)準(zhǔn)差 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); // 注意:圖像通道順序?yàn)锽GR(OpenCV默認(rèn)),而模型可能需要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); } // 調(diào)整圖像大小 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) { // 獲取邊界框坐標(biāo) float cx = output[0, i, 0]; float cy = output[0, i, 1]; float w = output[0, i, 2]; float h = output[0, i, 3]; // 轉(zhuǎn)換為左上角和右下角坐標(biāo) 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; } // 計(jì)算交并比 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); } // 繪制預(yù)測(cè)結(jié)果 private static void DrawPredictions(string inputImagePath, List<Prediction> predictions, string outputImagePath) { using var image = Image.FromFile(inputImagePath); using var graphics = Graphics.FromImage(image); // 設(shè)置繪圖質(zhì)量 graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; // 繪制邊界框和標(biāo)簽 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); // 繪制標(biāo)簽背景 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); } // 保存結(jié)果圖像 image.Save(outputImagePath, ImageFormat.Jpeg); } } // 預(yù)測(cè)結(jié)果類 public class Prediction { public int ClassId { get; set; } public float Score { get; set; } public RectangleF BBox { get; set; } } }
方法二:使用ML.NET部署YOLOv5模型
ML.NET是微軟的跨平臺(tái)機(jī)器學(xué)習(xí)框架,也可用于部署YOLO模型。
具體步驟
1.準(zhǔn)備工作與方法一相同
下載并轉(zhuǎn)換YOLOv5模型。
創(chuàng)建C#項(xiàng)目并安裝必要的NuGet包:
- Microsoft.ML
- Microsoft.ML.OnnxTransformer
實(shí)現(xiàn)代碼
下面是使用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 { // 檢測(cè)類別名稱(同上) private static readonly string[] classNames = new[] { ... }; // 同上,省略 static void Main(string[] args) { string modelPath = "yolov5s.onnx"; string imagePath = "test.jpg"; // 創(chuàng)建MLContext var mlContext = new MLContext(); // 定義模型輸入輸出架構(gòu) var data = new List<ImageInputData> { new ImageInputData { ImagePath = imagePath } }; var dataView = mlContext.Data.LoadFromEnumerable(data); // 定義數(shù)據(jù)轉(zhuǎn)換管道 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)); // 訓(xùn)練管道(這里只是為了創(chuàng)建預(yù)測(cè)引擎) var model = pipeline.Fit(dataView); // 創(chuàng)建預(yù)測(cè)引擎 var predictor = mlContext.Model.CreatePredictionEngine<ImageInputData, ImagePrediction>(model); // 進(jìn)行預(yù)測(cè) var prediction = predictor.Predict(new ImageInputData { ImagePath = imagePath }); // 處理輸出結(jié)果 var originalImage = Image.FromFile(imagePath); var predictions = ProcessOutput(prediction.Output, originalImage.Width, originalImage.Height); // 繪制檢測(cè)結(jié)果 DrawPredictions(imagePath, predictions, "output.jpg"); Console.WriteLine("檢測(cè)完成,結(jié)果已保存至 output.jpg"); } // 圖像處理和結(jié)果解析方法(與ONNX Runtime版本相同) private static List<Prediction> ProcessOutput(float[] output, int originalWidth, int originalHeight) { // 與ONNX Runtime版本中的ProcessOutput方法相同 // ... } // 非極大值抑制和IoU計(jì)算方法(同上) private static List<Prediction> NonMaxSuppression(List<Prediction> predictions, float threshold) { // ... } private static float IoU(RectangleF a, RectangleF b) { // ... } // 繪制預(yù)測(cè)結(jié)果(同上) private static void DrawPredictions(string inputImagePath, List<Prediction> predictions, string outputImagePath) { // ... } } // 數(shù)據(jù)模型類 public class ImageInputData { [LoadColumn(0)] public string ImagePath { get; set; } } public class ImagePrediction { [ColumnName("output")] public float[] Output { get; set; } } // 預(yù)測(cè)結(jié)果類(同上) public class Prediction { public int ClassId { get; set; } public float Score { get; set; } public RectangleF BBox { get; set; } } }
性能優(yōu)化建議
使用GPU加速:若要提升檢測(cè)速度,可安裝CUDA版本的ONNX Runtime,這樣就能利用GPU進(jìn)行推理。
模型量化:可以將FP32模型轉(zhuǎn)換為INT8量化模型,以此減小模型體積并提高推理速度。
批量處理:如果需要處理大量圖像,可以考慮使用批量輸入來(lái)提高吞吐量。
異步推理:對(duì)于實(shí)時(shí)應(yīng)用場(chǎng)景,建議使用異步API來(lái)避免阻塞主線程。
通過(guò)上述方法,你可以在C#環(huán)境中實(shí)現(xiàn)YOLO目標(biāo)檢測(cè)功能。ONNX Runtime方法更為直接,而ML.NET方法則能更好地與微軟生態(tài)系統(tǒng)集成。你可以根據(jù)自己的具體需求選擇合適的實(shí)現(xiàn)方式。
到此這篇關(guān)于C#中使用YOLO的常用方式的詳細(xì)指南的文章就介紹到這了,更多相關(guān)C#使用YOLO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)上位機(jī)與歐姆龍PLC通訊(FINS)
這篇文章主要介紹了C#實(shí)現(xiàn)上位機(jī)與歐姆龍PLC通訊(FINS)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05C# SendMail發(fā)送郵件功能實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了C# SendMail發(fā)送郵件功能實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06C#實(shí)現(xiàn)chart控件動(dòng)態(tài)曲線繪制
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)chart控件動(dòng)態(tài)曲線繪制,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C#使用InstallerProjects打包桌面應(yīng)用程序的完整步驟
這篇文章主要給大家介紹了關(guān)于C#使用InstallerProjects打包桌面應(yīng)用程序的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07C#使用dynamic一行代碼實(shí)現(xiàn)反射操作
dynamic的出現(xiàn)讓C#具有了弱語(yǔ)言類型的特性。編譯器在編譯的時(shí)候不再對(duì)類型進(jìn)行檢查,編譯時(shí)默認(rèn)dynamic對(duì)象支持你想要的任何特性,這篇文章主要介紹了C#用dynamic一行代碼實(shí)現(xiàn)反射操作,需要的朋友可以參考下2023-04-04C# 手動(dòng)/自動(dòng)保存圖片的實(shí)例代碼
C# 手動(dòng)/自動(dòng)保存圖片的實(shí)例代碼,需要的朋友可以參考一下2013-03-03C#使用HttpPost請(qǐng)求調(diào)用WebService的方法
這篇文章主要為大家詳細(xì)介紹了C#使用HttpPost請(qǐng)求調(diào)用WebService的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08