YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的约瑟夫-雷德蒙(Joseph Redmon)和阿里-法哈迪(Ali Farhadi)开发。YOLO 于 2015 年推出,因其高速度和高精确度而迅速受到欢迎。
- YOLOv8是YOLO 的最新版本,由Ultralytics 提供。YOLOv8 YOLOv8 支持全方位的视觉 AI 任务,包括检测、分割、姿态估计、跟踪和分类。这种多功能性使用户能够在各种应用和领域中利用YOLOv8 的功能。
根据YOLOv8官方文档介绍,通过使用model.export()将.pt文件转为.onnx文件。
from ultralytics import YOLO
# Load a model
model = YOLO('yolov8n.pt') # load an official model
model = YOLO('path/to/best.pt') # load a custom trained model
# Export the model
model.export(format='onnx')
YOLOv8预测目标检测模型
from ultralytics import YOLO
# Load a pretrained YOLOv8n model
model = YOLO('yolov8n.pt')
# Run inference on 'bus.jpg' with arguments
model.predict('bus.jpg', save=True, imgsz=320, conf=0.5)
将yolov8目标检测过程转移到C#中,调用GPU处理目标图像需提前引用一下模块:
using System;
using Microsoft.ML.OnnxRuntime.Tensors;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System.Runtime.InteropServices;
using Cv = OpenCvSharp.Cv2;
using System.Diagnostics;
将检测模型转到GPU中推理
public OnnxRuntimeTest(string OnnxPATH, string DetectSavePath)
{
int gpuDeviceId = 0; // The GPU device ID to execute on
using var gpuSessionOptoins = SessionOptions.MakeSessionOptionWithCudaProvider(gpuDeviceId);
DetectSurface = new InferenceSession(OnnxPATH, gpuSessionOptoins);
SavePath = DetectSavePath;
}
定义模型参数:
private int DetectBatch = 1;
private int DetectChannl = 1;
private int DetectWith = 512;
private int DetectHeight = 512;
结合Opencv修改图片:
List<Predict> out_res = new List<Predict>();
//int class_max = 80;
Mat mat_ori = Cv2.ImRead(readimg);
int width = mat_ori.Width;
int height = mat_ori.Height;
//double r = Math.Min(640.0 / width, 640.0 / height);
//int scale_w = Convert.ToInt32(width * r) / 2 * 2;
//int scale_h = Convert.ToInt32(height * r) / 2 * 2;
//int dw = (int)((640 - scale_w) / 2f);
//int dh = (int)((640 - scale_h) / 2f);
Mat mat = new Mat();
Cv.Resize(mat_ori, mat, new Size(512, 512));
//Cv2.CopyMakeBorder(mat, mat, dh, dh, dw, dw, BorderTypes.Constant, new Scalar(114, 114, 114));
DetectHeight = mat.Width;
DetectWith = mat.Height;
List<Predict> predicts = new List<Predict>();
// 分类识别
int[] dimensions = {DetectBatch, DetectChannl, DetectHeight, DetectWith };
// int *dimensions :Numpy Array 数组每一维度数据的个数。
int length = DetectChannl * DetectHeight * DetectWith;
彩色图像通道转换,并行加速赋值
//并行运算加速赋值
Parallel.For(0, total_pixel_count, (p_idx, state) =>
{
int r = p_idx;
int g = r + total_pixel_count;
int b = g + total_pixel_count;
fbuff[r] = buffer[r * 3 + 2] / 255f;
fbuff[g] = buffer[r * 3 + 1] / 255f;
fbuff[b] = buffer[r * 3] / 255f;
});
引入ONNX模型输入,输出参数
Memory<float> memory = new Memory<float>(fbuff);
DenseTensor<float> input = new DenseTensor<float>(memory, dimensions);
string OutputMetadataName = DetectSurface!.OutputNames[0];
string InputMetadataName = DetectSurface!.InputNames[0];
// 将图片传至YOLO模型输入层
var ClassInputs = new List<NamedOnnxValue>()
{
NamedOnnxValue.CreateFromTensor(InputMetadataName, input)
};
float[] res = ClassResults.First(item => item.Name == OutputMetadataName).AsEnumerable<float>().ToArray();
创建模型推理
// 创建计时实例
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> ClassResults = DetectSurface.Run(ClassInputs);
stopwatch.Stop();
// 计算时差
TimeSpan elapsed = stopwatch.Elapsed;
Console.WriteLine("runTime:" + elapsed.TotalMilliseconds + "ms");
获取模型推理结果,遍历检测参数,筛选符合条件(符合置信度要求)的目标:
double maxVal = 0;
int Index0 = 0;
for (int j = 4; j < length0; j++)
{
if (classValue[0, j, i] > 0.25f && classValue[0, j, i] > maxVal)
{
maxVal = classValue[0, j, i];
Index0 = j;
}
}
将符合检测要求的目标框,添加至预测坐标中
if (Index0 != 0)
{
predicts.Add(new()
{
CenterX = (int)classValue[0, 0, i],
CenterY = (int)classValue[0, 1, i],
Width = (int)classValue[0, 2, i],
Height = (int)classValue[0, 3, i],
Class = Index0 - 4,
Confidence = classValue[0, Index0, i]
});
}
显示检测结果,获取检测目标框,输出检测框,打印检测标签,保存检测结果为.bmp(其它格式文件会降低算法执行保存速率,其它格式需要系统会进行压缩,影响执行速率)
if (have)
{
int a = 0;
foreach (Predict emu_pre in predicts)
{
//if (x_max < emu_pre.CenterX)
//{
// x_max = emu_pre.CenterX;
// i_max = a;
//}
int widthmax = (int)(width / 512f * predicts[a].Width);
int heightmax = (int)(height / 512f * predicts[a].Height);
int centerx = (int)(width / 512f * predicts[a].CenterX);
int centery = (int)(height / 512f * (predicts[a].CenterY));
// 检测目标坐标提取
Point point1 = new Point(centerx - (widthmax / 2), centery - (heightmax / 2));
Point point2 = new Point(centerx + (widthmax / 2), centery + (heightmax / 2));
Point point3 = new Point(centerx + (widthmax / 2), centery);
// 输出检测框
Cv.Rectangle(mat_ori, point1, point2, new Scalar(0, 0, 255), 2);
// 打印标签
string outtext = lableName[predicts[a].Class];
Cv.PutText(mat_ori, outtext, point3, HersheyFonts.HersheySimplex, 1, new Scalar(0, 255, 255));
//class_path = SavePath + "class\\" + predicts[a].Class.ToString() + "\\";
a++;
}
class_path = SavePath + "class\\" + predicts[i_max].Class.ToString() + "\\";
if (!Directory.Exists(class_path))
{
//创建文件夹
try
{
Directory.CreateDirectory(class_path);
}
catch (Exception e)
{
}
}
SaveFileName = class_path + data_time + ".bmp";
}
设置检测结果参数信息
public int CenterX { get; set; }
public int CenterY { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public float Confidence { get; set; }
public int Class { get; set; }
public int CompareTo(Predict? other)
{
if (other?.Confidence - Confidence <= 0)
{
return -1;
}
else
{
return 1;
}
}
模型IOU计算
public float IOU(Predict predict)
{
float w_2 = Width / 2;
float h_2 = Height / 2;
float x1 = CenterX - w_2, x2 = CenterX + w_2;
float y1 = CenterY - h_2, y2 = CenterY + h_2;
w_2 = predict.Width / 2;
h_2 = predict.Height / 2;
float x3 = predict.CenterX - w_2, x4 = predict.CenterX + w_2;
float y3 = predict.CenterY - h_2, y4 = predict.CenterY + h_2;
float minX = Math.Max(x1, x3);
float maxX = Math.Min(x2, x4);
float minY = Math.Max(y1, y3);
float maxY = Math.Min(y2, y4);
float w = Math.Max(0, maxX - minX);
float h = Math.Max(0, maxY - minY);
float jiao = w * h;
float bing = Width * Height + predict.Width * predict.Height - jiao;
return jiao / bing;
}
NMS计算过程
public static bool NMS(IList<Predict> predicts, float ioc_threshold = 0.25f)
{
bool flag = false;
int index = -1;
for (int i = 0; i < predicts.Count; i++)
{
for (int j = i + 1; j < predicts.Count; j++)
{
float iou = predicts[i].IOU(predicts[j]);
if (iou >= ioc_threshold)
{
index = j;
flag = true;
break;
}
}
if (flag)
{
break;
}
}
if (flag)
{
predicts.RemoveAt(index);
}
return flag;
}
完整代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.ML.OnnxRuntime.Tensors;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System.Runtime.InteropServices;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using Cv = OpenCvSharp.Cv2;
using System.Security.Cryptography.X509Certificates;
using Microsoft.VisualBasic;
using static System.Collections.Specialized.BitVector32;
using System.Data.Common;
using System.Data;
using System.Timers;
using System.Diagnostics;
namespace YoloOnnxProject
{
// 创建检测类
public class OnnxRuntimeTest
{
private InferenceSession DetectSurface;
public OnnxRuntimeTest(string OnnxPATH, string DetectSavePath)
{
int gpuDeviceId = 0; // The GPU device ID to execute on
using var gpuSessionOptoins = SessionOptions.MakeSessionOptionWithCudaProvider(gpuDeviceId);
DetectSurface = new InferenceSession(OnnxPATH, gpuSessionOptoins);
SavePath = DetectSavePath;
}
public void Dispose()
{
DetectSurface?.Dispose();
}
// 分类标签
private Dictionary<int, string> lableName = new()
{
{0, "person"} ,
{1, "bicycle"} ,
{2, "car"} ,
{3, "motorcycle"} ,
{4, "airplane"} ,
{5, "bus"},
{6, "train"} ,
//{0, "person"} ,
//{1, "bicycle"} ,
//{2, "car"} ,
//{3, "motorcycle"} ,
//{4, "airplane"} ,
//{5, "bus"},
//{6, "train"} ,
//{7, "truck"},
//{8, "boat"} ,
//{9, "traffic light"} ,
//{10, "fire hydrant"} ,
//{11, "stop sign"} ,
//{12, "parking meter"} ,
//{13, "bench"} ,
//{14, "bird"} ,
//{15, "cat"},
//{16, "dog"},
//{17, "horse"} ,
//{18, "sheep"} ,
//{19, "cow"} ,
//{20, "elephant"} ,
//{21, "bear"},
//{22, "zebra"} ,
//{23, "giraffe"} ,
//{24, "backpack"} ,
//{25, "umbrella"} ,
//{26, "handbag"} ,
//{27, "tie"},
//{28, "suitcase"} ,
//{29, "frisbee"} ,
//{30, "skis"} ,
//{31, "snowboard"} ,
//{32, "sports ball"},
//{33, "kite"} ,
//{34, "baseball bat"},
//{35, "baseball glove"} ,
//{36, "skateboard"},
//{37, "surfboard"} ,
//{38, "tennis racket"} ,
//{39, "bottle"} ,
//{40, "wine glass"} ,
//{41, "cup"} ,
//{42, "fork"} ,
//{43, "knife"} ,
//{44, "spoon"},
//{45, "bowl"} ,
//{46, "banana"} ,
//{47, "apple"} ,
//{48, "sandwich"} ,
//{49, "orange"} ,
//{50, "broccoli"} ,
//{51, "carrot"} ,
//{52, "hot dog"} ,
//{53, "pizza"} ,
//{54, "donut"} ,
//{55, "cake"} ,
//{56, "chair"} ,
//{57, "couch"} ,
//{58, "potted plant"} ,
//{59, "bed"} ,
//{60, "dining table"} ,
//{61, "toilet"} ,
//{62, "tv"} ,
//{63, "laptop"},
//{64, "mouse"} ,
//{65, "remote"} ,
//{66, "keyboard"},
//{67, "cell phone"} ,
//{68, "microwave"} ,
//{69, "oven"} ,
//{70, "toaster"} ,
//{71, "sink"} ,
//{72, "refrigerator"} ,
//{73, "book"} ,
//{74, "clock"} ,
//{75, "vase"},
//{76, "scissors"} ,
//{77, "teddy bear"} ,
//{78, "hair drier"} ,
//{79, "toothbrush"}
};
private int DetectBatch = 1;
private int DetectChannl = 1;
private int DetectWith = 512;
private int DetectHeight = 512;
//private string OnnxPATH;
private string SavePath;
private string SaveFileName;
private string class_path;
public List<Predict> UnionInference(string readimg)
{
List<Predict> out_res = new List<Predict>();
//int class_max = 80;
Mat mat_ori = Cv2.ImRead(readimg);
int width = mat_ori.Width;
int height = mat_ori.Height;
//double r = Math.Min(640.0 / width, 640.0 / height);
//int scale_w = Convert.ToInt32(width * r) / 2 * 2;
//int scale_h = Convert.ToInt32(height * r) / 2 * 2;
//int dw = (int)((640 - scale_w) / 2f);
//int dh = (int)((640 - scale_h) / 2f);
Mat mat = new Mat();
Cv.Resize(mat_ori, mat, new Size(512, 512));
//Cv2.CopyMakeBorder(mat, mat, dh, dh, dw, dw, BorderTypes.Constant, new Scalar(114, 114, 114));
DetectHeight = mat.Width;
DetectWith = mat.Height;
List<Predict> predicts = new List<Predict>();
// 分类识别
int[] dimensions = {DetectBatch, DetectChannl, DetectHeight, DetectWith };
// int *dimensions :Numpy Array 数组每一维度数据的个数。
int length = DetectChannl * DetectHeight * DetectWith;
byte[] buffer = new byte[length];
Marshal.Copy(mat.Data, buffer, 0, length);
float[] fbuff = new float[length];
int total_pixel_count = mat.Rows * mat.Cols;
//并行运算加速赋值
Parallel.For(0, total_pixel_count, (p_idx, state) =>
{
int r = p_idx;
int g = r + total_pixel_count;
int b = g + total_pixel_count;
fbuff[r] = buffer[r * 3 + 2] / 255f;
fbuff[g] = buffer[r * 3 + 1] / 255f;
fbuff[b] = buffer[r * 3] / 255f;
});
Memory<float> memory = new Memory<float>(fbuff);
DenseTensor<float> input = new DenseTensor<float>(memory, dimensions);
string OutputMetadataName = DetectSurface!.OutputNames[0];
string InputMetadataName = DetectSurface!.InputNames[0];
// 将图片传至YOLO模型输入层
var ClassInputs = new List<NamedOnnxValue>()
{
NamedOnnxValue.CreateFromTensor(InputMetadataName, input)
};
// 创建计时实例
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> ClassResults = DetectSurface.Run(ClassInputs);
stopwatch.Stop();
// 计算时差
TimeSpan elapsed = stopwatch.Elapsed;
Console.WriteLine("runTime:" + elapsed.TotalMilliseconds + "ms");
float[] res = ClassResults.First(item => item.Name == OutputMetadataName).AsEnumerable<float>().ToArray();
var classValue = (DenseTensor<float>)ClassResults.ToArray().First().Value;
//ClassResults.Dispose();
if (ClassResults?.Any() == true)
{
//int[] dimen = [classValue.Dimensions[0], classValue.Dimensions[2], classValue.Dimensions[1]];
int length1 = classValue.Dimensions[2]; // 8400
int length0 = classValue.Dimensions[1]; // 84
for (int i = 0; i < length1; i++)
{
double maxVal = 0;
int Index0 = 0;
for (int j = 4; j < length0; j++)
{
if (classValue[0, j, i] > 0.25f && classValue[0, j, i] > maxVal)
{
maxVal = classValue[0, j, i];
Index0 = j;
}
}
if (Index0 != 0)
{
predicts.Add(new()
{
CenterX = (int)classValue[0, 0, i],
CenterY = (int)classValue[0, 1, i],
Width = (int)classValue[0, 2, i],
Height = (int)classValue[0, 3, i],
Class = Index0 - 4,
Confidence = classValue[0, Index0, i]
});
}
//float[] arr = new float[] { classValue[0, 4, i], classValue[0, 5, i], classValue[0, 6, i], classValue[0, 7, i] };
//var b = arr.Select((item, indx) => new { Item = item, Index = indx }).OrderByDescending(x => x.Item).Select(x => x.Index).Take(1).ToArray();
阈值
//if (classValue[0, 4, i] >= 0.5f || classValue[0, 5, i] >= 0.5f || classValue[0, 6, i] >= 0.5f || classValue[0, 7, i] >= 0.5f)
//{
// predicts.Add(new()
// {
// CenterX = (int)classValue[0, 0, i],
// CenterY = (int)classValue[0, 1, i],
// Width = (int)classValue[0, 2, i],
// Height = (int)classValue[0, 3, i],
// // ---------------
// Class = b[0],
// Confidence = classValue[0, (4 + b[0]), i]
// });
//}
}
predicts.Sort();
while (Predict.NMS(predicts, 0.25f)) ;
//Stack<int> stack = new Stack<int>();
//for (int i = 0; i < predicts.Count; i++)
//{
// if (predicts[i].Confidence < 0.25f)
// {
// stack.Push(i);
// }
//}
//while (stack.Any())
//{
// int peek = stack.Pop();
// predicts.Remove(predicts[peek]);
//}
}
// class处理部分
int x_max = 0;
int i_max = 0;
bool have = (predicts.Count != 0);
string data_time = System.DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss_ffff");
if (have)
{
int a = 0;
foreach (Predict emu_pre in predicts)
{
//if (x_max < emu_pre.CenterX)
//{
// x_max = emu_pre.CenterX;
// i_max = a;
//}
int widthmax = (int)(width / 512f * predicts[a].Width);
int heightmax = (int)(height / 512f * predicts[a].Height);
int centerx = (int)(width / 512f * predicts[a].CenterX);
int centery = (int)(height / 512f * (predicts[a].CenterY));
// 检测目标坐标提取
Point point1 = new Point(centerx - (widthmax / 2), centery - (heightmax / 2));
Point point2 = new Point(centerx + (widthmax / 2), centery + (heightmax / 2));
Point point3 = new Point(centerx + (widthmax / 2), centery);
// 输出检测框
Cv.Rectangle(mat_ori, point1, point2, new Scalar(0, 0, 255), 2);
// 打印标签
string outtext = lableName[predicts[a].Class];
Cv.PutText(mat_ori, outtext, point3, HersheyFonts.HersheySimplex, 1, new Scalar(0, 255, 255));
//class_path = SavePath + "class\\" + predicts[a].Class.ToString() + "\\";
a++;
}
//int widthmax = (int)(width / 640f * predicts[i_max].Width);
//int heightmax = (int)(width / 640f * predicts[i_max].Height);
//int centerx = (int)(width / 640f * predicts[i_max].CenterX);
//int centery = (int)(width / 640f * (predicts[i_max].CenterY - dh));
检测目标坐标提取
//Point point1 = new Point(centerx - (widthmax / 2), centery - (heightmax / 2));
//Point point2 = new Point(centerx + (widthmax / 2), centery + (widthmax / 2));
输出检测框
//Cv.Rectangle(mat_ori, point1, point2, new Scalar(0, 0, 255), 2);
打印标签
//string outtext = lableName[predicts[i_max].Class];
//Cv.PutText(mat_ori, outtext, point1, HersheyFonts.HersheySimplex, 1, new Scalar(0, 0, 255));
// 裁剪检测目标
//Mat cropped = mat_ori[centery - heightmax / 2, centery + heightmax / 2, centerx - widthmax / 2, centerx + widthmax / 2];
//UTC时间
//转北京时间,毫秒精确后两位
// 保存检测目标
class_path = SavePath + "class\\" + predicts[i_max].Class.ToString() + "\\";
if (!Directory.Exists(class_path))
{
//创建文件夹
try
{
Directory.CreateDirectory(class_path);
}
catch (Exception e)
{
}
}
SaveFileName = class_path + data_time + ".jpg";
//Cv2.ImWrite(SaveFileName, mat_ori);
//class_max = predicts[i_max].Class;
}
else
{
class_path = SavePath + "NoneObject\\" + "\\";
if (!Directory.Exists(class_path))
{
//创建文件夹
try
{
Directory.CreateDirectory(class_path);
}
catch (Exception e)
{
}
}
SaveFileName = class_path + data_time + ".jpg";
//Cv2.ImWrite(SaveFileName, mat_ori);
//Predict re_lable = outputLable(SaveFileName);
}
return out_res;
}
public class Predict : IComparable<Predict>
{
public int CenterX { get; set; }
public int CenterY { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public float Confidence { get; set; }
public int Class { get; set; }
public int CompareTo(Predict? other)
{
if (other?.Confidence - Confidence <= 0)
{
return -1;
}
else
{
return 1;
}
}
public float IOU(Predict predict)
{
float w_2 = Width / 2;
float h_2 = Height / 2;
float x1 = CenterX - w_2, x2 = CenterX + w_2;
float y1 = CenterY - h_2, y2 = CenterY + h_2;
w_2 = predict.Width / 2;
h_2 = predict.Height / 2;
float x3 = predict.CenterX - w_2, x4 = predict.CenterX + w_2;
float y3 = predict.CenterY - h_2, y4 = predict.CenterY + h_2;
float minX = Math.Max(x1, x3);
float maxX = Math.Min(x2, x4);
float minY = Math.Max(y1, y3);
float maxY = Math.Min(y2, y4);
float w = Math.Max(0, maxX - minX);
float h = Math.Max(0, maxY - minY);
float jiao = w * h;
float bing = Width * Height + predict.Width * predict.Height - jiao;
return jiao / bing;
}
public static bool NMS(IList<Predict> predicts, float ioc_threshold = 0.25f)
{
bool flag = false;
int index = -1;
for (int i = 0; i < predicts.Count; i++)
{
for (int j = i + 1; j < predicts.Count; j++)
{
float iou = predicts[i].IOU(predicts[j]);
if (iou >= ioc_threshold)
{
index = j;
flag = true;
break;
}
}
if (flag)
{
break;
}
}
if (flag)
{
predicts.RemoveAt(index);
}
return flag;
}
}
}
}
main.cs
using OpenCvSharp;
using System;
using System.Diagnostics;
using System.Drawing;
using static YoloOnnxProject.OnnxRuntimeTest;
using Cv = OpenCvSharp.Cv2;
namespace YoloOnnxProject
{
internal class Program
{
static void Main(string[] args)
{
// Onnx文件地址
string OnnxPATH = @"E:\DeskTop\Surface_detect\best.onnx";
// 检测输出文件地址
string imgSavePath = @"E:\DeskTop\Surface_detect\runs\detect1\";
// 调用算法
OnnxRuntimeTest OnnxYoloV8 = new OnnxRuntimeTest(OnnxPATH, imgSavePath);
// 检测目标地址
string imgPath = @"E:\DeskTop\Surface_detect\Annotation\val\images\";
int imgnum = 0;
// 遍历图片
foreach (string img in Directory.GetFiles(imgPath))
{
// 创建记时实例
Stopwatch stopwatch = new Stopwatch();
string readimg = img;
// 开始记时
stopwatch.Start();
List<Predict> predicts = OnnxYoloV8.UnionInference(readimg);
// 结束记时
stopwatch.Stop();
// 输出时间
TimeSpan timeSpan = stopwatch.Elapsed;
//Console.WriteLine("**********************");
//Console.WriteLine("第 " + imgnum + " 张图像" + "程序执行时间:" + timeSpan.TotalMilliseconds + "ms");
imgnum++;
}
Cv.DestroyAllWindows();
}
}
}