1. 训练模型
在python中用yolo v5 训练自己的数据集,得到best.pt。
参考:
Yolov5训练自己的数据集(详细完整版)_yolov5缔宇-CSDN博客
在python中使用此模型:
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 cv2
device = 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: 类别 ID
label = 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
python
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 = 12
torch.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
- 结果可视化
代码:
cs
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-1
tensor[0, 0, y, x] = pixel.R / 255.0f; // R
tensor[0, 1, y, x] = pixel.G / 255.0f; // G
tensor[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;
}
// 计算 IoU
private 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; }
}
}
运行后:
