简介一份包含2798张真实场景鱼类图片的目标检测数据集面向计算机视觉学习者、科研人员及渔业AI应用开发者覆盖31个常见鱼类种类每张图片均附带VOC格式XML边界框标注与YOLO格式TXT类别标签两种格式兼有可直接用于主流检测模型训练。压缩包共含2000个文件以XML标注文件为主另含YOLO格式TXT标签及说明文档整体大小约109MB。目前已有417人学习/下载适用于渔业资源监测、水产养殖智能化、鱼类分类识别等场景。数据集自带完整类别清单省去标注格式转换与类别映射的麻烦使用者可按需划分训练集和验证集快速在YOLOv5/YOLOv8、SSD、Faster R-CNN等框架上启动训练显著提升项目前期效率。无论学术实验还是工程落地都能即开即用减少自行采集与标注的时间成本尤其适合需要快速验证检测算法的团队与个人。1. 目标检测数据集到手先别急着训练2798张、31类的鱼数据集该从哪步开始拿到“鱼数据集2798张31个种类分类检测VOCYOLO格式”这种压缩包多数人的第一反应是解压、装环境、直接train。我自己第一次做类似的鱼目标检测时就这么干的结果训到第10轮loss还在0.1附近晃打开预测图一看模型把水草和鱼尾巴框成一团。问题不在训练参数而是我没分清VOC和YOLO两套标注之间的差异也没检查类别编号是否对齐。这套数据集的真实价值在于31类已经是中量级分类检测任务2798张图不算大但足够用来做迁移学习、练手VOC/YOLO格式转换、以及验证数据增强策略。它适合两类人一类是刚接触目标检测、想用现成标注跑通pipeline的初学者另一类是做渔业资源监测、水产养殖或水下机器人视觉方案需要一份贴近真实场景的鱼类基准数据做预研的工程师。在动手训练之前先花一个下午把这2798张图的目录结构和标注格式摸清楚后面能省下大半周。2. 拆解数据集内部目录结构、XML/TXT标注与31类鱼种的编排逻辑拿到压缩包先别急着复制粘贴到训练目录第一步应该做的是“体检”。一个打包好的VOCYOLO双格式检测数据集通常内部是两套平行的目录VOC风格的一套用于兼容老工具链YOLO风格的一套用于直接喂给darknet、ultralytics这类框架。这两套之间不是简单复制而是同一个标注信息的两种序列化方式。2.1 解压后的典型布局JPEGImages、Annotations和labels三个目录各管什么常见做法是压缩包解压后出现三个核心目录JPEGImages放所有原图Annotations放VOC格式的XML标注labels放YOLO格式的txt标注。如果你收到的包里有ImageSets/Main目录那是VOC体系用来存放训练集、验证集划分清单的固定位置没有也不奇怪因为很多打包者在分发时只保留了最核心的图片和标注。鱼数据集2798张31个种类分类检测VOCYOLO格式/ ├── JPEGImages/ # 2798张jpg图片 ├── Annotations/ # 2798个同名xml标注 ├── labels/ # 2798个同名txt标注 ├── classes.txt # 31个类别名每行一个 └── ImageSets/Main/ ├── train.txt # 训练集图片清单不含扩展名 └── val.txt # 验证集图片清单图片数量2798张对应XML和TXT各2798个这个对等关系是数据集质量的第一道体检线。用一行命令就能核对三者数量是否一致ls JPEGImages | wc -l分别看三个目录只要数量对不上就说明有图片缺标注或者标注文件是空壳训练时会出现“No labels found in ...”之类的报错。2.2 VOC格式为什么难直接训练从XML里读懂一个检测框的全部信息VOC格式的XML描述一张图里“有什么、在哪、属于什么类”。它把图片元信息和每个目标对象都写在同一个文件里人眼可读性好但训练框架不能直接吃需要先转成纯数值的txt。打开任意一个XML文件可以看到这样的结构annotation filenamefish_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nametilapia/name bndbox xmin112/xmin ymin208/ymin xmax734/xmax ymax652/ymax /bndbox /object /annotationfilename必须在图片集里真实存在bndbox的四个整数是像素坐标左上角原点。这里最容易出的问题有两个一是width和height与图片真实分辨率不一致导致归一化算错二是同一张图有多个object时目标之间可以重叠转YOLO格式时不需要处理重叠但要知道类别字段name的字符串必须和类别清单严格一致比如写Tilapia和tilapia在人工看的时候没啥差别但脚本一跑就变成两类。所以做格式转换时类别名一定要以classes.txt为准做映射而不是自己去猜。2.3 YOLO格式的精髓一行txt就是一个小目标图像坐标全被归一化YOLO格式的txt文件和图片同名每行代表一个检测框五个字段class_id x_center y_center width height。注意这里x_center、y_center不是边界框左上角而是中心点坐标并且全部做了归一化——除以图像宽度和高度值域落在0到1之间。3 0.4375 0.3589 0.1489 0.1847 3 0.6132 0.6611 0.1203 0.2351 17 0.2178 0.8123 0.0921 0.1456第一列的3和17不是类别名是类别编号。编号由classes.txt里的行顺序决定第1行对应0第2行对应1以此类推。这是YOLO训练中最容易翻车的地方——训练脚本要求你提供一个data.yaml里面names列表必须和classes.txt完全一致且顺序相同。如果压缩包里的classes.txt排的是“草鱼、鲤鱼、鲢鱼”而你在data.yaml里按字母序排成“鲢鱼、鲤鱼、草鱼”模型训练时就会把标注中编号3的鱼当成编号3的另一种鱼整个模型学出来的都是错的。2.4 31类鱼种编排的两种逻辑人工语义分类和字母序分类31个类别在classes.txt里的编排顺序直接决定YOLO编号规则。常见的有两种组织方式按生态环境分淡水鱼、海水鱼、观赏鱼或者按字母序a-z排。前者对做分类研究的博士友好后者对训练脚本友好。从工程角度我建议拿到包后先做一件事用脚本读一遍全部标注文件统计每个类别实际出现的次数看看有没有个别类别只有十几张图。目标检测是数据饥饿型任务一个类别只有几个框模型基本上学不会。具体命令cat labels/*.txt | awk {print $1} | sort | uniq -c | sort -n上面这行命令把每个txt标注文件里的第一列类别编号提出来统计编号出现次数。如果看到某个编号只有个位数说明这个类别是长尾类别训练时要么做重采样要么干脆合并到父类里。31类这个规模类别间样本量差距很容易到10倍以上这是做鱼检测比做通用物体检测更需要注意的点。3. 把VOC实测转成YOLO转换脚本与四个高频坑的排查如果你打算先用VOC格式做训练——比如用mmdetection、老版本Detectron2这类吃VOC标注的框架——那可以跳过本章。但大部分读者会走YOLO路线因为YOLO系列从v5到v8再到现在热门的yolov26官方工具的默认输入都是这种txt格式。与其用网上找的“一键转换工具”不如自己写一个20行的脚本既能把数据转干净还能顺便做一遍标注质量的全面体检。3.1 最小可用的VOC转YOLO脚本python版转换模板与参数说明import os import xml.etree.ElementTree as ET from pathlib import Path # 配置三个路径 voc_xml_dir Annotations yolo_label_dir labels classes [line.strip() for line in open(classes.txt, encodingutf-8)] def convert_voc_to_yolo(xml_path, label_path): tree ET.parse(xml_path) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in classes: print(f跳过未登记类别: {name} in {xml_path}) continue class_id classes.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h # 防止边缘框归一化后越界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) box_w min(max(box_w, 0.0), 1.0) box_h min(max(box_h, 0.0), 1.0) if box_w 0.001 or box_h 0.001: print(f跳过极小框: {xml_path} {name}) continue lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) Path(label_path).parent.mkdir(parentsTrue, exist_okTrue) with open(label_path, w) as f: f.write(\n.join(lines)) for xml_file in os.listdir(voc_xml_dir): if xml_file.endswith(.xml): convert_voc_to_yolo( os.path.join(voc_xml_dir, xml_file), os.path.join(yolo_label_dir, xml_file.replace(.xml, .txt)) )逻辑上分三段先读图片宽高因为归一化必须以真实尺寸为分母再遍历每个object把name映射成整数ID最后做越界裁剪和极小框过滤。这三个动作一个都不能少跳过任何一个都会给训练埋坑。参数上要注意w和h要用size节点里写的值但这不是绝对可信的——最稳的做法是用PIL或cv2读一遍图片的真实尺寸以真实尺寸为准。如果XML里的尺寸和图片实际尺寸不一致转出来的坐标会整体偏移训练时loss降不下去但检测框看起来又好像有点合理这种错误最迷惑人。3.2 避坑记录一类别编号错位导致模型把整类鱼全学错现象训练正常mAP也涨到0.7但验证集预测出来的鱼种和真实标签对不上比如把鲤鱼全识别成草鱼。原因classes.txt的行顺序和data.yaml里的names顺序不一致。VOC脚本用classes.index(name)给编号训练框架按data.yaml的顺序解释编号两者一旦错位所有标注都被静默错译。解决转换完成后抽查三个txt标注打开第一个数字不是0的标注用classes.txt里的名字回填确认和XML里的name一致。更彻底的做法是写一个小校验脚本把所有XML和txt读一遍统计每个编号的类别名分布对不上的直接报错。3.3 避坑记录二图片文件名大小写混用导致大量图片没有标注现象训练时log提示Found 2798 images but only 2105 labels直接少了693张图的标注。原因压缩包里的图片命名混用了.jpg和.JPG而标注文件统一是.jpg.xml。Windows打包工具在展示时隐藏了扩展名看起来文件名一样实际后缀不同很多初版转换脚本按扩展名简单拼接导致部分图片找不到对应标注。解决写脚本前先统一文件名。Linux下用rename命令批量改成小写后缀find JPEGImages -name *.JPG -exec rename s/\.JPG$/.jpg/ {} \;改完图片文件名立即检查Annotations目录和labels目录里的引用名是否同步。如果标注中的filename写的是.JPG而图片已经改成了.jpg需要一并改XML引用的文件名。3.4 避坑记录三XML里的bndbox越界导致loss爆炸变NaN现象训练前几轮loss正常第10轮左右突然变成nan整个训练报废。原因标注框的一部分超出了图片边界比如xmax写成12345归一化后大于1。YOLO损失里的坐标分支对异常值很敏感数值突变梯度爆炸就会冲成NaN。解决在转换脚本里加clip操作把坐标夹到[0, 1]区间。上面模板里已经写了这四行。需要额外提醒的是clip是合格标注的下限不是万能药。如果一张图里超过30%的框都要靠clip才能合法说明原始标注质量本身有问题建议人工检查那几张图而不是硬修。3.5 避坑记录四训练集验证集划分被重复随机化导致数据泄漏现象训练mAP高达0.9但把模型放到真实场景里预测效果很差怀疑是白嫖了测试集。原因原始数据集在ImageSets/Main里提供了固定的train/val划分但不少人在转换后会自己重新随机划分。如果随机划分时没固定随机种子每次跑脚本划分都不一样两次实验之间验证集有重叠模型等于提前见了答案。解决直接用包里的ImageSets/Main/train.txt和val.txt不要自己重新划分。如果原包没提供划分文件自己划分时务必备份划分结果并且在data.yaml里用绝对路径指向那两份固定的txt。数据集只有2798张最好坚持7:2:1划分其中1成为最终测试集训参时永远不碰这部分。4. 用YOLO格式直接训练从目录整理到跑通首个模型格式转清楚后训练本身反而是最机械的环节。用ultralytics的YOLOv8作为示例因为它是目前目标检测数据集在社区里验证最方便的框架装好库后五条命令就能跑一个完整流程。4.1 目录重排把数据摆成YOLO训练器能直接吃的样子YOLO训练框架对数据目录的要求很简单图片和txt标注默认放在同一个目录下图片叫xxx.jpg标注叫xxx.txt。但不少数据集打包者会把图片和标注分开还会在中间夹一层JPEGImages子目录需要手动归集。我一般用一个短脚本交集文件列表确保每一张图片都有标注、每一个标注都有图片然后把它们放平。mkdir -p dataset/images dataset/labels for f in JPEGImages/*.jpg; do base$(basename $f .jpg) if [ -f labels/$base.txt ]; then cp $f dataset/images/ cp labels/$base.txt dataset/labels/ fi done ls dataset/images | wc -l ls dataset/labels | wc -l这版脚本最核心的价值是最后一对wc -l两个数字必须相等且等于2798。如果不等说明有图片没标注或标注没图片训练时会丢样本。不要跳过这个检查因为框架能容忍部分样本无标注但你的模型会在推理时对这部分场景表现得像“没学过”。4.2 data.yaml的三个必写参数path、names和train/val路径在ultralytics框架里数据集描述文件是data.yamlYOLOv5、YOLOv8、包含一些社区对yolov26的复现实现都沿用这个风格。最简版本长这样path: /abs/path/to/your/dataset train: images/train val: images/val names: 0: tilapia 1: carp 2: catfish # 一直列到第31个类别三个关键点path必须写绝对路径因为框架会把它作为基准路径去拼接train和valnames列表的行顺序必须和classes.txt完全一致复制粘贴时要整段拉过来train和val写的是相对path的子目录名不是完整路径。数据集2798张、31个类别建议train放2200张、val放598张比例接近8:2。val集太小mAP的置信区间会过宽调参时看不出真实差异。4.3 训练命令与五个必调参数imgsz、batch、epochs、patience、device目录和配置就绪后跑训练只需要一条命令。第一轮训练建议用小模型不要一上来就yolov8x那是浪费GPU资源。yolo detect train \ data/abs/path/to/your/dataset/data.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ patience20 \ device0参数选择逻辑modelyolov8n.pt是官方预训练的nano版本参数量最小适合这个量级的数据做算法验证epochs120对2798张图来说足够YOLO一般训练到60轮左右mAP增速就开始放缓120轮会触发早停兜底patience20表示连续20轮验证指标不涨就自动停止是防止过拟合的后悔药imgsz640是速度和精度的中间值鱼这类中等目标不需要拉到1280否则训练时间翻倍收益不明显batch16在单张2080Ti级别显卡上是安全值内存紧张就降到8。4.4 首次训练后必须跑一次验证用val模式看mAP和PR曲线训练结束后框架会在runs/detect/train/下生成results.csv、混淆矩阵、PR曲线和验证集预测图。每个文件都不是摆设results.csv里有每轮的train/box_loss、val/box_loss和metrics/mAP50-95。观察mAP50-95要比观察mAP50更严格前者计算多个IoU阈值下的平均结果。鱼检测任务里很多鱼体积小且重叠mAP50-95过0.5就属于值得继续调参的成绩。yolo detect val \ modelruns/detect/train/weights/best.pt \ data/abs/path/to/your/dataset/data.yaml这行命令会在验证集上回归一遍输出每一类的AP。做这一步的目的是找出弱点类别如果31类里有一类AP只有0.1说明这个类样本量太少或标注噪声太大后续需要针对它做数据增强或加样本而不是盲目调整全局学习率。5. 值不值得深挖用训练曲线和预测图给数据集做体检训练完成后最值得做的一件事是把训练日志里的曲线调出来看三点loss是否先降后稳mAP50-95是否持续上升mask图里漏检的鱼有没有明显模式。如果loss曲线出现“陡降-反弹”说明学习率太高或数据里有脏标签如果mAP在某个类别上始终抬不起头这是数据集本身的类不均衡问题不是调参能解决的。一个我常用来验收鱼检测模型的土办法从验证集里随机挑20张图片用yolo predict跑一遍把结果图拼在一起用肉眼数漏检的鱼。20张图里只要出现3条以上漏检就说明模型对单张图里的小目标鱼群处理得还不够。鱼检测和通用目标检测最大的不同在于鱼群目标大量拥挤在一起NMS闸值从默认的0.45调到0.3会漏检调到0.6又会把两条相邻鱼并成一个框。遇到这种情况我会单独跑一个实验只调NMS闸值用验证集的mAP对比选最优。如果这2798张图的标注质量可靠、31个类别覆盖了你的业务范围这个数据集完全可以作为预训练底料。更进一步的路线是用它先训一个强基模型然后再用你自建的少量鱼种数据微调这样即使自建数据只有两三百张也能出不错的效果。用这套数据时有一点要提前想清楚31类鱼种的分类边界是不是足够清晰像罗非鱼和鲫鱼这种外形接近的类别模型容易混淆建议训练后用混淆矩阵确认到底哪些类互相串再去决定要不要合并类别。这就是数据集这步投入的全部价值——它不直接给你一个完美模型但能让你少走一个月的弯路。希望帮到你。本文还有配套的精品资源点击获取