news 2026/4/17 14:46:39

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[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
训练map98.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文件夹下面)

训练数据集

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 10:06:42

20、深入理解 TCP/IP 基础知识

深入理解 TCP/IP 基础知识 1. TCP/IP 相关协议 TCP/IP 协议族包含了多个重要的协议,它们各自承担着不同的功能: - ARP(地址解析协议) :将 IP 地址转换为 MAC 地址。 - RARP(反向地址解析协议) :将 MAC 地址转换为 IP 地址。 - Telnet :一种远程访问协议,允…

作者头像 李华
网站建设 2026/4/18 8:47:50

vivado2023.2下载安装教程:初学者避坑指南

Vivado 2023.2 安装避坑全攻略&#xff1a;从零开始搭建 FPGA 开发环境 你是不是也遇到过这样的情况&#xff1f; 刚决定入门 FPGA&#xff0c;兴致勃勃地打开浏览器搜索“vivado安装教程”&#xff0c;结果下载到一半断了、安装完打不开 GUI、许可证反复失效……折腾一整天&…

作者头像 李华
网站建设 2026/4/18 7:55:50

如何快速转换B站缓存视频:m4s-converter终极使用指南

如何快速转换B站缓存视频&#xff1a;m4s-converter终极使用指南 【免费下载链接】m4s-converter 将bilibili缓存的m4s转成mp4(读PC端缓存目录) 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经遇到过这样的情况&#xff1a;在B站收藏了精彩的视…

作者头像 李华
网站建设 2026/4/18 7:58:06

重庆大学毕业论文排版革命:CQUThesis模板全方位使用指南

重庆大学毕业论文排版革命&#xff1a;CQUThesis模板全方位使用指南 【免费下载链接】CQUThesis :pencil: 重庆大学毕业论文LaTeX模板---LaTeX Thesis Template for Chongqing University 项目地址: https://gitcode.com/gh_mirrors/cq/CQUThesis 还在为毕业论文格式调整…

作者头像 李华
网站建设 2026/4/18 7:55:23

ESP32教程:用Arduino IDE构建Web服务器通俗解释

用ESP32搭一个网页开关灯&#xff0c;原来这么简单&#xff1f;——手把手教你写嵌入式Web服务器你有没有想过&#xff0c;一块几十块钱的ESP32开发板&#xff0c;不接屏幕、不连电脑&#xff0c;也能让你用手机浏览器远程控制LED灯&#xff1f;而且整个过程不需要复杂的网络知…

作者头像 李华
网站建设 2026/4/18 8:04:37

BetterNCM安装器:解锁网易云音乐插件生态的终极方案

BetterNCM安装器&#xff1a;解锁网易云音乐插件生态的终极方案 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 还在为网易云音乐功能单一而烦恼&#xff1f;想要自定义界面却无从下手…

作者头像 李华