[C#][winform]基于yolov8的水表读数检测与识别系统C#源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】

智慧水务中的水表读数检测与识别系统,基于先进的YOLOv8算法,为城市供水管理的智能化升级提供了坚实的技术支撑。该系统通过集成YOLOv8的深度学习框架,实现了对水表数字及关键标识的实时、精准识别,覆盖["0","1","2","3","4","5","6","7","8","9","counter","liter"]等核心类别。

YOLOv8以其卓越的检测速度与高精度特性,能够快速解析水表图像中的数字信息,并精准定位计数器区域("counter")与单位标识("liter")。通过YOLOv8算法自动提取特征信息,完成数字识别与状态判断。此外,该系统支持多场景适配与动态追踪功能,可同时监测不同型号水表的读数变化,并兼容低光照、反光等复杂环境,如果增强数据集进行训练可以进一步提升了水务管理的精细化水平。

综上所述,基于YOLOv8的水表读数检测与识别系统,为智慧水务建设注入了创新动能,有效保障了供水计量的准确性,推动了城市水资源管理的数字化转型。

【效果展示】

注意:系统采用yolov8x大模型进行训练,所以单张图片预测1.1秒左右,如果采用yolov8n训练可以达到50ms左右。由于水表方向不确定为了能够正常读取完整水表数字需要正向放置,代码采用位置从左到右提取完整数字,最终显示在软件左上角区域。水表只读取整数部分,小数部分只检测没有识别,因为这个主要是依靠目标检测进行定位识别。

【训练数据集介绍】

注意数据集中有很多增强图片,主要是旋转增强图片

数据集格式:Pascal VOC格式+YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件)

图片数量(jpg文件个数):3552

标注数量(xml文件个数):3552

标注数量(txt文件个数):3552

标注类别数:12

所在github仓库:firc-dataset

标注类别名称(注意yolo格式类别顺序不和这个对应,而以labels文件夹classes.txt为准):["0","1","2","3","4","5","6","7","8","9","counter","liter"]

每个类别标注的框数:

0 框数 = 3865

1 框数 = 1768

2 框数 = 1390

3 框数 = 1295

4 框数 = 1198

5 框数 = 1074

6 框数 = 1019

7 框数 = 827

8 框数 = 908

9 框数 = 864

counter 框数 = 3552

liter 框数 = 3551

总框数:21311

图片分辨率:640x640

使用标注工具:labelImg

标注规则:对类别进行画矩形框

重要说明:暂无

特别声明:本数据集不对训练的模型或者权重文件精度作任何保证

图片预览:

标注例子:

【测试环境】

windows10 x64系统

VS2019

netframework4.7.2

opencvsharp4.9.0

onnxruntime1.22.0

【训练信息】

|-----------------|-------|
| 参数 | 值 |
| 训练集图片数 | 3072 |
| 验证集图片数 | 480 |
| 训练map | 98.6% |
| 训练精度(Precision) | 96.5% |
| 训练召回率(Recall) | 96.6% |

【界面设计代码】

复制代码
using DeploySharp.Data;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FIRC
{
    public partial class Form1 : Form
    {

        public bool videoStart = false;//视频停止标志
        string weightsPath = Application.StartupPath + "\\weights";//模型目录
        YoloDetector detetor = new YoloDetector();//推理引擎
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//线程更新控件不报错
        }
        private void LoadWeightsFromDir()
        {
            var di = new DirectoryInfo(weightsPath);
            foreach(var fi in di.GetFiles("*.onnx"))
            {
                comboBox1.Items.Add(fi.Name);
            }
            if(comboBox1.Items.Count>0)
            {
                comboBox1.SelectedIndex = 0;
            }
            else
            {
                tssl_show.Text = "未找到模型,请关闭程序,放入模型到weights文件夹!";
                tsb_pic.Enabled = false;
                tsb_video.Enabled = false;
                tsb_camera.Enabled = false;
            }
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            LoadWeightsFromDir();//从目录加载模型
                               
        }
        public string GetResultString(DetResult[] result)
        {
            Dictionary<string, int> resultDict = new Dictionary<string, int>();
            for (int i = 0; i < result.Length; i++)
            {
                if(resultDict.ContainsKey( result[i].Category) )
                {
                    resultDict[result[i].Category]++;
                }
                else
                {
                    resultDict[result[i].Category] =1;
                }
            }

            var resultStr = "";
            foreach(var item in resultDict)
            {
                resultStr += string.Format("{0}:{1}\r\n",item.Key,item.Value);
            }
            return resultStr;
        }
        private void tsb_pic_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";
            if (ofd.ShowDialog() != DialogResult.OK) return;
            tssl_show.Text = "正在检测中...";
            Task.Run(() => {
                var sw = new Stopwatch();
                sw.Start();
                Mat image = Cv2.ImRead(ofd.FileName);
                detetor.SetParams(Convert.ToSingle(numericUpDown1.Value), Convert.ToSingle(numericUpDown2.Value));
                var results=detetor.Inference(image);
                
                var resultImage = detetor.DrawImage(image, results);
    
                sw.Stop();
                pb_show.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(resultImage);
                tb_res.Text = GetResultString(results);
                tssl_show.Text = "检测已完成!总计耗时"+sw.Elapsed.TotalSeconds+"秒";
            });
           


        }

        public void VideoProcess(string videoPath)
        {
            Task.Run(() => {

                detetor.SetParams(Convert.ToSingle(numericUpDown1.Value), Convert.ToSingle(numericUpDown2.Value));
                VideoCapture capture = new VideoCapture(videoPath);
                if (!capture.IsOpened())
                {
                    tssl_show.Text="视频打开失败!";
                    return;
                }
                Mat frame = new Mat();
                var sw = new Stopwatch();
                int fps = 0;
                while (videoStart)
                {

                    capture.Read(frame);
                    if (frame.Empty())
                    {
                        Console.WriteLine("data is empty!");
                        break;
                    }
                    sw.Start();
                    var results = detetor.Inference(frame);
                    var resultImg = detetor.DrawImage(frame,results);
                    sw.Stop();
                    fps = Convert.ToInt32(1 / sw.Elapsed.TotalSeconds);
                    sw.Reset();
                    Cv2.PutText(resultImg, "FPS=" + fps, new OpenCvSharp.Point(30, 30), HersheyFonts.HersheyComplex, 1.0, new Scalar(255, 0, 0), 3);
                    //显示结果
                    pb_show.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(resultImg);
                    tb_res.Text = GetResultString(results);
                    Thread.Sleep(5);


                }

                capture.Release();

                pb_show.Image = null;
                tssl_show.Text = "视频已停止!";
                tsb_video.Text = "选择视频";

            });
        }
        public void CameraProcess(int cameraIndex=0)
        {
            Task.Run(() => {

                detetor.SetParams(Convert.ToSingle(numericUpDown1.Value), Convert.ToSingle(numericUpDown2.Value));
                VideoCapture capture = new VideoCapture(cameraIndex);
                if (!capture.IsOpened())
                {
                    tssl_show.Text = "摄像头打开失败!";
                    return;
                }
                Mat frame = new Mat();
                var sw = new Stopwatch();
                int fps = 0;
                while (videoStart)
                {

                    capture.Read(frame);
                    if (frame.Empty())
                    {
                        Console.WriteLine("data is empty!");
                        break;
                    }
                    sw.Start();
                    var results = detetor.Inference(frame);
                    var resultImg = detetor.DrawImage(frame, results);
                    sw.Stop();
                    fps = Convert.ToInt32(1 / sw.Elapsed.TotalSeconds);
                    sw.Reset();
                    Cv2.PutText(resultImg, "FPS=" + fps, new OpenCvSharp.Point(30, 30), HersheyFonts.HersheyComplex, 1.0, new Scalar(255, 0, 0), 3);
                    //显示结果
                    pb_show.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(resultImg);
                    tb_res.Text = GetResultString(results);
                    Thread.Sleep(5);


                }

                capture.Release();

                pb_show.Image = null;
                tssl_show.Text = "摄像头已停止!";
                tsb_camera.Text = "打开摄像头";

            });
        }
        private void tsb_video_Click(object sender, EventArgs e)
        {
            if(tsb_video.Text=="选择视频")
            {
                OpenFileDialog ofd = new OpenFileDialog();
                ofd.Filter = "视频文件(*.*)|*.mp4;*.avi";
                if (ofd.ShowDialog() != DialogResult.OK) return;
                videoStart = true;
                VideoProcess(ofd.FileName);
                tsb_video.Text = "停止";
                tssl_show.Text = "视频正在检测中...";

            }
            else
            {
                videoStart = false;
               
            }
        }

        private void tsb_camera_Click(object sender, EventArgs e)
        {
            if (tsb_camera.Text == "打开摄像头")
            {
                videoStart = true;
                CameraProcess(0);
                tsb_camera.Text = "停止";
                tssl_show.Text = "摄像头正在检测中...";

            }
            else
            {
                videoStart = false;

            }
        }

        private void tsb_exit_Click(object sender, EventArgs e)
        {
            videoStart = false;
            this.Close();
        }

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            numericUpDown1.Value = Convert.ToDecimal(trackBar1.Value / 100.0f);
        }

        private void trackBar2_Scroll(object sender, EventArgs e)
        {
            numericUpDown2.Value = Convert.ToDecimal(trackBar2.Value / 100.0f);
        }

        private void numericUpDown1_ValueChanged(object sender, EventArgs e)
        {
            trackBar1.Value = (int)(Convert.ToSingle(numericUpDown1.Value) * 100);
        }

        private void numericUpDown2_ValueChanged(object sender, EventArgs e)
        {
            trackBar2.Value = (int)(Convert.ToSingle(numericUpDown2.Value) * 100);
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            tssl_show.Text="加载模型:"+comboBox1.Text;
            detetor.LoadWeights(weightsPath+"\\"+comboBox1.Text);
            tssl_show.Text = "模型加载已完成!";
        }
    }
}

【使用步骤】

使用步骤:

(1)首先根据官方框架ultralytics安装教程安装好yolov8环境,并根据官方export命令将自己pt模型转成onnx模型,然后去github.com/futureflsl/firc-csharp-projects找到源码

(2)使用vs2019打开sln项目,选择x64 release并且修改一些必要的参数,比如输入shape等,点击运行即可查看最后效果

特别注意如果运行报错了,请参考我的博文进行重新引用我源码的DLL:[C#]opencvsharp报错System.Memory,Version=4.0.1.2,Culture=neutral,PublicKeyToken=cc7b13fcd2ddd51"版本高于所引_未能加载文件或程序集"system.memory, version=4.0.1.2, culture-CSDN博客

【提供文件】

C#源码

yolov8x.onnx模型(不提供pytorch模型)

训练的map,P,R曲线图(在weights\results.png)

测试图片(在test_img文件夹下面)

训练数据集

相关推荐
cnxy1884 小时前
围棋对弈Python程序开发完整指南:步骤1 - 棋盘基础框架搭建
开发语言·python
程序员-周李斌6 小时前
Java 死锁
java·开发语言·后端
JasmineWr6 小时前
CompletableFuture相关问题
java·开发语言
零雲6 小时前
java面试:知道java的反射机制吗
java·开发语言·面试
Jeremy爱编码6 小时前
实现 Trie (前缀树)
开发语言·c#
烛阴7 小时前
C# 正则表达式(4):分支与回溯引用
前端·正则表达式·c#
laocooon5238578867 小时前
插入法排序 python
开发语言·python·算法
你的冰西瓜7 小时前
C++中的list容器详解
开发语言·c++·stl·list
就不掉头发7 小时前
I/O复用
运维·服务器·c语言·开发语言