├── Models └── YoloModel.cs ├── Extentions ├── RectangleExtensions.cs └── Utils.cs ├── YoloClassifyPrediction.cs ├── YoloLabel.cs ├── YoloPrediction.cs ├── OBBPrediction.cs ├── README.md ├── Yolov7.cs ├── OBB.cs ├── RTDETR.cs ├── Yolov9.cs ├── Yolov8.cs └── Yolov5.cs /Models/YoloModel.cs: -------------------------------------------------------------------------------- 1 | namespace YOLO.Models 2 | { 3 | public class YoloModel 4 | { 5 | public int Dimensions { get; set; } //yolov7 包含nms 的模型不需要此参数 6 | 7 | public string[] Outputs { get; set; } 8 | 9 | public List Labels { get; set; } = []; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Extentions/RectangleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | 4 | namespace YOLO.Extentions 5 | { 6 | public static class RectangleExtensions 7 | { 8 | public static float Area(this RectangleF source) 9 | { 10 | return source.Width * source.Height; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /YoloClassifyPrediction.cs: -------------------------------------------------------------------------------- 1 | namespace YOLO 2 | { 3 | public class YoloClassifyPrediction 4 | { 5 | public YoloLabel? Label { get; set; } 6 | public float Score { get; set; } 7 | 8 | public YoloClassifyPrediction(YoloLabel? label, float score) 9 | { 10 | Label = label; 11 | Score = score; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /YoloLabel.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | 4 | namespace YOLO 5 | { 6 | public class YoloLabel 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public Color Color { get; set; } 13 | 14 | public YoloLabel(int Id, string Name, Color Color) 15 | { 16 | this.Id = Id; 17 | this.Name = Name; 18 | this.Color = Color; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /YoloPrediction.cs: -------------------------------------------------------------------------------- 1 | using YOLO.Extentions; 2 | using System.Drawing; 3 | 4 | 5 | namespace YOLO 6 | { 7 | public class YoloPrediction 8 | { 9 | public YoloLabel Label { get; set; } 10 | public RectangleF Rectangle { get; set; } 11 | public float Area { get; set; } 12 | public float Score { get; set; } 13 | 14 | public YoloPrediction(YoloLabel label, RectangleF rectangle, float confidence) 15 | { 16 | Label = label; 17 | Score = confidence; 18 | Rectangle = rectangle; 19 | Area = rectangle.Area(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /OBBPrediction.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using YOLO.Extentions; 3 | 4 | 5 | namespace YOLO 6 | { 7 | public class OBBPrediction 8 | { 9 | public YoloLabel Label { get; set; } 10 | public RectangleF Rectangle { get; set; } 11 | public float Area { get; set; } 12 | public float Score { get; set; } 13 | public float Angle { get; set; } 14 | 15 | public OBBPrediction(YoloLabel label, RectangleF rectangle, float angle, float confidence) 16 | { 17 | Label = label; 18 | Score = confidence; 19 | Rectangle = rectangle; 20 | Angle = angle; 21 | Area = rectangle.Area(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | ## YOLOv8 object detection 3 | 4 | ``` 5 | Yolov8 yolov8 = new("path/to/.onnx", false); 6 | Color[] colors = []; // fill in the colors for classes. 7 | yolov8.SetupColors(colors); 8 | Image image = Image.FromFile("path/to/img"); 9 | List predictions = yolov8.Preidct((Bitmap)image, .5f, .5f); 10 | Utils.DrawBoundingBox(image, predictions, 2, 16); // this return a image with bouding boxes drawn. 11 | ``` 12 | 13 | ## YOLOv8 oriented bounding box 14 | ``` 15 | OBB obb = new("path/to/.onnx", false); 16 | Color[] colors = []; 17 | obb.SetupColors(colors); 18 | Image image = Image.FromFile("path/to/img"); 19 | List predictions = obb.Predict((Bitmap)image, .5f, .5f); 20 | Utils.DrawRotatedBoundingBox(image, predictions, 2, 16); 21 | ``` 22 | 23 | ## RT-DETR object detection 24 | ``` 25 | RTDETR rtdetr = new("path/to/.onnx", false); 26 | Color[] colors = []; 27 | rtdetr.SetupColors(colors); 28 | Image image = Image.FromFile("path/to/img"); 29 | List predictions = rtdetr.Predict((Bitmap)image, .5f, .5f); 30 | Utils.DrawBoundingBox(image, predictions, 2, 16); 31 | ``` 32 | 33 | ## YOLOv9 object detection (https://github.com/WongKinYiu/yolov9) 34 | ``` 35 | Yolov9 yolov9 = new("path/to/.onnx", false); 36 | Color[] colors = []; 37 | yolov9.SetupColors(colors); 38 | Image image = Image.FromFile("path/to/img"); 39 | List predictions = yolov9.Preidct((Bitmap)image, .5f, .5f); 40 | Utils.DrawBoundingBox(image, predictions, 2, 16); 41 | ``` 42 | 43 | Note: for the Yolov9.cs, it supports this [yolov9](https://github.com/WongKinYiu/yolov9), for the yolov9 from [ultralyutics](https://github.com/ultralytics/ultralytics), Yolov8.cs will work just fine as they are using the same output head. 44 | -------------------------------------------------------------------------------- /Yolov7.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime; 2 | using Microsoft.ML.OnnxRuntime.Tensors; 3 | using System.Collections.Concurrent; 4 | using System.Drawing; 5 | using YOLO.Extentions; 6 | using YOLO.Models; 7 | 8 | 9 | namespace YOLO 10 | { 11 | public class Yolov7 : IDisposable 12 | { 13 | private readonly InferenceSession _inferenceSession; 14 | private readonly YoloModel _model = new(); 15 | int Imgsz { get; set; } 16 | int N_Class { get; set; } 17 | 18 | public Yolov7(string modelPath, bool useCuda = false) 19 | { 20 | if (useCuda) 21 | { 22 | OrtCUDAProviderOptions cudaProviderOptions = new(); 23 | cudaProviderOptions.UpdateOptions(new Dictionary() 24 | { 25 | { "cudnn_conv_use_max_workspace", "1" }, 26 | { "cudnn_conv1d_pad_to_nc1d", "1" }, 27 | { "arena_extend_strategy", "kSameAsRequested" }, 28 | { "do_copy_in_default_stream", "1" } 29 | }); 30 | SessionOptions opts = SessionOptions.MakeSessionOptionWithCudaProvider(cudaProviderOptions); 31 | opts.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; 32 | _inferenceSession = new InferenceSession(modelPath, opts); 33 | } 34 | else 35 | { 36 | SessionOptions opts = new(); 37 | _inferenceSession = new InferenceSession(modelPath, opts); 38 | } 39 | 40 | // Get model info 41 | get_input_details(); 42 | get_output_details(); 43 | using Bitmap bitmap = new(Imgsz, Imgsz); 44 | NamedOnnxValue[] inputs = [NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(bitmap))]; 45 | _inferenceSession.Run(inputs, _model.Outputs); 46 | } 47 | 48 | public void SetupLabels(Dictionary color_mapper) 49 | { 50 | int i = 0; 51 | foreach (KeyValuePair keyValuePair in color_mapper) 52 | { 53 | _model.Labels.Add(new(i, keyValuePair.Key, keyValuePair.Value)); 54 | ++i; 55 | } 56 | } 57 | 58 | private List ParseDetect(DenseTensor output, Image image, float conf) 59 | { 60 | ConcurrentBag result = []; 61 | var (w, h) = (image.Width, image.Height); // image w and h 62 | var (xGain, yGain) = (Imgsz / (float)w, Imgsz / (float)h); // x, y gains 63 | float gain = Math.Min(xGain, yGain); // gain = resized / original 64 | float gain_inv = 1.0f / gain; 65 | var (xPad, yPad) = ((Imgsz - w * gain) * 0.5f, (Imgsz - h * gain) * 0.5f); // left, right pads 66 | 67 | Parallel.For(0, output.Dimensions[0], i => 68 | { 69 | Span span = output.Buffer.Span[(i * output.Strides[0])..]; 70 | YoloLabel label = _model.Labels[(int)span[5]]; 71 | if (span[6] >= conf) 72 | { 73 | float xMin = (span[1] - xPad) * gain_inv; 74 | float yMin = (span[2] - yPad) * gain_inv; 75 | float xMax = (span[3] - xPad) * gain_inv; 76 | float yMax = (span[4] - yPad) * gain_inv; 77 | result.Add(new(label, new(xMin, yMin, xMax - xMin, yMax - yMin), span[6])); 78 | } 79 | }); 80 | return result.ToList(); 81 | } 82 | 83 | private List Suppress(List items, float iou_conf) 84 | { 85 | List result = new(items); 86 | foreach (YoloPrediction item in items) // iterate every prediction 87 | { 88 | foreach (YoloPrediction current in result.ToList()) // make a copy for each iteration 89 | { 90 | if (current == item) continue; 91 | float intArea = RectangleF.Intersect(item.Rectangle, current.Rectangle).Area(); 92 | if ((intArea / (item.Area + current.Area - intArea)) >= iou_conf) 93 | { 94 | if (item.Score >= current.Score) 95 | { 96 | result.Remove(current); 97 | } 98 | } 99 | } 100 | } 101 | return result; 102 | } 103 | 104 | private IDisposableReadOnlyCollection Inference(Image img) 105 | { 106 | NamedOnnxValue[] inputs = new[] // add image as onnx input 107 | { 108 | NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(Utils.ResizeImage(img, Imgsz, Imgsz))) 109 | }; 110 | 111 | return _inferenceSession.Run(inputs, _model.Outputs); // run inference 112 | } 113 | 114 | private void get_input_details() 115 | { 116 | Imgsz = _inferenceSession.InputMetadata["images"].Dimensions[2]; 117 | } 118 | 119 | private void get_output_details() 120 | { 121 | _model.Outputs = _inferenceSession.OutputMetadata.Keys.ToArray(); 122 | _model.Dimensions = _inferenceSession.OutputMetadata[_model.Outputs[0]].Dimensions[1]; 123 | N_Class = _inferenceSession.OutputMetadata.ToArray()[0].Value.Dimensions[1] - 4; 124 | } 125 | 126 | public void Dispose() 127 | { 128 | _inferenceSession.Dispose(); 129 | } 130 | 131 | public List Predict(Bitmap img, Dictionary class_conf, float conf_thres = 0, float iou_thres = 0) 132 | { 133 | using IDisposableReadOnlyCollection outputs = Inference(img); 134 | string firstOutput = _model.Outputs[0]; 135 | DenseTensor output = (DenseTensor)outputs.First(x => x.Name == firstOutput).Value; 136 | return Suppress(ParseDetect(output, img, conf_thres), iou_thres); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /OBB.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime; 2 | using Microsoft.ML.OnnxRuntime.Tensors; 3 | using System.Drawing.Drawing2D; 4 | using YOLO.Extentions; 5 | using System.Drawing; 6 | using Newtonsoft.Json; 7 | 8 | 9 | namespace YOLO 10 | { 11 | public class OBB 12 | { 13 | InferenceSession InferenceSession { get; set; } 14 | string[] OutputData { get; set; } 15 | int Imgsz { get; set; } 16 | float Imgsz_inv { get; set; } 17 | readonly int MAX_POSSIBLE_OBJECT; 18 | readonly int col_len; 19 | Dictionary? Labels { get; set; } 20 | Bitmap resized_img { get; set; } 21 | Graphics graphics { get; set; } 22 | NamedOnnxValue[] namedOnnxValues { get; set; } 23 | Dictionary col_len_cache { get; set; } 24 | public OBB(string model_path, bool use_cuda) 25 | { 26 | if (use_cuda) 27 | { 28 | OrtCUDAProviderOptions cudaProviderOptions = new(); 29 | cudaProviderOptions.UpdateOptions(new Dictionary() 30 | { 31 | { "cudnn_conv_use_max_workspace", "1" }, 32 | { "cudnn_conv1d_pad_to_nc1d", "1" }, 33 | { "arena_extend_strategy", "kSameAsRequested" }, 34 | { "do_copy_in_default_stream", "1" } 35 | }); 36 | SessionOptions sessionOptions = SessionOptions.MakeSessionOptionWithCudaProvider(cudaProviderOptions); 37 | sessionOptions.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; 38 | InferenceSession = new(model_path, sessionOptions); 39 | } 40 | else 41 | { 42 | InferenceSession = new(model_path); 43 | } 44 | Imgsz = InferenceSession.InputMetadata["images"].Dimensions[2]; 45 | Imgsz_inv = 1f / Imgsz; 46 | MAX_POSSIBLE_OBJECT = InferenceSession.OutputMetadata.ElementAt(0).Value.Dimensions[2]; 47 | OutputData = InferenceSession.OutputMetadata.Keys.ToArray(); 48 | col_len = InferenceSession.OutputMetadata.ElementAt(0).Value.Dimensions[1]; 49 | resized_img = new(Imgsz, Imgsz); 50 | graphics = Graphics.FromImage(resized_img); 51 | graphics.InterpolationMode = InterpolationMode.NearestNeighbor; 52 | namedOnnxValues = new NamedOnnxValue[1]; 53 | using Bitmap bitmap = new(Imgsz, Imgsz); 54 | namedOnnxValues[0] = NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(bitmap)); 55 | InferenceSession.Run(namedOnnxValues, OutputData); 56 | col_len_cache = []; 57 | Labels = []; 58 | for (int i = 0; i < col_len; i++) 59 | { 60 | col_len_cache.Add(i, i * MAX_POSSIBLE_OBJECT); 61 | } 62 | } 63 | 64 | public List Predict(Bitmap image, float conf, float iou_conf) 65 | { 66 | float x_scaler = image.Width * Imgsz_inv; 67 | float y_scaler = image.Height * Imgsz_inv; 68 | List predictions = []; 69 | ResizeImage(image); 70 | namedOnnxValues[0] = NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(resized_img)); 71 | Tensor output = InferenceSession.Run(namedOnnxValues, OutputData).ElementAt(0).AsTensor(); 72 | Parallel.For(0, MAX_POSSIBLE_OBJECT, j => 73 | { 74 | float max_score = 0f; 75 | int max_score_idx = 0; 76 | for (int i = 4; i < col_len - 1; i++) 77 | { 78 | float value = output.ElementAt(col_len_cache[i] + j); 79 | if (value > max_score) 80 | { 81 | max_score = value; 82 | max_score_idx = i - 4; 83 | if (max_score >= .5f) 84 | { 85 | break; 86 | } 87 | } 88 | } 89 | if (max_score >= conf) 90 | { 91 | YoloLabel label = new(max_score_idx, Labels.ElementAt(max_score_idx).Key, Labels.ElementAt(max_score_idx).Value); 92 | RectangleF rectangle = new((output.ElementAt(col_len_cache[0] + j) - output.ElementAt(col_len_cache[2] + j) * .5f) * x_scaler, 93 | (output.ElementAt(col_len_cache[1] + j) - output.ElementAt(col_len_cache[3] + j) * .5f) * y_scaler, 94 | output.ElementAt(col_len_cache[2] + j) * x_scaler, output.ElementAt(col_len_cache[3] + j) * y_scaler); 95 | float angle = output.ElementAt(col_len_cache[col_len - 1] + j); 96 | OBBPrediction prediction = new(label, rectangle, angle, max_score); 97 | predictions.Add(prediction); 98 | } 99 | }); 100 | return Suppress(predictions, iou_conf); 101 | } 102 | 103 | private List Suppress(List items, float iou_conf) 104 | { 105 | List result = new(items); 106 | foreach (OBBPrediction item in items) 107 | { 108 | foreach (OBBPrediction current in result.ToList()) // make a copy for each iteration 109 | { 110 | if (current != item) 111 | { 112 | float intArea = RectangleF.Intersect(item.Rectangle, current.Rectangle).Area(); 113 | if ((intArea / (item.Area + current.Area - intArea)) >= iou_conf) 114 | { 115 | if (item.Score >= current.Score) 116 | { 117 | result.Remove(current); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | return result; 124 | } 125 | 126 | public void ResizeImage(Image image) 127 | { 128 | graphics.DrawImage(image, 0, 0, Imgsz, Imgsz); 129 | } 130 | 131 | public void SetupColors(Color[] colors) 132 | { 133 | Dictionary classes = JsonConvert.DeserializeObject>(InferenceSession.ModelMetadata.CustomMetadataMap["names"])!; 134 | for (int i = 0; i < colors.Length; i++) 135 | { 136 | Labels.Add(classes.ElementAt(i).Value, colors[i]); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /RTDETR.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime; 2 | using Microsoft.ML.OnnxRuntime.Tensors; 3 | using System.Drawing.Drawing2D; 4 | using YOLO.Extentions; 5 | using System.Drawing; 6 | using Newtonsoft.Json; 7 | 8 | 9 | namespace YOLO 10 | { 11 | public class RTDETR 12 | { 13 | InferenceSession InferenceSession { get; set; } 14 | string[] OutputData { get; set; } 15 | int Imgsz { get; set; } 16 | readonly int MAX_POSSIBLE_OBJECT; 17 | readonly int N_CLASS; 18 | readonly int col_len; 19 | Dictionary? Labels { get; set; } 20 | Bitmap resized_img { get; set; } 21 | Graphics graphics { get; set; } 22 | NamedOnnxValue[] namedOnnxValues { get; set; } 23 | Dictionary col_len_caches { get; set; } 24 | public RTDETR(string model_path, bool use_cuda) 25 | { 26 | if (use_cuda) 27 | { 28 | OrtCUDAProviderOptions cudaProviderOptions = new(); 29 | cudaProviderOptions.UpdateOptions(new Dictionary() 30 | { 31 | { "cudnn_conv_use_max_workspace", "1" }, 32 | { "cudnn_conv1d_pad_to_nc1d", "1" }, 33 | { "arena_extend_strategy", "kSameAsRequested" }, 34 | { "do_copy_in_default_stream", "1" } 35 | }); 36 | SessionOptions sessionOptions = SessionOptions.MakeSessionOptionWithCudaProvider(cudaProviderOptions); 37 | sessionOptions.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; 38 | InferenceSession = new(model_path, sessionOptions); 39 | } 40 | else 41 | { 42 | InferenceSession = new(model_path); 43 | } 44 | Imgsz = InferenceSession.InputMetadata["images"].Dimensions[2]; 45 | MAX_POSSIBLE_OBJECT = InferenceSession.OutputMetadata.ElementAt(0).Value.Dimensions[1]; 46 | OutputData = InferenceSession.OutputMetadata.Keys.ToArray(); 47 | col_len = InferenceSession.OutputMetadata.ElementAt(0).Value.Dimensions[2]; 48 | N_CLASS = col_len - 4; 49 | resized_img = new(Imgsz, Imgsz); 50 | graphics = Graphics.FromImage(resized_img); 51 | graphics.InterpolationMode = InterpolationMode.NearestNeighbor; 52 | namedOnnxValues = new NamedOnnxValue[1]; 53 | using Bitmap bitmap = new(Imgsz, Imgsz); 54 | namedOnnxValues[0] = NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(bitmap)); 55 | InferenceSession.Run(namedOnnxValues, OutputData); 56 | col_len_caches = []; 57 | Labels = []; 58 | for (int i = 0; i < MAX_POSSIBLE_OBJECT; i++) 59 | { 60 | col_len_caches.Add(i, i * col_len); 61 | } 62 | } 63 | 64 | public void SetupColors(Color[] colors) 65 | { 66 | Dictionary classes = JsonConvert.DeserializeObject>(InferenceSession.ModelMetadata.CustomMetadataMap["names"])!; 67 | for (int i = 0; i < colors.Length; i++) 68 | { 69 | Labels.Add(classes.ElementAt(i).Value, colors[i]); 70 | } 71 | } 72 | 73 | public List Predict(Bitmap image, float conf, float iou_conf) 74 | { 75 | ResizeImage(image); 76 | namedOnnxValues[0] = NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(resized_img)); 77 | return Suppress(GetBboxes_n_Scores(InferenceSession.Run(namedOnnxValues, OutputData).ElementAt(0).AsTensor(), 78 | conf, image.Width, image.Height), iou_conf); 79 | } 80 | 81 | public List GetBboxes_n_Scores(Tensor input, float conf, int image_width, int image_height) 82 | { 83 | List predictions = []; 84 | Parallel.For(0, MAX_POSSIBLE_OBJECT, j => 85 | { 86 | float max_score = .0f; 87 | int max_score_idx = 0; 88 | int row_cache = col_len_caches[j]; 89 | for (int i = 0; i < N_CLASS; i++) 90 | { 91 | float value = input.ElementAt(row_cache + i + 4); 92 | if (value > max_score) 93 | { 94 | max_score = value; 95 | max_score_idx = i; 96 | if (max_score >= 0.5f) 97 | { 98 | break; 99 | } 100 | } 101 | } 102 | if (max_score >= conf) 103 | { 104 | predictions.Add( 105 | new( 106 | new(max_score_idx, 107 | Labels.ElementAt(max_score_idx).Key, 108 | Labels.ElementAt(max_score_idx).Value), 109 | new((input.ElementAt(row_cache) - input.ElementAt(row_cache + 2) * 0.5f) * image_width, 110 | (input.ElementAt(row_cache + 1) - input.ElementAt(row_cache + 3) * 0.5f) * image_height, 111 | input.ElementAt(row_cache + 2) * image_width, input.ElementAt(row_cache + 3) * image_height), 112 | max_score)); 113 | } 114 | }); 115 | return predictions; 116 | } 117 | 118 | public void ResizeImage(Image image) 119 | { 120 | graphics.DrawImage(image, 0, 0, Imgsz, Imgsz); 121 | } 122 | 123 | private List Suppress(List items, float iou_conf) 124 | { 125 | List result = new(items); 126 | foreach (YoloPrediction item in items) 127 | { 128 | foreach (YoloPrediction current in result.ToList()) // make a copy for each iteration 129 | { 130 | if (current != item) 131 | { 132 | float intArea = RectangleF.Intersect(item.Rectangle, current.Rectangle).Area(); 133 | if ((intArea / (item.Area + current.Area - intArea)) >= iou_conf) 134 | { 135 | if (item.Score >= current.Score) 136 | { 137 | result.Remove(current); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | return result; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Yolov9.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime; 2 | using Microsoft.ML.OnnxRuntime.Tensors; 3 | using System.Drawing.Drawing2D; 4 | using YOLO.Extentions; 5 | using System.Drawing; 6 | using Newtonsoft.Json; 7 | 8 | 9 | namespace YOLO 10 | { 11 | public class Yolov9 12 | { 13 | InferenceSession InferenceSession { get; set; } 14 | string[] OutputData { get; set; } 15 | int Imgsz { get; set; } 16 | float Imgsz_inv { get; set; } 17 | readonly int MAX_POSSIBLE_OBJECT; 18 | readonly int col_len; 19 | Dictionary? Labels { get; set; } 20 | Bitmap resized_img { get; set; } 21 | Graphics graphics { get; set; } 22 | NamedOnnxValue[] namedOnnxValues { get; set; } 23 | Dictionary col_len_caches { get; set; } 24 | public Yolov9(string model_path, bool use_cuda) 25 | { 26 | if (use_cuda) 27 | { 28 | OrtCUDAProviderOptions cudaProviderOptions = new(); 29 | cudaProviderOptions.UpdateOptions(new Dictionary() 30 | { 31 | { "cudnn_conv_use_max_workspace", "1" }, 32 | { "cudnn_conv1d_pad_to_nc1d", "1" }, 33 | { "arena_extend_strategy", "kSameAsRequested" }, 34 | { "do_copy_in_default_stream", "1" } 35 | }); 36 | SessionOptions sessionOptions = SessionOptions.MakeSessionOptionWithCudaProvider(cudaProviderOptions); 37 | sessionOptions.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; 38 | InferenceSession = new(model_path, sessionOptions); 39 | } 40 | else 41 | { 42 | InferenceSession = new(model_path); 43 | } 44 | Imgsz = InferenceSession.InputMetadata["images"].Dimensions[2]; 45 | Imgsz_inv = 1.0f / Imgsz; 46 | MAX_POSSIBLE_OBJECT = InferenceSession.OutputMetadata.ElementAt(0).Value.Dimensions[2]; 47 | OutputData = InferenceSession.OutputMetadata.Keys.ToArray(); 48 | col_len = InferenceSession.OutputMetadata.ElementAt(0).Value.Dimensions[1]; 49 | resized_img = new(Imgsz, Imgsz); 50 | graphics = Graphics.FromImage(resized_img); 51 | graphics.InterpolationMode = InterpolationMode.NearestNeighbor; 52 | namedOnnxValues = new NamedOnnxValue[1]; 53 | using Bitmap bitmap = new(Imgsz, Imgsz); 54 | namedOnnxValues[0] = NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(bitmap)); 55 | InferenceSession.Run(namedOnnxValues, OutputData); 56 | col_len_caches = []; 57 | Labels = []; 58 | for (int i = 0; i < col_len; i++) 59 | { 60 | col_len_caches.Add(i, i * MAX_POSSIBLE_OBJECT); 61 | } 62 | } 63 | 64 | public void SetupColors(Color[] colors) 65 | { 66 | Dictionary classes = JsonConvert.DeserializeObject>(InferenceSession.ModelMetadata.CustomMetadataMap["names"])!; 67 | for (int i = 0; i < colors.Length; i++) 68 | { 69 | Labels.Add(classes.ElementAt(i).Value, colors[i]); 70 | } 71 | } 72 | 73 | public List Predict(Bitmap image, float conf, float iou_conf) 74 | { 75 | ResizeImage(image); 76 | namedOnnxValues[0] = NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(resized_img)); 77 | return Suppress(GetBboxes_n_Scores(InferenceSession.Run(namedOnnxValues, OutputData).ElementAt(0).AsTensor(), 78 | conf, image.Width, image.Height), iou_conf); 79 | } 80 | 81 | public List GetBboxes_n_Scores(Tensor input, float conf, int image_width, int image_height) 82 | { 83 | List predictions = []; 84 | float width_scale = image_width * Imgsz_inv; 85 | float height_scale = image_height * Imgsz_inv; 86 | Parallel.For(0, MAX_POSSIBLE_OBJECT, i => 87 | { 88 | float max_score = .0f; 89 | int max_score_idx = 0; 90 | for (int j = 4; j < col_len; j++) 91 | { 92 | float value = input.ElementAt(col_len_caches[j] + i); 93 | if (value > max_score) 94 | { 95 | max_score = value; 96 | max_score_idx = j - 4; 97 | if (max_score >= 0.5f) 98 | { 99 | break; 100 | } 101 | } 102 | } 103 | if (max_score >= conf) 104 | { 105 | predictions.Add( 106 | new( 107 | new(max_score_idx, 108 | Labels.ElementAt(max_score_idx).Key, 109 | Labels.ElementAt(max_score_idx).Value), 110 | new((input.ElementAt(col_len_caches[0] + i) - input.ElementAt(col_len_caches[2] + i) * 0.5f) * width_scale, 111 | (input.ElementAt(col_len_caches[1] + i) - input.ElementAt(col_len_caches[3] + i) * 0.5f) * height_scale, 112 | input.ElementAt(col_len_caches[2] + i) * width_scale, input.ElementAt(col_len_caches[3] + i) * height_scale), 113 | max_score)); 114 | } 115 | }); 116 | return predictions; 117 | } 118 | 119 | public void ResizeImage(Image image) 120 | { 121 | graphics.DrawImage(image, 0, 0, Imgsz, Imgsz); 122 | } 123 | 124 | private List Suppress(List items, float iou_conf) 125 | { 126 | List result = new(items); 127 | foreach (YoloPrediction item in items) 128 | { 129 | foreach (YoloPrediction current in result.ToList()) // make a copy for each iteration 130 | { 131 | if (current != item) 132 | { 133 | float intArea = RectangleF.Intersect(item.Rectangle, current.Rectangle).Area(); 134 | if ((intArea / (item.Area + current.Area - intArea)) >= iou_conf) 135 | { 136 | if (item.Score >= current.Score) 137 | { 138 | result.Remove(current); 139 | } 140 | } 141 | } 142 | } 143 | } 144 | return result; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Extentions/Utils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime.Tensors; 2 | using System.Drawing.Drawing2D; 3 | using System.Drawing.Imaging; 4 | using System.Drawing; 5 | 6 | 7 | namespace YOLO.Extentions 8 | { 9 | public static class Utils 10 | { 11 | public static Bitmap ResizeImage(Image image, int target_width, int target_height) 12 | { 13 | Bitmap output = new(target_width, target_height, image.PixelFormat); 14 | var (w, h) = ((float)image.Width, (float)image.Height); // image width and height 15 | var (xRatio, yRatio) = (target_width / w, target_height / h); // x, y ratios 16 | float ratio = Math.Min(xRatio, yRatio); // ratio = resized / original 17 | var (width, height) = ((int)(w * ratio), (int)(h * ratio)); // roi width and height 18 | var (x, y) = ((int)((target_width * 0.5f) - (width * 0.5f)), (int)((target_height * 0.5f) - (height * 0.5f))); // roi x and y coordinates 19 | using Graphics graphics = Graphics.FromImage(output); 20 | graphics.Clear(Color.FromArgb(0, 0, 0, 0)); // clear canvas 21 | graphics.SmoothingMode = SmoothingMode.None; // no smoothing 22 | graphics.InterpolationMode = InterpolationMode.NearestNeighbor; // nn interpolation 23 | graphics.PixelOffsetMode = PixelOffsetMode.Half; // half pixel offset 24 | graphics.DrawImage(image, new Rectangle(x, y, width, height)); // draw scaled 25 | return output; 26 | } 27 | 28 | //https://github.com/ivilson/Yolov7net/issues/17 29 | public static Tensor ExtractPixels2(Bitmap bitmap) 30 | { 31 | int pixelCount = bitmap.Width * bitmap.Height; 32 | Rectangle rectangle = new(0, 0, bitmap.Width, bitmap.Height); 33 | DenseTensor tensor = new(new[] { 1, 3, bitmap.Height, bitmap.Width }); 34 | Span data; 35 | 36 | BitmapData bitmapData; 37 | if (bitmap.PixelFormat == PixelFormat.Format24bppRgb && bitmap.Width % 4 == 0) 38 | { 39 | bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 40 | 41 | unsafe 42 | { 43 | data = new Span((void*)bitmapData.Scan0, bitmapData.Height * bitmapData.Stride); 44 | } 45 | 46 | ExtractPixelsRgb(tensor, data, pixelCount); 47 | } 48 | else 49 | { 50 | // force convert to 32 bit PArgb 51 | bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb); 52 | 53 | unsafe 54 | { 55 | data = new Span((void*)bitmapData.Scan0, bitmapData.Height * bitmapData.Stride); 56 | } 57 | 58 | ExtractPixelsArgb(tensor, data, pixelCount); 59 | } 60 | 61 | bitmap.UnlockBits(bitmapData); 62 | 63 | return tensor; 64 | } 65 | 66 | public static void ExtractPixelsArgb(DenseTensor tensor, Span data, int pixelCount) 67 | { 68 | Span spanR = tensor.Buffer.Span; 69 | Span spanG = spanR[pixelCount..]; 70 | Span spanB = spanG[pixelCount..]; 71 | 72 | int sidx = 0; 73 | for (int i = 0; i < pixelCount; i++) 74 | { 75 | spanR[i] = data[sidx + 2] * 0.0039215686274509803921568627451f; 76 | spanG[i] = data[sidx + 1] * 0.0039215686274509803921568627451f; 77 | spanB[i] = data[sidx] * 0.0039215686274509803921568627451f; 78 | sidx += 4; 79 | } 80 | } 81 | 82 | public static void ExtractPixelsRgb(DenseTensor tensor, Span data, int pixelCount) 83 | { 84 | Span spanR = tensor.Buffer.Span; 85 | Span spanG = spanR[pixelCount..]; 86 | Span spanB = spanG[pixelCount..]; 87 | 88 | int sidx = 0; 89 | for (int i = 0; i < pixelCount; i++) 90 | { 91 | spanR[i] = data[sidx + 2] * 0.0039215686274509803921568627451f; 92 | spanG[i] = data[sidx + 1] * 0.0039215686274509803921568627451f; 93 | spanB[i] = data[sidx] * 0.0039215686274509803921568627451f; 94 | sidx += 3; 95 | } 96 | } 97 | 98 | public static float Clamp(float value, float min, float max) 99 | { 100 | return value < min ? min : value > max ? max : value; 101 | } 102 | 103 | public static Image DrawBoundingBox(Image image, List predictions, int bounding_box_thickness, int font_size) 104 | { 105 | using Graphics graphics = Graphics.FromImage(image); 106 | for (int i = 0; i < predictions.Count; i++) 107 | { 108 | float score = (float)Math.Round(predictions[i].Score, 2); 109 | graphics.DrawRectangles(new(predictions[i].Label.Color, bounding_box_thickness), new[] { predictions[i].Rectangle }); 110 | graphics.DrawString($"{predictions[i].Label.Name} ({score})", 111 | new("Consolas", font_size, GraphicsUnit.Pixel), new SolidBrush(predictions[i].Label.Color), 112 | new PointF(predictions[i].Rectangle.X, predictions[i].Rectangle.Y)); 113 | } 114 | return image; 115 | } 116 | 117 | public static Image DrawRotatedBoundingBox(Image image, List predictions, int bounding_box_thickness, int font_size) 118 | { 119 | using Graphics graphics = Graphics.FromImage(image); 120 | for (int i = 0; i < predictions.Count; i++) 121 | { 122 | float score = (float)Math.Round(predictions[i].Score, 2); 123 | graphics.DrawPolygon(new(predictions[i].Label.Color, bounding_box_thickness), GetRotatedPoints(predictions[i])); 124 | graphics.DrawString($"{predictions[i].Label.Name} ({score})", 125 | new("Consolas", font_size, GraphicsUnit.Pixel), new SolidBrush(predictions[i].Label.Color), 126 | new PointF(predictions[i].Rectangle.X, predictions[i].Rectangle.Y)); 127 | } 128 | return image; 129 | } 130 | 131 | public static PointF[] GetRotatedPoints(OBBPrediction prediction) 132 | { 133 | PointF[] points = new PointF[] 134 | { 135 | new(prediction.Rectangle.X, prediction.Rectangle.Y), 136 | new(prediction.Rectangle.X + prediction.Rectangle.Width, prediction.Rectangle.Y), 137 | new(prediction.Rectangle.X + prediction.Rectangle.Width, prediction.Rectangle.Y + prediction.Rectangle.Height), 138 | new(prediction.Rectangle.X, prediction.Rectangle.Y + prediction.Rectangle.Height) 139 | }; 140 | OpenCvSharp.Point2f middle = new(prediction.Rectangle.X + prediction.Rectangle.Width * .5f, 141 | prediction.Rectangle.Y + prediction.Rectangle.Height * .5f); 142 | float cos_angle = (float)Math.Cos(prediction.Angle); 143 | float sin_angle = (float)Math.Sin(prediction.Angle); 144 | for (int i = 0; i < 4; i++) 145 | { 146 | float offset_x = middle.X - points[i].X; 147 | float offset_y = middle.Y - points[i].Y; 148 | float rotated_x = offset_x * cos_angle - offset_y * sin_angle; 149 | float rotated_y = offset_x * sin_angle + offset_y * cos_angle; 150 | points[i] = new(middle.X + rotated_x, middle.Y + rotated_y); 151 | } 152 | return points; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Yolov8.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime; 2 | using Microsoft.ML.OnnxRuntime.Tensors; 3 | using Newtonsoft.Json; 4 | using System.Collections.Concurrent; 5 | using System.Drawing; 6 | using YOLO.Extentions; 7 | using YOLO.Models; 8 | 9 | 10 | namespace YOLO 11 | { 12 | public class Yolov8 : IDisposable 13 | { 14 | private readonly InferenceSession _inferenceSession; 15 | private readonly YoloModel _model = new(); 16 | int Imgsz { get; set; } 17 | 18 | public Yolov8(string modelPath, bool useCuda = false) 19 | { 20 | 21 | if (useCuda) 22 | { 23 | OrtCUDAProviderOptions cudaProviderOptions = new(); 24 | cudaProviderOptions.UpdateOptions(new() 25 | { 26 | { "cudnn_conv_use_max_workspace", "1" }, 27 | { "cudnn_conv1d_pad_to_nc1d", "1" }, 28 | { "arena_extend_strategy", "kSameAsRequested" }, 29 | { "do_copy_in_default_stream", "1" } 30 | }); 31 | SessionOptions opts = SessionOptions.MakeSessionOptionWithCudaProvider(cudaProviderOptions); 32 | opts.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; 33 | _inferenceSession = new(modelPath, opts); 34 | } 35 | else 36 | { 37 | SessionOptions opts = new(); 38 | _inferenceSession = new(modelPath, opts); 39 | } 40 | // Get model info 41 | get_input_details(); 42 | get_output_details(); 43 | using Bitmap bitmap = new(Imgsz, Imgsz); 44 | NamedOnnxValue[] inputs = [NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(bitmap))]; 45 | _inferenceSession.Run(inputs, _model.Outputs); 46 | } 47 | 48 | public void SetupColors(Color[] colors) 49 | { 50 | Dictionary classes = JsonConvert.DeserializeObject>(_inferenceSession.ModelMetadata.CustomMetadataMap["names"])!; 51 | for (int i = 0; i < colors.Length; i++) 52 | { 53 | _model.Labels.Add(new(i, classes.ElementAt(i).Value, colors[i])); 54 | } 55 | } 56 | 57 | public List Predict(Bitmap image, float conf_thres = 0, float iou_thres = 0) 58 | { 59 | using IDisposableReadOnlyCollection outputs = Inference(image); 60 | return Suppress(ParseOutput(outputs, image, conf_thres), iou_thres); 61 | } 62 | 63 | /// 64 | /// Removes overlapped duplicates (nms). 65 | /// 66 | private List Suppress(List items, float iou_conf) 67 | { 68 | List result = new(items); 69 | foreach (YoloPrediction item in items) // iterate every prediction 70 | { 71 | foreach (YoloPrediction current in items.ToList()) // make a copy for each iteration 72 | { 73 | if (current != item) 74 | { 75 | float intArea = RectangleF.Intersect(item.Rectangle, current.Rectangle).Area(); 76 | if (intArea / (item.Area + current.Area - intArea) >= iou_conf) 77 | { 78 | if (item.Score >= current.Score) 79 | { 80 | result.Remove(current); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | return result; 87 | } 88 | 89 | public YoloClassifyPrediction ClassifyPredict(Image img) 90 | { 91 | NamedOnnxValue[] inputs = 92 | [ 93 | NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(Utils.ResizeImage(img, Imgsz, Imgsz))) 94 | ]; 95 | IDisposableReadOnlyCollection result = _inferenceSession.Run(inputs); 96 | 97 | List> output = []; 98 | 99 | foreach (string item in _model.Outputs) // add outputs for processing 100 | { 101 | output.Add(result.First(x => x.Name == item).Value as DenseTensor); 102 | } 103 | 104 | float[] index_prob = Argmax_Score(output[0]); 105 | return new YoloClassifyPrediction(_model.Labels[(int)index_prob[0]], index_prob[1]); 106 | } 107 | 108 | private float[] Argmax_Score(DenseTensor probs) 109 | { 110 | int max_index = 0; 111 | float max_prob = 0.0f; 112 | int i = 0; 113 | foreach (float prob in probs) 114 | { 115 | if (prob > max_prob) 116 | { 117 | max_prob = prob; 118 | max_index = i; 119 | } 120 | ++i; 121 | if (prob >= 0.5f) 122 | { 123 | break; 124 | } 125 | } 126 | return [max_index, max_prob]; 127 | } 128 | 129 | private IDisposableReadOnlyCollection Inference(Image img) 130 | { 131 | NamedOnnxValue[] inputs = 132 | [ 133 | NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(Utils.ResizeImage(img, Imgsz, Imgsz))) 134 | ]; 135 | 136 | return _inferenceSession.Run(inputs, _model.Outputs); 137 | } 138 | 139 | private List ParseOutput(IDisposableReadOnlyCollection outputs, Image image, float conf) 140 | { 141 | string firstOutput = _model.Outputs[0]; 142 | DenseTensor output = (DenseTensor)outputs.First(x => x.Name == firstOutput).Value; 143 | return ParseDetect(output, image, conf); 144 | } 145 | 146 | private List ParseDetect(DenseTensor output, Image image, float conf) 147 | { 148 | ConcurrentBag result = []; 149 | var (w, h) = ((float)image.Width, (float)image.Height); 150 | var (xGain, yGain) = (Imgsz / w, Imgsz / h); 151 | float gain = Math.Min(xGain, yGain); 152 | float gain_inv = 1.0f / gain; 153 | var (xPad, yPad) = ((Imgsz - w * gain) * 0.5f, (Imgsz - h * gain) * 0.5f); 154 | 155 | Parallel.For(0, output.Dimensions[0], i => 156 | { 157 | Parallel.For(0, (int)(output.Length / output.Dimensions[1]), j => 158 | { 159 | int dim = output.Strides[1]; 160 | Span span = output.Buffer.Span[(i * output.Strides[0])..]; 161 | 162 | float a = span[j]; 163 | float b = span[dim + j]; 164 | float c = span[2 * dim + j]; 165 | float d = span[3 * dim + j]; 166 | float x_min = a - c * 0.5f - xPad; 167 | float y_min = b - d * 0.5f - yPad; 168 | float width = (a + c * 0.5f - xPad - x_min) * gain_inv; 169 | float height = (b + d * 0.5f - yPad - y_min) * gain_inv; 170 | 171 | for (int l = 0; l < _model.Dimensions - 4; l++) 172 | { 173 | float pred = span[(4 + l) * dim + j]; 174 | 175 | if (pred >= conf) 176 | { 177 | result.Add(new(_model.Labels[l], new(x_min * gain_inv, y_min * gain_inv, width, height), pred)); 178 | } 179 | } 180 | }); 181 | }); 182 | return [.. result]; 183 | } 184 | 185 | private void get_input_details() 186 | { 187 | Imgsz = _inferenceSession.InputMetadata["images"].Dimensions[2]; 188 | } 189 | 190 | private void get_output_details() 191 | { 192 | _model.Outputs = _inferenceSession.OutputMetadata.Keys.ToArray(); 193 | _model.Dimensions = _inferenceSession.OutputMetadata[_model.Outputs[0]].Dimensions[1]; 194 | } 195 | 196 | public void Dispose() 197 | { 198 | _inferenceSession.Dispose(); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Yolov5.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ML.OnnxRuntime; 2 | using Microsoft.ML.OnnxRuntime.Tensors; 3 | using System.Collections.Concurrent; 4 | using System.Drawing; 5 | using YOLO.Extentions; 6 | using YOLO.Models; 7 | 8 | 9 | namespace YOLO 10 | { 11 | /// 12 | /// yolov5、yolov6 模型,不包含nms结果 13 | /// 14 | public class Yolov5 : IDisposable 15 | { 16 | private readonly InferenceSession _inferenceSession; 17 | private readonly YoloModel _model = new(); 18 | int Imgsz { get; set; } 19 | int N_Class { get; set; } 20 | 21 | public Yolov5(string modelPath, bool useCuda = false) 22 | { 23 | 24 | if (useCuda) 25 | { 26 | OrtCUDAProviderOptions cudaProviderOptions = new(); 27 | cudaProviderOptions.UpdateOptions(new Dictionary() 28 | { 29 | { "cudnn_conv_use_max_workspace", "1" }, 30 | { "cudnn_conv1d_pad_to_nc1d", "1" }, 31 | { "arena_extend_strategy", "kSameAsRequested" }, 32 | { "do_copy_in_default_stream", "1" } 33 | }); 34 | SessionOptions opts = SessionOptions.MakeSessionOptionWithCudaProvider(cudaProviderOptions); 35 | opts.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; 36 | _inferenceSession = new InferenceSession(modelPath, opts); 37 | } 38 | else 39 | { 40 | SessionOptions opts = new(); 41 | _inferenceSession = new InferenceSession(modelPath, opts); 42 | } 43 | 44 | // Get model info 45 | get_input_details(); 46 | get_output_details(); 47 | 48 | using Bitmap bitmap = new(Imgsz, Imgsz); 49 | NamedOnnxValue[] inputs = [NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(bitmap))]; 50 | _inferenceSession.Run(inputs, _model.Outputs); 51 | } 52 | 53 | public void SetupLabels(Dictionary color_mapper) 54 | { 55 | int i = 0; 56 | foreach (KeyValuePair keyValuePair in color_mapper) 57 | { 58 | _model.Labels.Add(new(i, keyValuePair.Key, keyValuePair.Value)); 59 | ++i; 60 | } 61 | } 62 | 63 | public List Predict(Bitmap image, Dictionary class_conf, float conf_thres = 0, float iou_thres = 0) 64 | { 65 | 66 | using IDisposableReadOnlyCollection outputs = Inference(image); 67 | return Suppress(ParseOutput(outputs, image, class_conf, conf_thres), iou_thres); 68 | } 69 | 70 | public YoloClassifyPrediction ClassifyPredict(Image img) 71 | { 72 | NamedOnnxValue[] inputs = new[] // add image as onnx input 73 | { 74 | NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(Utils.ResizeImage(img, Imgsz, Imgsz))) 75 | }; 76 | IDisposableReadOnlyCollection result = _inferenceSession.Run(inputs); // run inference 77 | 78 | List> output = new(); 79 | 80 | foreach (string item in _model.Outputs) // add outputs for processing 81 | { 82 | output.Add(result.First(x => x.Name == item).Value as DenseTensor); 83 | } 84 | 85 | float[] index_prob = Argmax_Score(Softmax(output[0].ToArray())); 86 | return new YoloClassifyPrediction(_model.Labels[(int)index_prob[0]], index_prob[1]); 87 | } 88 | 89 | private float[] Argmax_Score(float[] probs) 90 | { 91 | int max_index = 0; 92 | float max_prob = 0.0f; 93 | int i = 0; 94 | foreach (float prob in probs) 95 | { 96 | if (prob > max_prob) 97 | { 98 | max_prob = prob; 99 | max_index = i; 100 | } 101 | ++i; 102 | if (prob >= 0.5f) 103 | { 104 | break; 105 | } 106 | } 107 | return new float[] { max_index, max_prob }; 108 | } 109 | 110 | private float[] Softmax(float[] input) 111 | { 112 | float[] prob_vec = new float[input.Length]; 113 | float deno = 0.0f; 114 | foreach (float num in input) 115 | { 116 | deno += (float)Math.Exp(num); 117 | } 118 | 119 | float deno_inv = 1.0f / deno; 120 | for (int i = 0; i < prob_vec.Length; ++i) 121 | { 122 | prob_vec[i] = (float)Math.Exp(input[i]) * deno_inv; 123 | } 124 | 125 | return prob_vec; 126 | } 127 | 128 | /// 129 | /// Removes overlaped duplicates (nms). 130 | /// 131 | private List Suppress(List items, float iou_conf) 132 | { 133 | List result = new(items); 134 | foreach (YoloPrediction item in items) // iterate every prediction 135 | { 136 | foreach (YoloPrediction current in items.ToList()) // make a copy for each iteration 137 | { 138 | if (current != item) 139 | { 140 | float intArea = RectangleF.Intersect(item.Rectangle, current.Rectangle).Area(); 141 | if (intArea / (item.Area + current.Area - intArea) >= iou_conf) 142 | { 143 | if (item.Score >= current.Score) 144 | { 145 | result.Remove(current); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | return result; 152 | } 153 | 154 | private IDisposableReadOnlyCollection Inference(Image img) 155 | { 156 | NamedOnnxValue[] inputs = new[] // add image as onnx input 157 | { 158 | NamedOnnxValue.CreateFromTensor("images", Utils.ExtractPixels2(Utils.ResizeImage(img, Imgsz, Imgsz))) 159 | }; 160 | 161 | return _inferenceSession.Run(inputs, _model.Outputs); // run inference 162 | } 163 | 164 | private List ParseOutput(IDisposableReadOnlyCollection outputs, Image image, Dictionary class_conf, float conf_thres) 165 | { 166 | string firstOutput = _model.Outputs[0]; 167 | DenseTensor output = (DenseTensor)outputs.First(x => x.Name == firstOutput).Value; 168 | return ParseDetect(output, image, class_conf, conf_thres); 169 | } 170 | 171 | private List ParseDetect(DenseTensor output, Image image, Dictionary class_conf, float conf_thres) 172 | { 173 | ConcurrentBag result = new(); 174 | 175 | var (w, h) = ((float)image.Width, (float)image.Height); // image w and h 176 | var (xGain, yGain) = (Imgsz / w, Imgsz / h); // x, y gains 177 | float gain = Math.Min(xGain, yGain); // gain = resized / original 178 | float gain_inv = 1.0f / gain; 179 | var (xPad, yPad) = ((Imgsz - w * gain) * 0.5f, (Imgsz - h * gain) * 0.5f); // left, right pads 180 | 181 | Parallel.For(0, (int)output.Length / _model.Dimensions, i => 182 | { 183 | var span = output.Buffer.Span[(i * _model.Dimensions)..]; 184 | if (span[4] <= conf_thres) return; // skip low obj_conf results 185 | 186 | for (int j = 5; j < _model.Dimensions; j++) 187 | { 188 | span[j] *= span[4]; // mul_conf = obj_conf * cls_conf 189 | } 190 | 191 | float xMin = Utils.Clamp((span[0] - span[2] * 0.5f - xPad) * gain_inv, 0, w); // unpad bbox tlx to original 192 | float yMin = Utils.Clamp((span[1] - span[3] * 0.5f - yPad) * gain_inv, 0, h); // unpad bbox tly to original 193 | float xMax = Utils.Clamp((span[0] + span[2] * 0.5f - xPad) * gain_inv, 0, w - 1); // unpad bbox brx to original 194 | float yMax = Utils.Clamp((span[1] + span[3] * 0.5f - yPad) * gain_inv, 0, h - 1); // unpad bbox bry to original 195 | 196 | for (int k = 5; k < _model.Dimensions; k++) 197 | { 198 | YoloLabel label = _model.Labels[k - 5]; 199 | if (span[k] <= conf_thres || span[k] <= class_conf[label.Name]) continue; // skip low mul_conf results 200 | result.Add(new(label, new(xMin, yMin, xMax - xMin, yMax - yMin), span[k])); 201 | } 202 | }); 203 | 204 | return result.ToList(); 205 | } 206 | 207 | private void get_input_details() 208 | { 209 | Imgsz = _inferenceSession.InputMetadata["images"].Dimensions[2]; 210 | } 211 | 212 | private void get_output_details() 213 | { 214 | _model.Outputs = _inferenceSession.OutputMetadata.Keys.ToArray(); 215 | _model.Dimensions = _inferenceSession.OutputMetadata[_model.Outputs[0]].Dimensions[2]; 216 | N_Class = _inferenceSession.OutputMetadata.ToArray()[0].Value.Dimensions[2] - 5; 217 | } 218 | 219 | public void Dispose() 220 | { 221 | _inferenceSession.Dispose(); 222 | } 223 | } 224 | } 225 | --------------------------------------------------------------------------------