[C#][winform]基于yolov11的淡水鱼种类检测识别系统C#源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】

基于YOLOv11的淡水鱼种类检测系统是一种利用深度学习技术,特别是YOLOv11目标检测算法,实现对淡水鱼种类进行自动识别的高效系统,其支持识别的目标种类涵盖:孟加拉鲳鱼(Bangus)、鳙鱼(Big Head Carp)、黑斑鲃(Black Spotted Barb)、鲶鱼(Catfish)、攀鲈(Climbing Perch)、四指马鲅(Fourfinger Threadfish)、淡水鳗鲡(Freshwater Eel)、玻璃鲈鱼(Glass Perchlet)、虾虎鱼(Goby)、金鱼(Gold Fish)、吻口鱼(Gourami)、草鱼(Grass Carp)、绿斑河豚(Green Spotted Puffer)、印度鲤(Indian Carp)、印度鲮(Indo-Pacific Tarpon)、美洲鲈(Jaguar Gapote)、清道夫鱼(Janitor Fish)、刀鱼(Knifefish)、长吻管鳚(Long-Snouted Pipefish)、食蚊鱼(Mosquito Fish)、塘鳢(Mudfish)、鲻鱼(Mullet)、湄公鱼(Pangasius)、鲈鱼(Perch)、油鱼(Scat Fish)、银鲃(Silver Barb)、银鲫(Silver Carp)、银鲈(Silver Perch)、黑鱼(Snakehead)、巨鲶(Tenpounder)及罗非鱼(Tilapia)。

该系统的工作原理是通过训练YOLOv11模型,使其能够准确识别淡水图像或视频中的鱼类种类。YOLOv11作为YOLO系列的最新版本,具有强大的特征提取能力和高效的推理速度,这使得它能够在复杂的水下环境中快速、准确地检测出淡水鱼,并标注出其种类和位置。

在构建该系统时,首先需要准备一个包含各种淡水鱼图像的数据集,并对这些图像进行标注,包括鱼类的位置和类别信息。然后,使用这些数据对YOLOv11模型进行训练,使其学习到淡水鱼的特征。训练完成后,系统就可以对输入的淡水鱼图像或视频进行实时检测,并输出检测结果。

该系统可以广泛应用于淡水渔业资源管理、生态保护、水产养殖等领域。例如,在水产养殖中,通过该系统可以实时监测鱼塘中的鱼类种类和数量,为养殖者提供决策依据。在生态保护方面,该系统可以帮助科研人员更好地了解淡水鱼类的分布和数量,为生态保护提供数据支持。

总之,基于YOLOv11的淡水鱼种类检测系统是一种高效、准确的目标检测系统,具有广泛的应用前景。

【效果展示】

【测试环境】

windows10 x64系统

VS2019

netframework4.7.2

opencvsharp4.9.0

onnxruntime1.22.0

注意使用CPU推理,没有使用cuda推理因此不需要电脑具有nvidia显卡,无需安装安装cuda+dunn

【训练数据集介绍】

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

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

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

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

标注类别数:31

标注类别名称(注意yolo格式类别顺序不和这个对应,而以labels文件夹classes.txt为准):["Bangus","Big Head Carp","Black Spotted Barb","Catfish","Climbing Perch","Fourfinger Threadfish","Freshwater Eel","Glass Perchlet","Goby","Gold Fish","Gourami","Grass Carp","Green Spotted Puffer","Indian Carp","Indo-Pacific Tarpon","Jaguar Gapote","Janitor Fish","Knifefish","Long-Snouted Pipefish","Mosquito Fish","Mudfish","Mullet","Pangasius","Perch","Scat Fish","Silver Barb","Silver Carp","Silver Perch","Snakehead","Tenpounder","Tilapia"]

每个类别标注的框数:

Bangus 框数 = 81

Big Head Carp 框数 = 77

Black Spotted Barb 框数 = 76

Catfish 框数 = 84

Climbing Perch 框数 = 91

Fourfinger Threadfish 框数 = 55

Freshwater Eel 框数 = 77

Glass Perchlet 框数 = 89

Goby 框数 = 118

Gold Fish 框数 = 81

Gourami 框数 = 87

Grass Carp 框数 = 170

Green Spotted Puffer 框数 = 60

Indian Carp 框数 = 75

Indo-Pacific Tarpon 框数 = 86

Jaguar Gapote 框数 = 75

Janitor Fish 框数 = 88

Knifefish 框数 = 83

Long-Snouted Pipefish 框数 = 84

Mosquito Fish 框数 = 91

Mudfish 框数 = 55

Mullet 框数 = 86

Pangasius 框数 = 87

Perch 框数 = 81

Scat Fish 框数 = 67

Silver Barb 框数 = 98

Silver Carp 框数 = 74

Silver Perch 框数 = 80

Snakehead 框数 = 81

Tenpounder 框数 = 104

Tilapia 框数 = 1763

总框数:4304

使用标注工具:labelImg

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

重要说明:暂无

特别声明:本数据集不对训练的模型或者权重文件精度作任何保证,数据集只提供准确且合理标注

图片预览:

标注例子:

【训练信息】

|-----------------|-------|
| 参数 | 值 |
| 训练集图片数 | 2003 |
| 验证集图片数 | 760 |
| 训练map | 75.6% |
| 训练精度(Precision) | 75.6% |
| 训练召回率(Recall) | 69.8% |

验证集评估精度信息:

|-----------------------|--------|-----------|-------|-------|-------|----------|
| Class | Images | Instances | P | R | mAP50 | mAP50-95 |
| all | 760 | 966 | 0.756 | 0.698 | 0.756 | 0.628 |
| Bangus | 8 | 12 | 0.585 | 0.417 | 0.462 | 0.418 |
| Big Head Carp | 15 | 15 | 0.725 | 0.467 | 0.561 | 0.511 |
| Black Spotted Barb | 12 | 13 | 0.606 | 0.593 | 0.584 | 0.513 |
| Catfish | 11 | 11 | 0.546 | 0.636 | 0.531 | 0.345 |
| Climbing Perch | 12 | 12 | 0.713 | 0.917 | 0.882 | 0.713 |
| Fourfinger Threadfish | 9 | 9 | 0.332 | 0.889 | 0.685 | 0.546 |
| Freshwater Eel | 14 | 14 | 0.786 | 0.857 | 0.87 | 0.726 |
| Glass Perchlet | 13 | 13 | 0.61 | 0.723 | 0.663 | 0.565 |
| Goby | 51 | 54 | 0.888 | 0.439 | 0.79 | 0.645 |
| Gold Fish | 6 | 6 | 0.904 | 1 | 0.995 | 0.94 |
| Gourami | 9 | 9 | 0.962 | 1 | 0.995 | 0.83 |
| Grass Carp | 22 | 22 | 0.618 | 0.864 | 0.746 | 0.655 |
| Green Spotted Puffer | 15 | 15 | 0.955 | 0.933 | 0.988 | 0.829 |
| Indian Carp | 11 | 11 | 0.393 | 0.545 | 0.389 | 0.311 |
| Indo-Pacific Tarpon | 8 | 9 | 0.124 | 0.333 | 0.156 | 0.139 |
| Jaguar Gapote | 11 | 11 | 0.771 | 0.921 | 0.968 | 0.906 |
| Janitor Fish | 19 | 21 | 0.84 | 0.619 | 0.749 | 0.543 |
| Knifefish | 10 | 10 | 0.963 | 1 | 0.995 | 0.87 |
| Long-Snouted Pipefish | 18 | 20 | 0.929 | 0.651 | 0.823 | 0.561 |
| Mosquito Fish | 13 | 14 | 1 | 0.627 | 0.786 | 0.595 |
| Mudfish | 13 | 13 | 0.361 | 0.462 | 0.499 | 0.413 |
| Mullet | 13 | 16 | 0.691 | 0.625 | 0.644 | 0.589 |
| Pangasius | 12 | 12 | 0.88 | 0.917 | 0.951 | 0.837 |
| Perch | 29 | 29 | 1 | 0.87 | 0.98 | 0.917 |
| Scat Fish | 46 | 46 | 0.987 | 0.848 | 0.981 | 0.811 |
| Silver Barb | 52 | 54 | 0.87 | 0.371 | 0.693 | 0.585 |
| Silver Carp | 16 | 16 | 0.778 | 0.938 | 0.786 | 0.71 |
| Silver Perch | 39 | 40 | 0.801 | 0.65 | 0.808 | 0.652 |
| Snakehead | 46 | 49 | 0.916 | 0.447 | 0.788 | 0.507 |
| Tenpounder | 75 | 80 | 0.98 | 0.275 | 0.793 | 0.668 |
| Tilapia | 130 | 310 | 0.921 | 0.79 | 0.906 | 0.602 |

【界面代码】

复制代码
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. Class
    • 这通常指的是模型被设计用来检测的目标类别。例如,一个模型可能被训练来检测车辆、行人或动物等不同类别的对象。
  2. Images
    • 表示验证集中的图片数量。验证集是用来评估模型性能的数据集,与训练集分开,以确保评估结果的公正性。
  3. Instances
    • 在所有图片中目标对象的总数。这包括了所有类别对象的总和,例如,如果验证集包含100张图片,每张图片平均有5个目标对象,则Instances为500。
  4. P(精确度Precision)
    • 精确度是模型预测为正样本的实例中,真正为正样本的比例。计算公式为:Precision = TP / (TP + FP),其中TP表示真正例(True Positives),FP表示假正例(False Positives)。
  5. R(召回率Recall)
    • 召回率是所有真正的正样本中被模型正确预测为正样本的比例。计算公式为:Recall = TP / (TP + FN),其中FN表示假负例(False Negatives)。
  6. mAP50
    • 表示在IoU(交并比)阈值为0.5时的平均精度(mean Average Precision)。IoU是衡量预测框和真实框重叠程度的指标。mAP是一个综合指标,考虑了精确度和召回率,用于评估模型在不同召回率水平上的性能。在IoU=0.5时,如果预测框与真实框的重叠程度达到或超过50%,则认为该预测是正确的。
  7. mAP50-95
    • 表示在IoU从0.5到0.95(间隔0.05)的范围内,模型的平均精度。这是一个更严格的评估标准,要求预测框与真实框的重叠程度更高。在目标检测任务中,更高的IoU阈值意味着模型需要更准确地定位目标对象。mAP50-95的计算考虑了从宽松到严格的多个IoU阈值,因此能够更全面地评估模型的性能。

这些指标共同构成了评估目标检测模型性能的重要框架。通过比较不同模型在这些指标上的表现,可以判断哪个模型在实际应用中可能更有效。

【使用步骤】

使用步骤:

(1)首先根据官方框架ultralytics安装教程安装好yolov11环境,并根据官方export命令将自己pt模型转成onnx模型,然后去github仓库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#源码

yolo11n.onnx模型(提供pytorch模型)

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

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

特别注意这里不提供训练数据集

相关推荐
NAGNIP6 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab7 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab7 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP11 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年11 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼11 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS11 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区12 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈12 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang13 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx