简介:面向目标检测算法学习者的捕鱼识别数据集,包含1813张带标签图像,覆盖钓鱼、捕鱼等真实场景。资源已针对yolov5、yolov7、yolov8、yolov9、yolov10及yolo11等主流算法完成训练集与验证集划分,解压后可直接开始模型训练和验证测试,省去自行整理数据集的环节。标签部分同时提供yolo格式的txt文件和voc格式的xml文件,分别存放在独立目录中:txt每行记录类别索引与归一化后的中心坐标、宽高,适合yolo系列直接读取;xml则兼容更通用的目标检测标注规范,便于在其他框架或标注工具中复用。压缩包共2000个文件,其中xml标注1584个、txt标注416个,整体大小96.7MB,数据规模适中,适合快速跑通训练流程并验证不同算法在该场景下的表现;目前已有75人学习/下载。依托这份数据,可以低成本完成捕鱼识别任务的模型训练、参数调优与效果对比,也能作为毕业设计或算法竞赛的数据支撑。
1. yolo算法与捕鱼识别数据集:1813张带标签图像,够训出一个能落地的检测模型吗
市场上一堆讲yolo算法讲解ppt的内容,把网络结构拆得天花乱坠,但真到了训练环节,决定成败的往往是数据集本身。这套捕鱼识别数据集由1813张带标签图像组成,压缩包名叫“钓鱼.zip”,解决的是水产养殖自动计数、渔政监控事件触发、休闲钓鱼视频自动打点这一类目标检测需求。我的结论是:只要场景集中、标签干净,这个量级的数据足以训出一个能用的模型;想覆盖所有水域、天气和拍摄角度,它就不够。下面按拆数据、训模型、调参、排坑、落地的顺序,把这条路完整走一遍。
2. 先拆zip再谈训练:目录结构、标注格式与质量体检
拿到一个压缩包,最忌讳的是解压后直接开训。捕鱼识别这类数据在市面上流传时,组织形式五花八门,有规范的也有粗制的。先把结构看清楚、把质量问题排掉,后面训练才不会变成一场玄学调参。
2.1 解压后先确认四样东西:图像、标签、划分、类别名
一个能直接开训的YOLO格式数据集,通常长这样:
| 类型 | 常见形式 | 说明 |
|---|---|---|
| 图像目录 | images/train 下的 jpg 文件 | 训练图像,可能混有 webp、bmp |
| 标签目录 | labels/train 下的同名 txt | 每行一个目标,格式为 class cx cy w h |
| 划分信息 | 目录名或 train.txt/val.txt | 没有划分时需要自己切 |
| 类别名 | classes.txt 或 data.yaml | 决定类别 id 的映射顺序 |
一条经验:images 里的图像和 labels 里的 txt 必须同名一一对应。如果出现一张图对应了多个标签文件,或者 txt 文件名比图像名多了后缀,训练时 YOLO 会静默跳过,只留下一行警告,很多人不注意就少训了上百张图。
2.2 捕鱼数据集全身体检脚本:缺标签、空标签、坏图和类别分布一次看完
第一次拿到数据集,我先跑一个体检脚本,而不是直接启动训练。这个脚本检查四个问题:缺标签的图、空标签的图、损坏的图、类别的目标数分布。
# dataset_check.py # 检查YOLO格式捕鱼识别数据集:结构、缺失标签、空标签、坏图、类别统计 from pathlib import Path from collections import Counter from PIL import Image base = Path("fish_dataset") images_dir = base / "images" labels_dir = base / "labels" img_exts = {".jpg", ".jpeg", ".png", ".bmp", ".webp"} missing, empty, corrupt = [], [], [] img_list = [p for p in images_dir.rglob("*") if p.suffix.lower() in img_exts] for img in img_list: txt = labels_dir / (img.stem + ".txt") if not txt.exists(): missing.append(img) elif txt.stat().st_size == 0: empty.append(img) try: with Image.open(img) as im: im.verify() except Exception: corrupt.append(img) print(f"图像总数: {len(img_list)}") print(f"缺标签: {len(missing)}, 空标签: {len(empty)}, 坏图: {len(corrupt)}") # 统计每个类别目标数 counter = Counter() for txt in sorted(labels_dir.rglob("*.txt")): for line in txt.read_text().splitlines(): parts = line.split() if len(parts) >= 5: # 形如: cls_id cx cy w h counter[parts[0]] += 1 print("类别id -> 目标数:", dict(sorted(counter.items())))img_exts 集合按需扩展,遇到 tiff 就加上 .tiff。img.stem 取不带后缀的文件名,保证 img 和 txt 能对上。im.verify() 只做文件结构校验,不把像素全读进内存,对上千张图的目录很快。
缺标签的图单独看其实不危险,但量大了会稀释训练信号;空标签的图是有意为之的负样本,如果某个目录里大量空标签,先确认制作方是不是把负样本也放了进来;坏图会直接导致训练中断。类别统计如果发现某个类别只有几十个目标,要么砍类,要么准备过采样,否则训练出来这个类基本废掉。
2.3 捕鱼数据集标注格式不统一:三种格式差异与YOLO txt互转脚本
捕鱼识别数据集的标签,市面上流通的主要是三种:YOLO txt、VOC XML、COCO JSON。YOLO txt 每行是“类别id 中心x 中心y 宽 高”,全部归一化;VOC XML 把坐标存成 xmin/ymin/xmax/ymax 原始像素值,还带着 image size 信息;COCO JSON 则是以 annotation 数组形式存全部标注,坐标也有绝对和相对两种变体。
遇到 VOC XML,我用下面这段转成 YOLO txt:
# voc_to_yolo.py # 将VOC XML标注转换为YOLO格式txt import xml.etree.ElementTree as ET from pathlib import Path class_list = ["fish", "net", "person"] # 与训练yaml中names顺序一致 def convert(xml_path: Path, out_dir: Path) -> None: root = ET.parse(xml_path).getroot() img_w = int(root.findtext("size/width")) img_h = int(root.findtext("size/height")) lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in class_list: continue # 忽略不在类别列表的目标 cls_id = class_list.index(name) b = obj.find("bndbox") x1, y1 = float(b.findtext("xmin")), float(b.findtext("ymin")) x2, y2 = float(b.findtext("xmax")), float(b.findtext("ymax")) cx = (x1 + x2) / 2 / img_w cy = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") out_dir.mkdir(parents=True, exist_ok=True) (out_dir / (xml_path.stem + ".txt")).write_text( "\n".join(lines) + "\n", encoding="utf-8")class_list 的排序是全局的,一旦定下,后面的训练 yaml、推理显示、后处理脚本都得沿用。img_w 和 img_h 必须是原图尺寸,有些 XML 的 size 字段和实际图片不一致,转换前先抽查两张核对。VOC 坐标算出来偶尔出现负值或大于 1,原因往往是标注框越界,这类框要么 clip 到 0~1,要么直接丢弃,否则训练过程会出现 loss 为 NaN 的异常。
COCO JSON 转 YOLO txt 的道理一样,只是要从 annotations 里按 image_id 回连图片元信息,多一步 id 映射。转完后把 2.2 的体检脚本再跑一遍,保证转换产物和原图一一对应,这一步别省。
3. 用YOLOv8把1813张图跑起来:环境、data.yaml与最小训练命令
3.1 捕鱼数据集训练的环境准备:一条pip命令与一个GPU检查
对于 YOLO 训练,我几乎只用 ultralytics 的 YOLOv8 这条线。很多人会花一晚上在源码编译上折腾,完全没必要,pip 包封装得很完整。先用干净的解释器环境装上:
conda create -n fish python=3.10 -y conda activate fish pip install ultralytics装完立刻验证 CUDA 可用性,别等到训练开始才发现用的是 CPU:
python -c "import torch, ultralytics; print(torch.cuda.is_available(), ultralytics.__version__)"如果第一个值是 False,说明 pip 装的是 CPU 版。常见做法是卸载后按本机驱动对应的 CUDA 版本重装 torch,比如 CUDA 12.x 版本。注意显卡驱动的 CUDA 版本要高于等于 torch 所需的版本,用 nvidia-smi 查一下即可。1813 张图用 CPU 不是不能训,但一个 epoch 可能近一分钟,调试迭代太慢,会严重影响你调参的心情。
3.2 捕鱼数据集的data.yaml:相对路径与类别顺序是命门
ultralytics 训练读的是 YAML 配置。一个捕鱼识别配置长这样,这里以常见的 fish、net、person 三类为例:
# fish_data.yaml # 所有路径都相对于本文件所在目录 path: .. # 数据集根目录相对路径 train: fish_dataset/images/train val: fish_dataset/images/val test: fish_dataset/images/test # 可选 names: 0: fish 1: net 2: personpath 写 .. 表示 yaml 文件放在数据集根目录的上一级;train 和 val 的路径从 path 出发拼接。千万不要写死绝对路径,否则目录一挪就要改配置。names 顺序决定标签 class id 的映射,这个顺序一旦定下来,训练、推理、显示都要沿用,改一个地方就容易出现类别错乱。
3.3 最小训练命令:预训练权重转移与关键参数
数据配置好了,一行命令就能开训:
yolo detect train data=fish_data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16model=yolov8n.pt 表示从 COCO 预训练权重开始微调。捕鱼识别与 COCO 里的人物、船只有一定语义重叠,迁移学习能让模型在几十个 epoch 内就学到鱼的基本形态,而不是从头学边缘纹理。yolov8n 是最轻的骨干,如果显卡显存超过 12G,可以换成 yolov8s 或 yolov8m,精度会更好。
训练开始后,终端会输出当前 epoch 的 box loss、cls loss、mAP 等指标。第一次跑的时候,不要盯着终端一行行看,而是等它跑到 10 个 epoch,再看训练目录 runs/detect/trainXX/ 下的 results.png。那张图里有四条 loss 曲线和几条 mAP 曲线,比终端数字直观得多。如果 loss 曲线在下降、mAP 曲线在上升,说明方向基本对了。
3.4 断点续训与best/last权重选择:哪个才是最终模型
训练中途断了是常态。断点续训的命令是把 model 参数指向 last.pt:
yolo detect train data=fish_data.yaml model=runs/detect/trainXX/weights/last.pt epochs=150注意此时 epochs 表示“总 epoch 数”而不是“剩余 epoch 数”,ultralytics 会从已有状态继续往下走。训练结束后 weights 目录里出现 best.pt 和 last.pt:best 是验证 mAP 最高的点,last 是最后一步。验证集如果划分得比较偏,last.pt 在真实场景里可能反而更稳,两个都留着,推理时各跑一条视频对比后再选。
4. 让模型在真实钓鱼场景里可用:imgsz/batch/patience与指标解读
4.1 三个必调参数,以及一组可以直接抄的基线
默认参数能跑通,但针对“1813张图 + 可能很多小目标”的数据集,我会先给出一张参数表,标注哪些必须动:
| 参数 | 捕鱼识别建议 | 设置理由 |
|---|---|---|
| lr0 | 0.005 | 数据量小,学习率太大会震荡 |
| epochs | 150 | 配合早停,给收敛留余量 |
| imgsz | 960 | 远距离的鱼目标小,大分辨率保特征 |
| batch | 8 | 8G 显存下兼顾速度与稳定 |
| patience | 30 | 小数据集 loss 波动大,早停太灵敏反而早退 |
imgsz=960 对显存压力明显增大,batch=8 能让大多数 8G 显存卡跑起来。如果显卡只有 6G,把 imgsz 退回 640,优先保证训练能跑完。patience 表示连续多少个 epoch 验证指标没有提升就自动停止,我用 30 而不是默认值,是因为 1813 张图的小数据集在第 40 到 60 个 epoch 之间可能出现暂时的指标停滞,太早停会错过后面的提升。
4.2 mAP50、mAP50-95和混淆矩阵:指标说人话
捕鱼识别任务里,mAP50 衡量框的粗略重合度,mAP50-95 衡量框的精确定位。用于监控触发,mAP50 到 0.85 就够用;用于鱼的尺寸估算或精确计数,mAP50-95 才提现真实难度。判断模型是否能用,不要只盯着 mAP50,至少把 val 目录里的混淆矩阵打开看一眼。
背景类别往往是捕鱼识别最大的假想敌。如果混淆矩阵里 background 一列有大量预测值,说明模型把反光、水草、波浪当成了鱼。此时先不要继续加训练轮次,回第 5 章去处理负样本,否则训练再久误检率也压不下来。
4.3 没有测试集时,自己做一个躲避数据泄漏的盲测集
很多数据集只给了 train 和 val,且 val 是从同一个视频里随机抽的帧。这种划分方式会造成数据泄漏:同一个视频的相邻帧几乎长得一样,模型相当于在 val 里见到了训练集里的“熟人”,指标虚高。
我的做法是把数据集里不同场景、不同时间段的图像挑出来,让 train 和 val 按场景隔开,保证 val 里的摄像头角度、光线时段和 train 有明显差异。没有测试集时,这个盲测集就是判断模型能否上线的最后关卡。模型在盲测集上的分数比 val 低 5 到 10 个点都属于正常,低太多就要回头查划分。
5. 捕鱼识别训练避坑:5个典型问题与排查方法
这几条都是实际项目里踩出来的坑,写成了现象、原因、解决三段,方便你直接对号入座。
5.1 现象:loss一路下降,mAP却在原地打转
原因是标注噪声。框边缘不齐、类别错标、重复框都是噪声源。loss 下降说明模型在努力拟合训练集,而 mAP 不动说明拟合到的是一套错误的信息。解决:先用 2.2 节的脚本按类别抽样,人工看 50 张图,重点看两类错误:框比目标大了一圈,或者把漂浮物标成了鱼。清理掉明显错标后重训,往往比调整十次参数都有效。标签清洗是 YOLO 项目里最不性感但回报最高的一步。
5.2 现象:把水面反光、浮漂、波浪误检成鱼
原因是负样本不足。1813 张图如果每张都有鱼,而且背景都是清一色水面,模型没见过“有反光但没有鱼”的画面,推理时只能靠纹理猜测,而水面反光的光斑和高光鱼体在特征上很接近。解决:从没有鱼的视频片段里抽 100 到 200 帧,每帧配一个空 txt 标签,放进训练集。这个操作成本极低,对误检率的压制立竿见影。我一般把负样本比例控制在总样本的五分之一左右,再多就会拖累召回率。
5.3 现象:远距离的小目标鱼几乎全部漏检
原因是目标像素占比太小。1080p 画面里一条 30 像素长的鱼,在 imgsz=640 训练时缩到约 17 像素,经过骨干网络的几次下采样,特征图里的响应已经很弱。解决:把 imgsz 提到 960 或 1280;如果是 4K 视频抽帧,先切图再训练,把大图切成无重叠的 1280×1280 块,推理时再放回原坐标拼接。切图训练会让数据量变大,但小目标召回率的提升非常明显。
5.4 现象:验证集mAP 0.95,换个新场景什么都框不出来
原因是数据泄漏。随机拆分时同一个视频的相邻帧同时进了 train 和 val,验证集看着是“没用过的数据”,实际已经被模型记住了。解决:按视频或按时间段划分数据,禁止随机抽帧。这也是为什么我习惯自己再造一份盲测集,把 val 切成与 train 完全不同的场景再评一次。模型如果在这两份数据上分数差距大,说明是记忆而非泛化。
5.5 现象:导出ONNX部署后,指标与训练时差距明显
原因是预处理不一致。训练时 YOLO 用 letterbox 保持宽高比并补边,导出后的模型如果直接对原图做等比 resize,特别是截图后直接 reshape,检测框位置和置信度都会漂移。解决:在推理代码里复现 letterbox 处理,并且导出时固定输入尺寸。这个修复通常十分钟,但很多人排查一整天都不怀疑预处理,属于最典型的部署坑之一。
6. 部署前的最后一锤:调置信度阈值、固定预处理再导出ONNX
6.1 阈值扫描:先压误检再谈其他
模型指标不差,不代表拿起来就能直接上线。第一次跑推理,我会把 best.pt 拿到一个没参与训练的视频上完整看一遍,数一数误检和漏检。捕鱼识别场景里,水面反光引起的误检如果明显偏多,把 conf 从 0.25 抬到 0.35,往往能压掉一半假框。
提示:部署时的 conf 和验证时的 conf 是两套参数,训练命令行里的 conf 只影响验证输出,不影响模型权重。
6.2 固定预处理逻辑,导出前后各验证一次
YOLO 训练时的 letterbox 逻辑必须原样搬到推理端。导出时 imgsz 固定成训练值,opset 固定为 12,导出完成后,我把同一张图分别用 pt 和 onnx 格式各推理一遍,对比输出框坐标和置信度,偏差超过 1% 就回头查预处理。我自己的习惯是把 letterbox 和归一化封装成一个共享函数,训练和推理用同一份代码。这个习惯帮我省掉了大部分部署返工。希望这次分享能帮你在捕鱼识别这个方向少踩几个坑,让数据集真正变成能上线的检测服务。
本文还有配套的精品资源,点击获取