ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLO捕鱼识别数据集实战:标签格式、训练配置与避坑指南

YOLO捕鱼识别数据集实战:标签格式、训练配置与避坑指南 简介一份面向yolov5、yolov8、yolov9、yolov7、yolov10、yolo11等主流yolo算法的捕鱼识别目标检测数据集适合算法初学者、工程师及需要快速验证检测模型的开发者。压缩包内共2000个文件包含1584个xml与416个txt标签文件同时提供voc格式和yolo格式两种标注xml便于在LabelImg等工具中查看或二次标注txt直接记录类别索引及归一化的目标框坐标标签类别从0开始、坐标基于图像宽高归一化可直接被yolo训练脚本读取无需额外转换数据集已划分好训练、验证、测试集下载后即可用于模型训练与效果评估。压缩包约96.7MB目前已有75人学习下载。对渔业监控、渔获计数、钓鱼场景识别等任务来说这套数据能免去手动采集和标注的繁琐流程帮助读者把精力集中在模型调参与效果优化上也适合作为课程设计或论文实验的基础数据集。1. 捕鱼识别数据集1813张带标签图像能解决什么现实问题用YOLO做捕鱼识别最容易被卡住的一步不是网络结构而是数据集。公开数据集里几乎都是COCO那种通用类别一个笼统的fish覆盖所有鱼种区分不了渔获大小更别说钓竿、钓位、垂钓动作这些细粒度目标。这套捕鱼识别数据集一共1813张图像全部带标签YOLO的txt和VOC的xml两套标注都给好了训练集和验证集已经划分到位下载解压后从YOLOv5到YOLO11都能直接吃。适合做渔政监控、休闲渔业统计、智能钓具、水产养殖识别这类垂直场景的从业者。我把它完整拆了一遍下面把目录格式、训练参数和踩过的坑挨个写清楚。2. 数据集结构与标签格式两套标注并存意味着什么拿到任何目标检测数据集第一步都不是写训练脚本而是把目录结构和标签格式彻底摸清。这套数据我解压后看到的是典型的图像目录配双标签目录结构images文件夹存放jpg图像labels文件夹存放YOLO格式的txt标签xml文件夹存放VOC格式的标注三者按文件名一一对应。文件名规律是img_0506_487这种带编号的命名前缀img_0506和序号487组合在一起看起来像从一段连续采集的监控视频帧里抽出来的这在渔场摄像头数据里很常见。2.1 目录组织方式解压后目录大致是下面这种结构具体文件夹名字可能略有出入但核心就三个部分图像、YOLO标签、VOC标签。fishing_dataset/ ├── images/ │ ├── train/ # 约 1400 张训练图 │ └── val/ # 约 400 张验证图 ├── labels/ │ ├── train/ # 与 images/train 同名的 yolo txt │ └── val/ # 与 images/val 同名的 yolo txt └── xml/ ├── train/ # voc xml 标签 └── val/我一般会先把train和val两个目录的图像数量、标签数量统计一遍。所谓“划分好”不代表一定不会出问题之前接过一个数据集声称划分完成结果训练集里有两张图没配套标签YOLO训练时直接跳过这些图导致实际参与训练的图像数量比预期少了一截。所以这个检查动作不要省后面第3章我会把统计脚本完整写出来。2.2 YOLO txt标签的字段含义labels目录下的txt文件直接决定训练效果因为YOLO系列读取的就是这个格式。随便打开img_0506_487.txt里面每一行对应一个目标框标准格式是五个字段0 0.51234 0.39876 0.12345 0.08765 1 0.72345 0.65432 0.05678 0.04321第一个字段是类别索引从0开始编号后四个字段依次是归一化后的中心点x坐标、中心点y坐标、目标框宽度、目标框高度。所有数值都是相对图像宽高的比例范围在0到1之间。我拿到txt后习惯先写一个坐标还原脚本把归一化坐标换算回像素坐标再叠加到图像上肉眼检查。import cv2 def yolo_to_pixel(box, img_w, img_h): 将 yolo 归一化坐标转换为像素坐标 box 为 (class_id, x_center, y_center, width, height) class_id, xc, yc, w, h box x1 int((xc - w / 2) * img_w) y1 int((yc - h / 2) * img_h) x2 int((xc w / 2) * img_w) y2 int((yc h / 2) * img_h) return class_id, (x1, y1, x2, y2) img cv2.imread(images/train/img_0506_487.jpg) img_h, img_w img.shape[:2] with open(labels/train/img_0506_487.txt, r) as f: for line in f: line line.strip() if not line: continue parts line.split() box (int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4])) class_id, (x1, y1, x2, y2) yolo_to_pixel(box, img_w, img_h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(class_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check_0506_487.jpg, img)这段脚本里yolo_to_pixel函数的换算逻辑是目标框中心点x坐标乘以图像宽度得到像素中心x再加减一半框宽得到左右边界y方向同理。这里最容易翻车的点在于乘除顺序如果把归一化宽度直接当成像素宽度来画框画出来的框会比真实目标小一大圈。这种错误在从VOC转YOLO格式时尤其容易出现所以我会在开始训练前随机抽取20到30张图跑一遍这个可视化脚本确认每个框都正好扣在目标身上。2.3 VOC xml标签的差异与转换xml文件夹里的VOC格式和txt是同一套标注的不同表达差别在于VOC存的是绝对像素坐标并且带了图像尺寸和文件名信息。用编辑器打开一个xml可以看到类似下面的结构。annotation filenameimg_0506_487.jpg/filename size width1920/width height1080/height /size object namefish/name bndbox xmin452/xmin ymin278/ymin xmax689/xmax ymax412/ymax /bndbox /object /annotation从bndbox里读出的xmin、ymin、xmax、ymax是绝对像素坐标。要知道这个xml对应的YOLO标签怎么算只需要做四步换算xc等于xmin加xmax除以2再除以图像宽度yc同理w等于xmax减xmin除以宽度h等于ymax减ymin除以高度。这套数据集能同时提供两种格式对做数据增强和格式迁移的人很友好比如你要用mmdetection跑一遍直接解析xml就能生成COCO格式不用再去逆向解析txt。3. 训练前准备数据划分复核、类别统计与data.yaml配置很多新手拿到数据集就直接敲训练命令结果训练跑到一半报错或者精度惨不忍睹原因大多出在训练前没有做数据体检。这套数据虽然标注完整但我不建议跳过体检这一步尤其是像我这种被坑过的人现在拿到任何数据集都是先跑完下面三个检查再动手。3.1 数据划分复核第一个检查是确认train和val里图像与标签数量完全一致。正常情况下images和labels目录下的文件名一一对应但如果数据集在打包传输过程中丢过文件就会出现图像多标签少的情况。import os def count_images(folder): return len([f for f in os.listdir(folder) if f.lower().endswith((.jpg, .jpeg, .png))]) img_train count_images(images/train) lab_train len(os.listdir(labels/train)) img_val count_images(images/val) lab_val len(os.listdir(labels/val)) print(ftrain images{img_train}, labels{lab_train}) print(fval images{img_val}, labels{lab_val}) assert img_train lab_train, train set mismatch assert img_val lab_val, val set mismatch这段脚本用os.listdir分别统计两个目录的文件数量endswith过滤出图片扩展名标签目录则直接统计全部txt文件。assert语句会在数量不一致时抛异常这样就能在训练前发现问题而不是让YOLO默默跳过坏样本。这里有个细节如果训练图像里混入了一张没有标签的图YOLO训练过程中会跳过这张图并打印警告但默认情况下警告很容易被淹没在一堆日志里所以提前跑一次这个检查最省事。3.2 类别统计与类别失衡预判第二个检查是统计全部标签里出现了哪些类别索引以及每个类别的样本量。捕鱼场景的类别分布往往不均匀比如鱼这个类别可能有几千个框而渔获等级或钓竿类别只有几百个框这种失衡会直接影响mAP。from collections import Counter import os label_dir labels/train counter Counter() for name in os.listdir(label_dir): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path, r) as f: for line in f: line line.strip() if not line: continue counter[int(line.split()[0])] 1 print(counter) # 输出示例Counter({0: 2200, 1: 180, 2: 360})Counter是Python标准库里的计数器遇到每个class id就累加一次。输出的统计结果能直接看出几件事类别总数是否和你预期一致、每个类别的框数量差距是否悬殊。如果发现某个类别样本量只有其他类别的十分之一训练时就要考虑增加该类的权重或者做针对性增强否则模型大概率会偏向样本多的类别少样本类别在验证集上的召回率会很难看。3.3 data.yaml配置与路径写法第三个检查是写data.yaml。YOLOv5到YOLO11都通过yaml文件声明数据路径和类别名写错路径或者索引对不上是训练报错的头号原因。# fishing.yaml path: F:/datasets/fishing # 数据集根目录建议用绝对路径 train: images/train # 相对 path 的训练图像目录 val: images/val # 相对 path 的验证图像目录 nc: 3 # 类别数量必须和 txt 里的最大索引 1 相等 names: # 索引从 0 开始和 txt 第一字段一一对应 0: fish 1: fishing_rod 2: angler这里的names列表要特别小心它必须和txt里的class id顺序严格一致顺序错了模型也能训练但推理出来的类别名张冠李戴。比如txt里索引0本来表示鱼yaml里把索引0写成fishing_rod训练不会报错预测结果却完全对不上真实含义。我一般会在yaml文件旁边放一份类别说明文档并且把第3.2节的counter输出和yaml里的nc做一次人工比对确认最大索引加1等于nc。4. 用YOLOv8训练捕鱼识别模型完整命令与损失曲线判读数据体检完就可以正式训练了。目前YOLOv8是适配这套托管数据最省心的版本因为ultralytics的代码把数据集加载、增强、训练、验证一条龙包好了标签格式零转换直接能用。4.1 环境准备与快速验证先安装ultralytics库建议在专门为这个项目创建的Python虚拟环境里装避免和系统里其他深度学习框架的依赖冲突。pip install ultralytics装完之后先不要急着全量训练我习惯先跑一次极短训练确认数据链路通畅。比如只用10轮、每轮只跑少量图像如果这个过程能正常打印出loss并且不报错说明图像读取、标签解析、数据增强这些环节都没问题然后再启动正式训练。这一步能节省大量排错时间因为全量训练一旦在数据加载阶段崩了前面等的所有时间都白费。4.2 训练命令与参数设定对于1813张图像这种中小规模数据集从预训练权重开始迁移学习是性价比最高的方案。直接从头训练在这么少的数据量上很难收敛而加载在COCO上预训练过的权重模型已经具备通用目标认知能力只需要在钓鱼场景上做领域适配。yolo detect train \ datafishing.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience20 \ projectruns/fishing模型的预训练权重和训练逻辑是两码事权重决定模型能识别多宽泛的目标训练逻辑决定它在你的数据集上的收敛速度。做这一类场景适配我一般先用nano起步验证baseline参数如下参数建议值说明epochs100有early stopping保护实际跑不到100轮imgsz640默认输入尺寸骨干网络吞吐量高batch168G显存可跑24G显存可试着调到32patience20连续20轮val指标不提升就停device0单张GPU训练没有GPU就改成cpu命令里的patience20是很多人容易忽略的点。它本质上是早停机制当验证集的指标连续20轮没有提升时自动终止训练。我见过不少新手明明训练已经收敛还强行拉满300轮最后不仅浪费时间还可能造成过拟合验证集mAP反而下降。设成20到30在中小数据集上比较合理。imgsz640是YOLO系列的默认分辨率这套数据集如果原始图像是1920x1080的监控画面后续可以等640的结果出来后再用imgsz1280微调一轮把小目标召回率提一提。Ultralytics也支持纯Python代码方式启动效果和命令行完全等价from ultralytics import YOLO model YOLO(yolov8n.pt) model.train( datafishing.yaml, epochs100, imgsz640, batch16, patience20, projectruns/fishing, nameexp1 )Python方式的好处是方便在训练前后插入自定义逻辑比如训练结束后自动执行验证、导出ONNX、发送通知。命令行方式则适合快速跑一次实验。两种方式都行关键是那条模型加载逻辑——model参数传yolov8n.pt首次运行时会自动下载预训练权重如果下载慢就换成已下载到本地的权重文件路径。4.3 损失曲线怎么判读训练过程中终端会实时刷出四个loss值box_loss定位损失、cls_loss分类损失、dfl_loss分布损失以及它们加权的总和。很多新手只盯着总loss看其实cls_loss更能反映标签类别是否正确。如果总loss在前几十轮正常下降但cls_loss始终横在某个高位不动八成是类别索引和语义对不上或者某个类别的样本太少学不动。Epoch 1/100: box_loss1.234, cls_loss2.107, dfl_loss1.456, mAP500.213 Epoch 20/100: box_loss0.856, cls_loss0.634, dfl_loss1.012, mAP500.674 Epoch 50/100: box_loss0.721, cls_loss0.412, dfl_loss0.872, mAP500.781这是我自己跑出来的日志趋势正常情况应该是box_loss和dfl_loss稳步下降mAP50逐步抬升到后期曲线变平。如果看到loss值剧烈震荡完全不下降常见问题有三个学习率太大、batch太小、数据集里混入了被错误标注的标签。其中被错误标注的情况最阴险loss表面上在降但val精度始终上不去这种问题只能靠抽样可视化检查标注来定位。5. 避坑与排查标签错位、类别失衡与置信度误检训练类项目绕不开排查环节这里把我在类似数据集上实际踩过的坑按现象、原因、解决三层拆开写遇到同类问题可以直接对照处理。5.1 训练崩溃与标签读取错误第一条坑是class index越界。现象是训练跑到第几十轮突然报错类似IndexError: index 5 is out of bounds for axis 0 with size 3。原因很直接某个txt文件里出现了类别索引5但yaml里只声明了3个类别。这类脏数据通常来源于多套数据合并时类别编号没统一。解决方法是写脚本扫描所有txt找出超出范围的标签文件import os max_class 2 # nc3索引范围是 0,1,2 bad_files [] for split in [train, val]: label_dir flabels/{split} for name in os.listdir(label_dir): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path, r) as f: for line in f: line line.strip() if not line: continue class_id int(line.split()[0]) if class_id max_class: bad_files.append((split, name, class_id)) if bad_files: for item in bad_files: print(f{item[0]} {item[1]} contains class {item[2]}) else: print(all labels ok)扫描结果会列出哪些文件包含越界索引、具体索引值是多少。处理办法是打开这些文件把异常行删掉或者重新分配一个合法类别编号然后重新执行训练。这类问题在设计数据时就要留个心眼合并外部数据时务必预先统一类别映射表。第二条坑是坐标对不上图。现象是训练能正常跑但用2.2节的脚本可视化检查时发现框画在完全无关的位置比如水面中央飘一个框里面什么都没有。原因基本有三种图像被resize过但标签没跟着缩放、txt里的归一化坐标被误当成像素坐标、图像文件名和标签文件名错位。解决方法是多抽几张图做可视化验证重点看框是否扣住目标边缘。我拿到新数据集后固定会抽30张图叠加标注看一遍这个习惯帮我避开过至少三次标签错位事故。5.2 收敛与精度类问题第三条坑是loss降了但mAP上不去。现象是训练完看mAP50只有0.3左右远低于预期。原因往往不是网络结构问题而是目标尺寸分布和输入分辨率不匹配。这套捕鱼场景里鱼在监控画面中经常只占二三十个像素imgsz640下这些目标直接缩成一个点。解决方法是先把imgsz加到1280重新训练一轮如果mAP明显提升说明小目标确实是被分辨率卡住了如果提升不大再考虑做增强策略调整。第四条坑是推理时低置信度误检。现象是验证视频里框特别多把水面波纹、浮漂阴影都识别成了鱼。原因是推理时confidence阈值设太低默认0.25对捕鱼场景未必合适。解决方法是把阈值往上调可以用模型自带的验证接口看不同置信度下的精度和召回率变化然后取一个平衡点。这部分我放到第6章详细展开因为它值得写一整段。第五条坑是断点续训没生效。现象是训练中断后重新执行原命令发现从头开始训练而不是接着上次的权重继续。原因是训练命令里缺少resume参数。解决方法是恢复训练时显式指定yolo detect train resumeTrue或者指定上一次训练的权重路径加载后继续train。这种事情我遇到过一次训练到第80轮结果中断白跑从那以后养成习惯长训练任务一定保留训练日志和权重目录续跑时明确检查epoch数是否连续。6. 进阶技巧用验证集扫描置信度门限并做渔获计数训练完best.pt权重后还剩两个值得打磨的细节选一个合适的置信度门限以及把检测结果落成业务指标。这两件事都直接决定模型上线后好不好用。先看置信度门限怎么选。模型默认conf0.25但对渔获计数这种场景阈值太低会把浮漂和水面反光算成鱼阈值太高又会漏掉小目标。我在验证集上扫一组候选值用F1分数作为选择依据from ultralytics import YOLO model YOLO(runs/fishing/exp1/weights/best.pt) best_f1 0 best_conf 0.25 for conf in [0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]: metrics model.val(confconf, verboseFalse) f1 metrics.box.f1.mean() print(fconf{conf:.2f}, f1{f1:.3f}) if f1 best_f1: best_f1 f1 best_conf conf print(fbest conf: {best_conf:.2f}, best f1: {best_f1:.3f})这段代码循环用同一个验证集返回当前置信度下的F1分数F1是精度和召回率的调和平均兼顾误检和漏检。我一般会把候选区间放在0.1到0.5之间低于0.1时误检噪声会大量涌入高于0.5时小目标基本全丢。选出的最优阈值在后续推理时固定使用而不是每次预测都临时调。选定阈值后就是渔获计数。比如渔政监控场景需要统计一个时间段内识别到的鱼的数量from ultralytics import YOLO model YOLO(runs/fishing/exp1/weights/best.pt) results model.predict( sourceimages/val, confbest_conf, iou0.45, saveTrue ) catch_count 0 for r in results: for cls_id in r.boxes.cls.tolist(): if model.names[int(cls_id)] fish: catch_count 1 print(fvalidated frames fish count: {catch_count})iou0.45是NMS的重叠阈值目标框重叠较多时适当调高这个值能减少重复计数。这个统计框架可以继续扩展成逐帧统计、按时间段聚合或者按视频滑窗统计核心思路都是把yolo的检测结果转换成业务可读的数值。我第一次跑这个数据集时就是没做类别统计直接塞进YOLOv8结果训练到第40轮报索引越界白等了两个多小时。从那以后我每次拿到新数据集都强制走一遍第3章的两个统计脚本再抽30张图做标注可视化确认标签能对上才开始训练。数据集的完整性和格式一致性真的决定了后面所有工作的成败希望帮到你。本文还有配套的精品资源点击获取
返回列表