c# 使用yolov5模型
1. 训练模型
在python中用yolo v5 训练自己的数据集,得到best.pt。
参考:
Yolov5训练自己的数据集(详细完整版)_yolov5缔宇-CSDN博客
在python中使用此模型:
import torch
from models.experimental import attempt_load
from utils.general import non_max_suppression, scale_boxes
from utils.plots import Annotator, colors
import cv2device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = attempt_load('runs/train/exp/weights/best.pt').to(device) # 加载模型
stride = int(model.stride.max()) # 模型步长(用于缩放坐标)
names = model.module.names if hasattr(model, 'module') else model.names # 类别名称img = cv2.imread("../ultralytics-main/dataset/images/val/camera0_20250611143415545667.jpg_0.0.png")
img_resize = cv2.resize(img,(640,640))
img_tensor = torch.from_numpy(img_resize).to(device).float() / 255.0 # 归一化
img_tensor = img_tensor.permute(2, 0, 1).unsqueeze(0) # HWC → CHW,添加 batch 维度# 推理
with torch.no_grad():pred = model(img_tensor)[0] # 推理# 后处理:NMS + 坐标缩放
pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45) # NMS
annotator = Annotator(img_resize, line_width=2, example=str(names)) # 初始化标注器# 绘制检测结果
for det in pred: # 遍历每张图片的检测结果(batch=1 时只有 1 个 det)if len(det):det[:, :4] = scale_boxes(img_tensor.shape[2:], det[:, :4], img_resize.shape[:2]).round() # 缩放坐标到原图for *xyxy, conf, cls in reversed(det): # xyxy: 边界框坐标, conf: 置信度, cls: 类别 IDlabel = f'{names[int(cls)]} {conf:.2f}'annotator.box_label(xyxy, label, color=colors(int(cls))) # 绘制边界框和标签cv2.imshow("result",img_resize)
cv2.waitKey(0)
这里用一个比较简单的划痕污点检测模型,进行测试。
dataset:
2.模型转换
.pt是python格式,需要转换成.onnx。
pip install onnx
import torch
import onnx
# 加载 YOLOv5 模型
model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt')# 转换为 ONNX 格式
dummy_input = torch.randn(1, 3, 640, 640)
print("开始转换")opset_version = 12torch.onnx.export(model,dummy_input,"best.onnx",input_names=['images'],output_names=['output'],opset_version=opset_version,dynamic_axes=None
)
print("转换完成")
得到best.onnx
3.在c#中使用此模型。
创建 winform 项目 test—yolo
在nuget中 添加Microsoft.ML.OnnxRuntime
项目属性,生成,取消 首选32
界面设计:
主要方法:
-
test_yolov5
- 主流程控制 -
LoadImage
- 图像预处理 -
ProcessOutput
- 模型输出解析 -
NonMaxSuppression
- 非极大值抑制 -
DrawPredictions
- 结果可视化
代码:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Drawing.Imaging;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;namespace test_yolo
{public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button_start_Click(object sender, EventArgs e){//使用 ONNX 格式的模型//string modelPath = "best.onnx";//string imagePath = "test.png";Image result = yolo_Program.test_yolov5(textBox_model.Text, textBox_img.Text);pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;pictureBox1.Image = result;}private void button_img_Click(object sender, EventArgs e){OpenFileDialog openFileDialog = new OpenFileDialog{Title = "选择文件",Filter = "所有文件 (*.*)|*.*",//InitialDirectory = @"C:\"};if (openFileDialog.ShowDialog() == DialogResult.OK){// 获取仅包含文件名的字符串string fileName = openFileDialog.SafeFileName;textBox_img.Text = fileName;}}private void button_model_Click(object sender, EventArgs e){OpenFileDialog openFileDialog = new OpenFileDialog{Title = "选择文件",Filter = "所有文件 (*.*)|*.*",//InitialDirectory = @"C:\"};if (openFileDialog.ShowDialog() == DialogResult.OK){// 获取仅包含文件名的字符串string fileName = openFileDialog.SafeFileName;textBox_model.Text = fileName;}}}class yolo_Program{// 检测类别名称private static readonly string[] classNames = new[] {"A","B","C","D","E","F","G","H","I","J"};public static Image test_yolov5(string modelPath, string imagePath){try{// 检查文件是否存在if (!System.IO.File.Exists(modelPath)){MessageBox.Show($"模型文件不存在: {modelPath}");return null;}if (!System.IO.File.Exists(imagePath)){MessageBox.Show($"图像文件不存在: {imagePath}");return null;}// 设置 ONNX Runtime 选项var options = new SessionOptions();// 根据您的需求选择合适的执行提供程序// 对于 CPU:options.AppendExecutionProvider_CPU();// 如果支持 GPU,可以添加 CUDA 提供程序(需要安装相应的包)// options.AppendExecutionProvider_CUDA();// 加载模型var session = new InferenceSession(modelPath, options);// 加载并预处理图像var (inputTensor, originalWidth, originalHeight) = LoadImage(imagePath);// 准备输入参数var inputs = new List<NamedOnnxValue>{NamedOnnxValue.CreateFromTensor("images", inputTensor)};// 运行模型推理var results = session.Run(inputs);// 处理输出结果var outputTensor = results.First().AsTensor<float>();var predictions = ProcessOutput(outputTensor, originalWidth, originalHeight);// 绘制检测结果Image result = DrawPredictions(imagePath, predictions, "output.jpg");// MessageBox.Show($"检测完成,结果已保存至 output.jpg\n检测到 {predictions.Count} 个目标");return result;}catch (Exception ex){MessageBox.Show($"错误: {ex.Message}\n{ex.StackTrace}");return null;}}// 加载并预处理图像private static (DenseTensor<float> tensor, int width, int height) LoadImage(string imagePath){var image = Image.FromFile(imagePath);int originalWidth = image.Width;int originalHeight = image.Height;// 调整图像大小为模型输入尺寸var resizedImage = ResizeImage(image, 640, 640);// 图像转张量var tensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });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);// YOLOv5 使用 RGB 顺序,归一化到 0-1tensor[0, 0, y, x] = pixel.R / 255.0f; // Rtensor[0, 1, y, x] = pixel.G / 255.0f; // Gtensor[0, 2, y, x] = pixel.B / 255.0f; // B}}}return (tensor, originalWidth, originalHeight);}// 调整图像大小private static Bitmap 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 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>();// YOLOv5 ONNX 模型的输出格式通常是 [1, 25200, 85]// 其中 85 = [x, y, w, h, confidence, class_probabilities...]int numDetections = output.Dimensions[1];int numClasses = output.Dimensions[2] - 5;for (int i = 0; i < numDetections; i++){float confidence = output[0, i, 4];if (confidence >= confidenceThreshold){// 找到概率最高的类别int classId = 0;float maxClassProb = 0;for (int c = 0; c < numClasses; c++){float classProb = output[0, i, 5 + c];if (classProb > maxClassProb){maxClassProb = classProb;classId = c;}}float score = confidence * maxClassProb;if (score >= confidenceThreshold){// 获取边界框坐标(相对于 640x640)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) * originalWidth / 640;float y1 = (cy - h / 2) * originalHeight / 640;float x2 = (cx + w / 2) * originalWidth / 640;float y2 = (cy + h / 2) * originalHeight / 640;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 =>{float iou = CalculateIoU(best.BBox, p.BBox);return iou < threshold;}).ToList();}return result;}// 计算 IoUprivate static float CalculateIoU(RectangleF box1, RectangleF box2){float x1 = Math.Max(box1.Left, box2.Left);float y1 = Math.Max(box1.Top, box2.Top);float x2 = Math.Min(box1.Right, box2.Right);float y2 = Math.Min(box1.Bottom, box2.Bottom);float intersection = Math.Max(0, x2 - x1) * Math.Max(0, y2 - y1);float area1 = box1.Width * box1.Height;float area2 = box2.Width * box2.Height;return intersection / (area1 + area2 - intersection);}// 绘制预测结果private static Image DrawPredictions(string inputImagePath, List<Prediction> predictions, string outputImagePath){var image = Image.FromFile(inputImagePath);var graphics = Graphics.FromImage(image);graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;var colors = new[]{Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Orange,Color.Purple, Color.Cyan, Color.Magenta, Color.Lime, Color.Pink};foreach (var prediction in predictions){var bbox = prediction.BBox;var color = colors[prediction.ClassId % colors.Length];var label = $"{classNames[prediction.ClassId]}: {prediction.Score:F2}";var pen = new Pen(color, 3);graphics.DrawRectangle(pen, bbox.X, bbox.Y, bbox.Width, bbox.Height);var font = new Font("Arial", 12, FontStyle.Bold);var brush = new SolidBrush(color);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);return image;}}// 预测结果类public class Prediction{public int ClassId { get; set; }public float Score { get; set; }public RectangleF BBox { get; set; }}
}
运行后: