在VS的winform中基于C#和OPenCV实现基于python的yolo模型深度学习的使用与训练

一、整体解决方案架构

核心思路:WinForm(C#)作为前端交互层 ,负责界面展示、用户操作、图像显示;Python 脚本作为后端算法层,负责 YOLO 模型的 ONNX 推理、模型训练;C# 与 Python 通过「进程调用 + JSON 数据交互」实现通信(避免 Pythonnet 的环境冲突问题,稳定性更高)。

模块 技术栈 核心职责
前端交互层 C# + WinForm + OpenCVSharp 界面、图像显示、调用 Python
后端算法层 Python 3.9+ + Ultralytics + ONNX Runtime YOLO 推理、训练、导出 ONNX
通信层 进程调用(Process)+ JSON 传递参数 / 结果、日志输出

二、环境准备

1. Python 环境配置

安装依赖包(建议用虚拟环境):

python 复制代码
pip install ultralytics==8.0.200  # YOLOv8核心库(支持训练/推理/导出ONNX)
pip install onnxruntime==1.15.1   # ONNX推理引擎
pip install opencv-python==4.8.1.78 numpy==1.26.0 json5 pillow==10.0.1
2. C# WinForm 环境配置(VS 2022)
  • 创建项目:选择「Windows Forms App (.NET 6/7/8)」(或.NET Framework 4.8)。

  • 安装 NuGet 包:

    cs 复制代码
    Install-Package OpenCVSharp4                  # OpenCV C#封装
    Install-Package OpenCVSharp4.runtime.win     # Windows运行时
    Install-Package Newtonsoft.Json              # JSON解析
    Install-Package System.Diagnostics.Process   # 进程调用(默认已包含)

三、Python 脚本实现

1. YOLO ONNX 推理脚本(yolo_onnx_infer.py)

接收 C# 传递的「模型路径、图像路径」,执行推理后返回 JSON 格式的检测结果(类别、坐标、置信度):

python 复制代码
import cv2
import onnxruntime as ort
import numpy as np
import json
import sys

# YOLOv8 ONNX后处理(适配YOLOv8输出格式)
def postprocess(outputs, img_shape, conf_thres=0.5, iou_thres=0.45):
    predictions = np.squeeze(outputs[0]).T
    scores = np.max(predictions[:, 4:], axis=1)
    predictions = predictions[scores > conf_thres, :]
    scores = scores[scores > conf_thres]
    class_ids = np.argmax(predictions[:, 4:], axis=1)
    
    # 转换坐标:xywh -> xyxy
    boxes = predictions[:, :4]
    h, w = img_shape
    boxes[:, 0] = (boxes[:, 0] - boxes[:, 2] / 2) * w  # x1
    boxes[:, 1] = (boxes[:, 1] - boxes[:, 3] / 2) * h  # y1
    boxes[:, 2] = (boxes[:, 0] + boxes[:, 2]) * w      # x2
    boxes[:, 3] = (boxes[:, 1] + boxes[:, 3]) * h      # y2
    
    # 非极大值抑制(NMS)
    indices = cv2.dnn.NMSBoxes(boxes[:, :4].tolist(), scores.tolist(), conf_thres, iou_thres)
    if len(indices) == 0:
        return []
    
    # 整理结果
    results = []
    for i in indices.flatten():
        results.append({
            "class_id": int(class_ids[i]),
            "confidence": float(scores[i]),
            "x1": float(boxes[i][0]),
            "y1": float(boxes[i][1]),
            "x2": float(boxes[i][2]),
            "y2": float(boxes[i][3])
        })
    return results

# 主函数(接收C#传参:模型路径、图像路径)
if __name__ == "__main__":
    try:
        # 从命令行获取参数
        onnx_model_path = sys.argv[1]
        img_path = sys.argv[2]
        conf_thres = float(sys.argv[3]) if len(sys.argv) > 3 else 0.5
        
        # 1. 读取图像并预处理(YOLOv8要求640x640,BGR转RGB,归一化)
        img = cv2.imread(img_path)
        img_shape = img.shape[:2]  # (h, w)
        img_input = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img_input = cv2.resize(img_input, (640, 640))
        img_input = img_input / 255.0
        img_input = np.transpose(img_input, (2, 0, 1))  # (3, 640, 640)
        img_input = np.expand_dims(img_input, axis=0).astype(np.float32)
        
        # 2. ONNX Runtime推理
        session = ort.InferenceSession(onnx_model_path, providers=['CPUExecutionProvider'])  # 若有GPU可加CUDAProvider
        input_name = session.get_inputs()[0].name
        outputs = session.run(None, {input_name: img_input})
        
        # 3. 后处理获取检测结果
        det_results = postprocess(outputs, img_shape, conf_thres)
        
        # 4. 输出JSON结果(供C#解析)
        print(json.dumps(det_results, ensure_ascii=False))
        
    except Exception as e:
        # 异常时输出JSON格式错误信息
        print(json.dumps({"error": str(e)}, ensure_ascii=False))
2. YOLO 训练脚本(yolo_train.py)

接收 C# 传递的训练参数(数据集路径、epochs、批次等),执行训练并实时输出日志,训练完成后导出 ONNX 模型:

python 复制代码
from ultralytics import YOLO
import sys
import json

# 主函数(接收C#传参:配置参数JSON字符串)
if __name__ == "__main__":
    try:
        # 解析C#传递的训练配置
        train_config = json.loads(sys.argv[1])
        data_yaml = train_config["data_yaml"]  # 数据集yaml路径(YOLO格式)
        epochs = int(train_config["epochs"])
        batch_size = int(train_config["batch_size"])
        pretrained_model = train_config["pretrained_model"]  # 预训练模型(如yolov8n.pt)
        save_onnx = train_config["save_onnx"]  # 是否导出ONNX
        output_dir = train_config["output_dir"]
        
        # 1. 加载预训练模型
        model = YOLO(pretrained_model)
        
        # 2. 开始训练(实时打印日志供C#捕获)
        results = model.train(
            data=data_yaml,
            epochs=epochs,
            batch=batch_size,
            project=output_dir,
            name="train_exp",
            exist_ok=True,
            verbose=True
        )
        
        # 3. 训练完成后导出ONNX
        if save_onnx:
            model.export(format="onnx", imgsz=640)  # 导出ONNX到训练输出目录
        
        # 4. 输出训练完成信息
        print(json.dumps({"status": "success", "output_dir": f"{output_dir}/train_exp"}, ensure_ascii=False))
        
    except Exception as e:
        print(json.dumps({"error": str(e)}, ensure_ascii=False))

四、C# WinForm 实现

1. 界面设计(核心控件)
控件类型 名称 用途
PictureBox pbImage 显示原图 / 检测结果图
TextBox txtModelPath 输入 ONNX 模型路径
TextBox txtImagePath 输入图像路径
Button btnSelectImage 选择图像
Button btnInfer 执行推理
TextBox txtDetResult 显示检测结果(JSON)
TextBox txtTrainConfig 训练配置(JSON 格式)
Button btnTrain 开始训练
TextBox txtTrainLog 显示训练日志(多行)
2. 核心 C# 代码
cs 复制代码
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Newtonsoft.Json;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace YOLOWinForm
{
    public partial class MainForm : Form
    {
        // Python路径(根据实际安装路径修改)
        private readonly string _pythonPath = @"C:\Python39\python.exe";
        // Python脚本路径
        private readonly string _inferScriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "yolo_onnx_infer.py");
        private readonly string _trainScriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "yolo_train.py");

        public MainForm()
        {
            InitializeComponent();
            // 初始化控件(如设置多行日志框)
            txtTrainLog.Multiline = true;
            txtTrainLog.ScrollBars = ScrollBars.Vertical;
            txtDetResult.Multiline = true;
            txtDetResult.ScrollBars = ScrollBars.Vertical;
        }

        #region 1. 推理功能
        // 选择图像按钮点击事件
        private void btnSelectImage_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                ofd.Filter = "图像文件|*.jpg;*.png;*.jpeg";
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    txtImagePath.Text = ofd.FileName;
                    // 显示原图
                    using (Mat img = Cv2.ImRead(ofd.FileName))
                    {
                        pbImage.Image = BitmapConverter.ToBitmap(img);
                    }
                }
            }
        }

        // 推理按钮点击事件
        private void btnInfer_Click(object sender, EventArgs e)
        {
            try
            {
                // 校验参数
                if (!File.Exists(txtModelPath.Text) || !File.Exists(txtImagePath.Text))
                {
                    MessageBox.Show("模型或图像路径不存在!");
                    return;
                }

                // 构造Python推理命令参数
                string[] args = new[]
                {
                    _inferScriptPath,
                    txtModelPath.Text,       // 参数1:ONNX模型路径
                    txtImagePath.Text,       // 参数2:图像路径
                    "0.5"                    // 参数3:置信度阈值
                };

                // 调用Python脚本并获取结果
                string inferResult = RunPythonScript(_pythonPath, args, out string errorMsg);
                if (!string.IsNullOrEmpty(errorMsg))
                {
                    txtDetResult.Text = $"推理错误:{errorMsg}";
                    return;
                }

                // 解析JSON结果
                var detResults = JsonConvert.DeserializeObject<DetResult[]>(inferResult);
                txtDetResult.Text = JsonConvert.SerializeObject(detResults, Formatting.Indented);

                // 绘制检测框并显示
                DrawDetectionBoxes(txtImagePath.Text, detResults);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"推理失败:{ex.Message}");
            }
        }

        // 绘制检测框
        private void DrawDetectionBoxes(string imgPath, DetResult[] detResults)
        {
            using (Mat img = Cv2.ImRead(imgPath))
            {
                foreach (var result in detResults)
                {
                    // 绘制矩形框(红色,线宽2)
                    Cv2.Rectangle(img, 
                        new OpenCvSharp.Point((int)result.x1, (int)result.y1), 
                        new OpenCvSharp.Point((int)result.x2, (int)result.y2), 
                        new Scalar(0, 0, 255), 2);
                    // 绘制类别+置信度文本
                    string label = $"Class {result.class_id}: {result.confidence:F2}";
                    Cv2.PutText(img, label, 
                        new OpenCvSharp.Point((int)result.x1, (int)result.y1 - 10), 
                        HersheyFonts.HersheySimplex, 0.5, new Scalar(0, 0, 255), 1);
                }
                // 显示绘制后的图像
                pbImage.Image = BitmapConverter.ToBitmap(img);
            }
        }
        #endregion

        #region 2. 训练功能
        // 训练按钮点击事件
        private void btnTrain_Click(object sender, EventArgs e)
        {
            try
            {
                // 校验训练配置(示例:JSON格式 {"data_yaml":"dataset/data.yaml","epochs":10,"batch_size":8,...})
                if (string.IsNullOrEmpty(txtTrainConfig.Text))
                {
                    MessageBox.Show("请输入训练配置JSON!");
                    return;
                }

                // 构造训练参数
                string[] args = new[]
                {
                    _trainScriptPath,
                    txtTrainConfig.Text  // 参数1:训练配置JSON字符串
                };

                // 异步执行训练(避免界面卡死)
                Task.Run(() =>
                {
                    RunPythonScriptWithLog(_pythonPath, args, (log) =>
                    {
                        // 实时更新训练日志(跨线程更新UI)
                        Invoke(new Action(() =>
                        {
                            txtTrainLog.AppendText($"{DateTime.Now:HH:mm:ss} | {log}\r\n");
                            txtTrainLog.ScrollToCaret();
                        }));
                    });
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show($"训练启动失败:{ex.Message}");
            }
        }
        #endregion

        #region 通用:调用Python脚本(无实时日志)
        private string RunPythonScript(string pythonExe, string[] args, out string errorMsg)
        {
            errorMsg = string.Empty;
            using (Process process = new Process())
            {
                process.StartInfo.FileName = pythonExe;
                process.StartInfo.Arguments = string.Join(" ", args);
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.CreateNoWindow = true;  // 不显示Python控制台窗口

                // 执行脚本
                process.Start();
                string output = process.StandardOutput.ReadToEnd();
                errorMsg = process.StandardError.ReadToEnd();
                process.WaitForExit();

                return output;
            }
        }
        #endregion

        #region 通用:调用Python脚本(带实时日志输出)
        private void RunPythonScriptWithLog(string pythonExe, string[] args, Action<string> logCallback)
        {
            using (Process process = new Process())
            {
                process.StartInfo.FileName = pythonExe;
                process.StartInfo.Arguments = string.Join(" ", args);
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.CreateNoWindow = true;

                // 实时捕获输出(日志)
                process.OutputDataReceived += (s, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                        logCallback.Invoke(e.Data);
                };
                process.ErrorDataReceived += (s, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                        logCallback.Invoke($"[ERROR] {e.Data}");
                };

                // 启动并开始监听输出
                process.Start();
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
            }
        }
        #endregion
    }

    // 检测结果实体类(与Python脚本输出JSON对应)
    public class DetResult
    {
        public int class_id { get; set; }
        public float confidence { get; set; }
        public float x1 { get; set; }
        public float y1 { get; set; }
        public float x2 { get; set; }
        public float y2 { get; set; }
    }
}

五、关键说明与调试技巧

1. 数据集格式(YOLO 训练要求)

训练脚本依赖 YOLO 格式的数据集,需准备data.yaml配置文件:

python 复制代码
# data.yaml示例
train: ./train/images  # 训练集图像路径
val: ./val/images      # 验证集图像路径
nc: 2                 # 类别数
names: ['cat', 'dog'] # 类别名称

标注文件需为.txt(与图像同名),每行格式:class_id x_center y_center width height(归一化坐标)。

2. 路径问题(核心避坑点)
  • Python 脚本中所有路径建议用绝对路径(C# 传递参数时转换为绝对路径)。
  • C# 中AppDomain.CurrentDomain.BaseDirectory可获取程序运行目录,将 Python 脚本放在该目录下避免路径错误。
3. 性能优化
  • 推理时若有 GPU,修改 Python 脚本中 ONNX Runtime 的providers['CUDAExecutionProvider'](需安装 CUDA 版 onnxruntime)。
  • 训练时建议用 GPU(需安装 CUDA、cuDNN,Ultralytics 会自动识别)。
4. 调试技巧
  • 先单独运行 Python 脚本测试功能(如python yolo_onnx_infer.py model.onnx test.jpg 0.5),确保脚本无错误。
  • C# 中捕获 Python 的StandardError输出,定位参数传递、环境依赖等问题。

六、扩展功能

  1. 视频推理:修改 Python 脚本支持视频帧处理,C# 通过 OpenCVSharp 逐帧读取视频并调用推理。
  2. 模型导出:在 C# 中增加「导出 ONNX」按钮,调用 Python 脚本执行model.export(format="onnx")
  3. 批量推理:遍历文件夹内所有图像,批量执行推理并保存结果。
相关推荐
梦茹^_^1 分钟前
Flsk框架(自学)2
后端·python·flask·web框架
NullPointer826 分钟前
【剪映小助手源码精讲】第39章:CI流程
python·aigc
七夜zippoe28 分钟前
Python并发与并行编程深度剖析:从GIL原理到高并发实战
服务器·网络·python·并发·gil
weixin_4573402133 分钟前
lora监督微调(SFT)
开发语言·python
weixin_3903084639 分钟前
Jenkins报Host key verification failed错误
python·jenkins
码农三叔1 小时前
(4-2-05)Python SDK仓库:MCP服务器端(5)Streamable HTTP传输+Streamable HTTP传输
开发语言·python·http·大模型·1024程序员节·mcp·mcp sdk
IT枫斗者1 小时前
Spring Boot 4.0 正式发布:新一代起点到底“新”在哪?(Spring Framework 7 / Java 25 / JSpecify / API 版本管理 / HTTP Service
java·开发语言·spring boot·后端·python·spring·http
AI大佬的小弟1 小时前
Python基础(10):Python函数基础详解
开发语言·python·函数·ai大模型基础·嵌套函数·变量的作用域·全局变量和局部变量
SZ1701102311 小时前
N-S图 (盒图) N-S图是“盒子嵌套”,PAD图是“树干分叉”*
python
智航GIS1 小时前
8.5 os 模块
python