YoLo运用学习7


前言

根据YoLo运用学习3所说,实现在C#环境使用YoLo模型进行AI推理检测,最常见有两种:
1、python训练好YoLo的模型pt格式,将其转化为onnx格式,通过C#的相关库去加载并推理onnx格式并训练好的YoLo模型
2、直接通过python训练好YoLo模型后直接去识别检测图片,将定位、置信阈值等信息传入Halcon,进行显示。

目前AI推理检测所记录为第一种:加载图片->将图片预处理(图片缩放,图片bgr值转成onnx格式的输入张量)转成输入张量->将输入张量创建输入容器,运行推理->将推理结果绘画到winform的pictureBox。
流程图如下:
在这里插入图片描述


一、C#环境使用YoLo模型进行AI推理检测

1.加载图片

代码如下(示例):

OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "图文件(*.*)|*.jpg;*.png;*.jpeg;*.bmp";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
    //获取用户选择推断的图像路径
	imgUr = openFileDialog.FileName;
	//把图像显示到图像控件上面
	bitmap = new Bitmap(openFileDialog.FileName);
	//绑定图像到控件显示
	pic_result.Image = bitmap;
}

2.图片预处理

说明:将图片缩放并归一化,将图片BGR\RGB值转成onnx格式输入张量。
代码如下(示例):

/// <summary>
/// OpenCV默认使用 BGR 通道顺序
/// </summary>
public static DenseTensor<float> BitmapToOnnxInput(Bitmap pic, int[] inputT)
{
    #region 图片缩放成目标张量的宽高

    //图片宽高
    float picW = pic.Width;
    float picH = pic.Height;

    //目标张量宽高(640,640)
    float goalW = inputT[3];
    float goalH = inputT[2];

    if(picW > goalW || picH > goalH)
    {
        //图片缩放
        zoom = (goalW / picW) < (goalH / picH) ? (goalW / picW) : (goalH / picH);

        picW = picW * zoom;
        picH = picH * zoom;
    }

    #endregion

    #region 缩放后重新创建一个图片和张量

    //创建一个空张量
    float[,,] tempData = new float[inputT[1], inputT[2], inputT[3]];
    //创建一个缩放后图
    Bitmap bitmap = new Bitmap(pic, (int)picW, (int)picH);

    #endregion

    //将缩放后的图片bgr值归一化到[0,1]范围
    for(int y = 0; y < bitmap.Height; y++)
    {
        for(int x = 0; x < bitmap.Width; x++)
        {
            Color pixe1 = bitmap.GetPixel(x, y);
            //将BGR值归一化到[0,1]范围
            tempData[0, y, x] = pixe1.B / 255.0f; //B通道
            tempData[1, y, x] = pixe1.G / 255.0f; //G通道
            tempData[2, y, x] = pixe1.R / 255.0f; //R通道
        }
    }

    //创建一个数组
    float[] unfoldData = new float[inputT[1]*inputT[2]*inputT[3]];
    //将tempData的数据填充到unfoldData数组中
    Buffer.BlockCopy(tempData, 0, unfoldData, 0, unfoldData.Length * sizeof(float));

    return new DenseTensor<float>(unfoldData, inputT);
}

3.加载onnx模型并推理

说明:使用InferenceSession从模型文件加载ONNX模型并推理
安装:Microsoft.ML.OnnxRuntime库、Microsoft.ML.OnnxRuntime.Gpu库

在CPU上运行

using Microsoft.ML.OnnxRuntime;

string modelPath = "path/to/your/model.onnx";
var session = new InferenceSession(modelPath);

在GPU上运行

using Microsoft.ML.OnnxRuntime;

//需使用SessionOptions配置GPU设备
string modelPath = "path/to/your/model.onnx";
int gpuDeviceId = 0; // GPU设备ID,通常为0
var sessionOptions = SessionOptions.MakeSessionOptionWithCudaProvider(gpuDeviceId);
var session = new InferenceSession(modelPath, sessionOptions);

代码如下(示例):

//构建要模型文件的路径
var modelPath = Path.Combine(rootPath, "best.onnx");
var session = new InferenceSession(modelPath);

//输入名称:images
string inputName = session.InputNames.First();
//输入张量:[1,3,640,640]
int[] inputTensor = session.InputMetadata[inputName].Dimensions;

//提取当前检测模型标签类型数组
var modelMessage = session.ModelMetadata.CustomMetadataMap;
if (modelMessage.Keys.Contains("names"))
{
	//将标签字符串分割并放入string类型数组内
    labels = SplitLabels(modelMessage["names"]);
}
else
{
    labels = [];
}

var tensor = BitmapToOnnxInput(bitmap, inputTensor);
//创建输入容器
var inputs = new List<NamedOnnxValue> {
    NamedOnnxValue.CreateFromTensor(inputName, tensor)
};

//运行推理
var results = session.Run(inputs);

//处理结果
outputTensor = results.First().AsTensor<float>();

分割模型标签类型字符串成string类型数组方法

private string[] SplitLabels(string labels)
{
    //temp1: 0: '马叔', 1: '彪哥', 2: '玉芬', 3: '桂英'
    string temp1 = labels.Replace("{", "").Replace("}", "");
    string[] temp2 = temp1.Split(',');
    string[] temp3 = new string[temp2.Length];

    for (int i = 0; i < temp2.Length; i++)
    {
        int start = temp2[i].IndexOf(':') + 3;
        int end = temp2[i].Length - 1;
        temp3[i] = temp2[i].Substring(start, end - start);
    }

    return temp3;
}

4.数据后处理

说明:将输出张量outputTensor进行处理,解析检测框、类型和置信度等信息放到一个集合
代码如下(示例):

private List<float[]> GetOutputValue(Tensor<float> outputTensor)
{
    List<float[]> filteredResults = new List<float[]>();

    //解析张量,将张量转换为数组
    float[] dataArray = outputTensor.ToArray();

    //批量大小
    int batchSize = outputTensor.Dimensions[0];
    //每个检测框的属性数量
    int numDetections = outputTensor.Dimensions[1];
    //检测框的数量
    int outputSize = outputTensor.Dimensions[2];

    //遍历检测结果
    for(int i = 0; i < batchSize; i++) //遍历批量
    {
        for(int j = 0; j < numDetections; j++) //遍历检测框
        {
            //计算当前检测框的起始索引
            int startIndex = i * outputSize * numDetections + j * numDetections;

            float[] result = new float[6];

            result[0] = dataArray[startIndex + 0]; // x:边界框中心点的 x 坐标
            result[1] = dataArray[startIndex + 1]; // y:边界框中心点的 y 坐标
            result[2] = dataArray[startIndex + 2]; // w:边界框的宽度
            result[3] = dataArray[startIndex + 3]; // h:边界框的高度
            result[4] = dataArray[startIndex + 4]; // 置信度
            result[5] = (int)dataArray[startIndex + 5]; // 类别ID

            filteredResults.Add(result);
        }
    }

    return filteredResults;
}

5.图片属性值缩放还原和反归一化

目前主要参数:

//解析检测框、置信度、类型等信息放入一个集合
List<float[]> reArray = GetOutputValue(outputTensor);

代码如下(示例):

//imgWidth 和 imgHeight:原始图像的宽度和高度
public void RestorePoint(ref List<float[]> data, Bitmap bitmap)
{
    if (data.Count > 0)
    {
        if (data[0].Length >= 4) // 确保每个数组至少有4个元素(边界框信息)
        {
            for (int i = 0; i < data.Count; i++)
            {
                // 反归一化中心点坐标
                // x_center * bitmap.Width
                // y_center * bitmap.Height
                
                //缩放还原
                data[i][0] = data[i][0] / zoom;
                data[i][1] = data[i][1] / zoom;

                // 反归一化宽度和高度
                //width * bitmap.Width
                //height * bitmap.Height

                //缩放还原
                data[i][2] = data[i][2] / zoom;
                data[i][3] = data[i][3] / zoom;
            }
        }
    }
}

6.将解析到的信息的集合绘制成处理后图片

说明:将解析到信息的集合绘制成bitmap类型图片

目前主要参数:

//解析检测框、置信度、类型等信息放入一个集合
List<float[]> reArray = GetOutputValue(outputTensor);

使用System.Drawing类型

private Bitmap DrawDetectionsBySysDraw(Bitmap bitmap, List<float[]> dates, string[] labels)
{
    Bitmap rePic = new Bitmap(bitmap.Width, bitmap.Height);
    Graphics G = Graphics.FromImage(rePic);

    //将原始图片绘制到新Bitmap上
    G.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);

    //设置绘图质量
    G.SmoothingMode = SmoothingMode.AntiAlias;
    G.InterpolationMode = InterpolationMode.HighQualityBicubic;
    G.PixelOffsetMode = PixelOffsetMode.HighQuality;

    //定义字体和画笔
    Font font = new Font("Arial", 12);
    Brush brush = Brushes.Red;
    Pen pen = new Pen(Color.Red, 2);
    
    //遍历检测框并绘制
    for (int i = 0; i < dates.Count; i++)
    {
        float[] detection = dates[i];
        //string label = labels[i];

        //解析检测框坐标和置信度
        float x = detection[0];          //边界框中心点的 x 坐标
        float y = detection[1];          //边界框中心点的 y 坐标
        float width = detection[2];      //边界框的宽度
        float height = detection[3];     //边界框的高度
        float confidence = detection[4]; //置信度

        //绘制检测框
        G.DrawRectangle(pen, x, y, width, height);

        //绘制标签和置信度
        string text = $"{confidence * 100:0.00}%";
        SizeF textSize = G.MeasureString(text, font);

        //绘制文本背景
        G.FillRectangle(Brushes.Blue, x, y - textSize.Height - 5, textSize.Width + 10, textSize.Height + 10);

        //绘制文本
        G.DrawString(text, font, brush, x + 5, y - textSize.Height - 5);

    }

    return rePic;
}

使用OpenCvSharp类型

 public Bitmap DrawDetectionsByOpenCV(Bitmap bitmap, List<float[]> dates, string[] labels)
 {
     // 将Bitmap转换成OpenCvSharp的Mat对象
     Mat image = BitmapConverter.ToMat(bitmap);

     //遍历dates集合,绘制检测框和标签
     for (int i = 0; i < dates.Count; i++)
     {
         float[] detection = dates[i];

         //解析检测框坐标和置信度
         float x = detection[0];          //边界框中心点的 x 坐标
         float y = detection[1];          //边界框中心点的 y 坐标
         float width = detection[2];      //边界框的宽度
         float height = detection[3];     //边界框的高度

         // 绘制矩形框
         Cv2.Rectangle(image, new OpenCvSharp.Point(x, y), new OpenCvSharp.Point(x + width, y + height), Scalar.Red, 2);
         // 绘制标签
         Cv2.PutText(image, labels[1], new OpenCvSharp.Point(x, y - 10), HersheyFonts.HersheyScriptSimplex, 0.5, Scalar.Red, 1);
     }

     //将绘制后的Mat对象转换回Bitmap
     Bitmap resultBitmap = BitmapConverter.ToBitmap(image);

     return resultBitmap;
 }

总结

目前未能实现成功,实现模型视觉识别。但需进一步调试,目前问题有两个。
一个是检测框使用C#中winform显示都会在最左上角,一个是检测框显示个数、置信度显示不对。原因:有可能:1、推理yolo模型代码有问题 2、图片属性还原或绘制代码有误,需跟进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值