ARTICLE DETAIL

资讯详情

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

水下物体检测数据集实战:从YOLO格式转换到训练避坑全流程

水下物体检测数据集实战:从YOLO格式转换到训练避坑全流程 简介这份水下物体检测数据集面向目标检测与海洋AI开发者提供涵盖训练、验证、测试划分的545张真实水下图像及配套YOLO格式标注可用于水下机器人、海洋生态监测等场景的模型训练与算法验证。压缩包共1092个文件以jpg图像和txt标注文件为主体另含yaml配置文件可直接对接YOLO训练流程以及docx说明文档便于快速了解数据组织方式整体仅7.29MB轻量易上手。已有136人学习下载适合需要开展水下目标识别实验的计算机视觉初学者或工业落地人员。通过该数据集可掌握水下环境下的边界框标注规范训练具备一定鲁棒性的检测模型其类别统一为水下物体便于聚焦场景特征为后续扩展到多类海洋目标识别奠定基础。1. 为什么拿水下物体检测数据集试手先搞清楚它是给谁准备的做水下机器人、生态监测或者水产养殖的朋友第一步卡住的往往不是模型而是数据集。市面上的目标检测数据集大多是VOC/COCO格式的地面场景真正能直接拿来喂给YOLO的水下标注数据并不多。这个《水下物体检测数据集.zip》正好补上这块解压之后就是YOLO可读的images和labels目录标注的是水下常见的生物和人工目标覆盖生态环境类目标检测的场景。适合刚接触目标检测的开发者用来跑通全流程也适合做工业水下检测的工程师拿来做预训练。很多人以为水下检测难在算法其实真正花时间的是把数据和标签整理到能训练的状态。这份资源给你的不是一个训练好了的权重而是一套能落地复现的数据起点。你接下来要做的是理解目录结构、确认类别映射、设计训练参数然后再去提升精度。下面我就按拆包数据、格式转换、训练配置、避坑排查、验证提效这几步把整个流程过一遍。2. 拆开压缩包图像目录、标签文件与类别映射怎么对齐2.1 目录结构里真正有用的四个东西我解压过不少水下数据集形态五花八门但只要是给目标检测用的最后都有规律。这份压缩包大概率会包含这么几块路径/文件作用说明images/原始水下图像JPG或PNG文件名是唯一标识labels/YOLO格式标签每张图对应一个同名txtclasses.txt类别清单一行一个类别名顺序直接决定编号data.yaml训练入口配置YOLOv5/v8 读取的路径和类别定义其中最重要的就是classes.txt。它决定了编号0到底对应鱼、螃蟹、水母还是水下垃圾。YOLO在读标签的时候只认txt里的数字不认类别名。如果classes.txt的顺序和你之前手工标注的顺序不一致模型就会把猫当成狗训练再久也白搭。我一般会建议拿到任何数据集后先不看图先把classes.txt打开数一行一行对应什么。同时看一眼labels/目录里有没有空txt文件空标签文件会导致训练时loss异常这类文件在企业级工业数据集里尤其常见。工业水下管道检测场景中经常因为标注员漏标留下一堆空标签。2.2 一行txt标签怎么读YOLO格式的标签文件每一行对应一个标注目标核心是五个数字0 0.4821 0.7312 0.1154 0.0867 1 0.2355 0.4129 0.0622 0.0531第一列是类别编号从0开始。后面四个是归一化的中心坐标和宽高统一除以图像的宽和高。所以数值都在0到1之间这和VOC里用像素绝对坐标是两套体系。如果你打开一个txt发现数值超过1或者出现负数要么是标注工具出了bug要么是从VOC格式转YOLO时没做边界处理。转格式时把边界越界直接丢弃是最常见的翻车点。我通常会用下面这段代码把一张图的标签可视化出来确认坐标有没有明显贴错import cv2 def draw_yolo_label(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() if len(parts) ! 5: continue cls int(parts[0]) x_c float(parts[1]) * w y_c float(parts[2]) * h bw float(parts[3]) * w bh float(parts[4]) * h x1 int(x_c - bw / 2) y1 int(y_c - bh / 2) x2 int(x_c bw / 2) y2 int(y_c bh / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imshow(check, img) cv2.waitKey(0) if __name__ __main__: names [fish, crab, jellyfish, garbage] draw_yolo_label(images/0001.jpg, labels/0001.txt, names)逻辑是先把归一化坐标乘以宽高还原成像素坐标再画框。参数class_names必须和classes.txt保持一致否则显示出来的类别名是错的。这一步能帮你快速发现标签是不是错位、框是不是画到岸上去了。2.3 统计类别分布提前发现长尾问题水下场景的类别分布天然极端鱼的数量可能是其他生物的几十倍水母和垃圾往往只占很小比例。如果直接拿去训练模型会对低频类别完全失明。我一般在拿到数据集后第二步就会跑一个类别统计脚本看看每类样本数量和bbox数量。这里给你一个基于glob和os的简单统计import os label_dir labels class_counts {} for filename in os.listdir(label_dir): if not filename.endswith(.txt): continue with open(os.path.join(label_dir, filename), r) as f: for line in f.readlines(): parts line.strip().split() if len(parts) 5: cls int(parts[0]) class_counts[cls] class_counts.get(cls, 0) 1 sorted_counts sorted(class_counts.items(), keylambda x: x[0]) for cls, count in sorted_counts: print(fclass {cls}: {count} bboxes)这里class_counts记录的是每个类别出现的bbox总数不是图像数。一张图里有三条鱼就加三次。如果你更关心图像层面的覆盖需要把cls放进一个集合再计数。这两个指标差很多工业场景里往往大量图像都集中在少部分类别上不看图像覆盖率很容易被总数骗到。如果发现某些类别占比不到5%后续训练时就要考虑重采样或损失函数加权。生态类目标检测里像水母这种带触手的目标边缘特征弱样本再少模型几乎学不到。遇到这种情况我宁愿先剔除部分噪声样本也不要让模型在这个类别上过拟合蓝绿色背景。3. 从XML到YOLOVOC标注转换脚本与四个边界问题3.1 为什么下载后可能不是txt虽然压缩包通常带YOLO格式的labels但有些水下数据集因为透明度高、类别复杂原发布方用的是VOC XML或COCO JSON。这类格式在生态和工业数据集里很常见因为标注工具如LabelImg默认导出XML。如果打开labels目录发现是空的但旁边有个Annotations文件夹说明得先做一次转换。这一步不能手动改容易漏和错。我习惯用下面这个Python脚本把VOC XML转成YOLO txt。它读取每张图的目标框根据图像宽高归一化再写入同名txt。import os import xml.etree.ElementTree as ET voc_dir Annotations yolo_dir labels os.makedirs(yolo_dir, exist_okTrue) class_mapping { fish: 0, crab: 1, jellyfish: 2, garbage: 3 } def convert_xml(xml_path, yolo_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_mapping: continue cls class_mapping[name] box obj.find(bndbox) xmin max(float(box.find(xmin).text), 0) ymin max(float(box.find(ymin).text), 0) xmax min(float(box.find(xmax).text), img_w) ymax min(float(box.find(ymax).text), img_h) x_c ((xmin xmax) / 2) / img_w y_c ((ymin ymax) / 2) / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{cls} {x_c:.6f} {y_c:.6f} {bw:.6f} {bh:.6f}) with open(yolo_path, w) as f: f.write(\n.join(lines)) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue base os.path.splitext(xml_file)[0] convert_xml(os.path.join(voc_dir, xml_file), os.path.join(yolo_dir, base .txt))这里有几个参数需要解释清楚。class_mapping是类别名到编号的映射它的顺序必须和之后classes.txt一致0不能随便给。xmin/ymin做了下限钳制防止标注框的坐标被标成了负数xmax/ymax做了上限钳制防止框超出图像尺寸。这种钳制是必要的因为很多水下图像有透明遮挡标注员经常把框画到图像边缘外。脚本跑完yolo_dir下会出现和xml同名的txt。如果发现某个xml没有生成txt多半是里面所有class都不在class_mapping里或者框宽高为零。这种空文件不能直接删要看一眼是不是漏标。3.2 类别映射表错了训练就全白费类别映射的坑最常见的是顺序错位。比如原VOC里类别顺序是fish, crab, garbage但你用的预训练模型类别顺序是fish, garbage, crab。XML转换时class_mapping一旦写错模型训练到后期loss降不下去但验证集mAP非常差。我自己的习惯是把映射表单独做成一个YAML或JSON文件而不是散在旁边。例如class_mapping { fish: 0, crab: 1, jellyfish: 2, plastic: 3, net: 4 }转换完成后用上一章的统计脚本再跑一次看看每个编号对应的bbox数量是否符合你对数据的直观预期。如果发现某个类别数量突然多了一倍多半是两种鱼被合并了。水下数据集里不同品种的鱼外观接近这种合并常导致标签噪声上升。另外如果classes.txt第一行是背景或者__background__YOLO会把它当成类别0来训练最终预测时会多出一个背景类框。很多坑都源于此尤其是在工业数据集中标注工具默认导出背景类。建议转换后把这类多余行去掉。3.3 训练集/验证集划分随机还是分层数据划分看起来简单实际上对训练结果影响很大。常见做法是按8:1:1随机划分但水下数据集往往来自几段连续视频同一段视频的相邻帧高度相似。如果随机划分时没有按拍摄场景分组验证集里难免泄漏训练帧特征导致validation分数虚高。我一般用以下逻辑先按文件名前缀提取视频段标识再按段为单位划分而不是按单张图。比如文件名是cam01_00215.jpgcam01就是段ID。import os import random random.seed(42) image_files os.listdir(images) segments {} for fname in image_files: seg fname.split(_)[0] segments.setdefault(seg, []).append(fname) keys list(segments.keys()) random.shuffle(keys) train_seg keys[:int(len(keys)*0.8)] val_seg keys[int(len(keys)*0.8):int(len(keys)*0.9)] test_seg keys[int(len(keys)*0.9):] def write_list(seg_list, out_path): with open(out_path, w) as f: for seg in seg_list: for fname in segments[seg]: f.write(images/ fname \n) write_list(train_seg, train.txt) write_list(val_seg, val.txt) write_list(test_seg, test.txt)这里的seed42是固定随机种子保证每次运行结果一致。如果你数据量不大但像素质量高可以用20%做验证不用再额外分测试集。关键点是训练和验证不能来自同一段水下视频否则模型只是背下了背景纹理遇到新场景照样抓瞎。3.4 四个边界问题负坐标、超界、空标注、中文路径这四个问题我几乎每次帮同事调数据集都会遇到单独列出来。负坐标XML里xmin可能标记为-5直接除以宽高会得到负的归一化值。解决方法是钳制到0而不是丢弃整个框因为目标主体通常还在图像内。如果框一大部分在图像外再丢掉也不迟。超界坐标xmax可能大于图像宽度导致归一化值大于1。用min(xmax, img_w)处理。如果没有这一步YOLO训练时会因为坐标超出图幅而出现NaN loss。空标注转换完成后的txt如果是零字节会在训练时被忽略但如果这类样本太多会造成正样本不足。排查方式是统计标签目录里os.path.getsize(path) 0的文件再决定是补标还是删除。中文路径如果整个数据集放在D:\水底检测\数据集\这种中文路径下OpenCV和YOLO的某些组件在Windows上会读不到图或者图片解码失败。这不是玄学是编码问题。解决办法是把所有目录统一改成英文路径不要带空格和特殊符号。4. 训练实操YOLOv8跑通水下检测的配置、命令与增强参数4.1 data.yaml路径写错是第一个坑YOLOv8的训练入口不是直接传图片目录而是通过一个data.yaml描述数据。这个文件必须和数据集放在一起或者路径指向它。我用得最顺的配置如下path: /home/user/underwater_dataset train: images/train val: images/val test: images/test nc: 5 names: 0: fish 1: crab 2: jellyfish 3: plastic 4: netpath是整个数据集根目录的绝对路径。train和val是相对于path的目录不是文件列表。如果你的目录布局是train/images和val/images需要把train写成train/images。很多人在这里把路径写成图片文件路径导致训练时找不到图片。另一个问题是path写成相对路径时命令行的工作目录改变了就容易失效。我建议统一用绝对路径。如果你在服务器上跑可以用pwd确认当前目录再把path设成实际绝对路径。4.2 训练命令从n规模开始别一上来就large水下目标检测的样本量通常不大直接上yolov8m或yolov8l容易过拟合。我会先用n或s这个规模跑通流程再根据mAP决定要不要增大模型。yolo detect train \ dataunderwater.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ projectruns/underwater \ nameexp_n参数说明modelyolov8n.pt表示从预训练权重开始而不是随机初始化。预训练权重在COCO上学到的特征对水下边缘和纹理也有迁移价值。imgsz640是输入分辨率水下小目标多时可以换成960或1280但显存和训练时间会上升。batch16是单卡批大小如果遇到OOM降到8或4。训练过程中终端会每轮打印一次loss和速度重点看box_loss和cls_loss有没有稳步下降。如果loss震荡很厉害很可能前面已经提到过的标签类型错位问题。训练结束后runs/underwater/exp_n/weights/best.pt就是验证集上mAP最高的权重。如果你用的是YOLOv5命令也类似只是配置文件格式相同入口脚本是train.py。两者在数据组织上没有本质区别这份数据集完全可以同时供v5/v8使用。4.3 水下偏色场景的增强调整水下图像普遍偏蓝绿红色通道很弱鱼类和岩石的反差低。如果直接训练模型容易把颜色当成主要特征遇到背景变化就失效。YOLOv8自带HSV增强可以针对这个场景做调整。yolo detect train \ dataunderwater.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ hsv_h0.02 \ hsv_s0.8 \ hsv_v0.5hsv_h0.02让色相在很小范围内扰动避免把鱼的品种颜色改变得太夸张。hsv_s0.8是饱和度增强让模型适应不同水深的颜色浓淡。hsv_v0.5是明度扰动模拟水下光照变化。除了内置增强实际项目中还有一个很朴素的技巧对训练图像做灰度化或CLAHE对比度增强作为第二份数据混进去。水下图像对比度低CLAHE能明显提升边缘可见度。但注意别把所有图都做强增强否则训练分布和真实场景差别太大验证时mAP会掉。5. 避坑指南水下目标检测最容易翻车的五个细节5.1 类别从1开始mAP直接崩现象训练loss正常下降验证loss也正常但最终mAP0.5只有0.2左右预测结果几乎全是错的。原因标注工具或转换脚本里类别编号从1开始没有为背景留出0。YOLO默认0是第一个真实类别如果0被设置成背景模型学到的目标和真实标签错位。解决打开classes.txt确认第一行不是background或空行。如果有用脚本把所有标签的类别编号整体减1再检查种类数是否还等于nc。5.2 小目标几乎全漏现象训练后对大目标能检出但距离远、面积小的物体一个都检不出来recall很低。原因水下相机分辨率高但目标很小640分辨率下5像素宽的目标只有不到5个特征点模型很难学到有效特征。解决把imgsz调到1280或者用SAHI切片推理。我在训练时还习惯把conf_thres从0.25降到0.1同时提高iou_thres到0.6让模型更愿意给出低置信度框再用NMS合并。5.3 漏标注导致训练集被污染现象训练过程中cls_loss在某个epoch后突然上升验证集precision很高但recall很低。原因数据集中部分图片只标注了水母漏了旁边螃蟹。YOLO会把这部分漏掉的区域当成背景反向传播时告诉模型这里不是目标从而把原本正确的检测抑制掉。解决这个坑最麻烦。我一般先用预训练模型跑一遍所有训练图生成候选框再对照原标注找出那些有高置信度但没标签的区域。把明显的漏标框补回去宁缺毋滥也不要让背景里有大量未标注目标。5.4 模型学到蓝色而不是物体现象验证集上出现大量边界框在空白水域的误报precision极低。原因水下图像背景单一模型偷懒学到了有特殊纹理的蓝色区域就是鱼这种捷径而不是关注物体边缘。解决在预处理中加入灰度图或随机色彩扰动削弱颜色捷径。也可以做简单的背景分割把纯蓝背景区域增强成深灰强迫模型依赖形状特征。这个操作在生态环境类目标检测里很管用。5.5 数据处理不当导致分数虚高现象训练集mAP很高一换到没见过的视频上就崩。原因随机划分时同一段水下视频的连续帧同时进了训练集和验证集时序上的相似性让验证集失去了盲测意义。解决按视频段划分数据确保同一段视频的所有帧只出现在一个集合中。你可以在文件名前缀里找到视频段ID如果没有前缀用光流法估算帧相似度再聚类划分。这一步对做工业数据集评估尤其重要。6. 验证与提效把mAP、混淆矩阵和TTA用起来6.1 先跑一次val看四个指标训练结束后不要只盯着训练loss。我习惯立刻用验证集跑一次独立评估yolo detect val \ dataunderwater.yaml \ modelruns/underwater/exp_n/weights/best.pt \ imgsz640输出里最值得看的是mAP0.5和mAP0.5:0.95同时看precision和recall。mAP0.5是IoU阈值为0.5时的平均精度适合快速判断模型有没有基本检测能力。mAP0.5:0.95更严格如果它比前者低很多说明框的位置还不够准需要继续调回归头或训练轮数。6.2 混淆矩阵不只是看对角线验证完成后runs/underwater/exp_n/confusion_matrix.png会给出每个类别的预测分布。不要只看对角线重点看哪两个类别互相混淆。水下场景里螃蟹和塑料瓶容易互相误判因为颜色和纹理相似。如果发现这一类混在一起说明标注样本里类别间特征区分度不够需要补充更多难例而不是盲目调模型。6.3 用TTA和自动标注把效率提上去TTA是最后一招提速工具。把每张测试图做水平翻转、多尺度缩放再对预测结果取平均通常能提升1-3个mAP点代价是推理时间翻倍。yolo detect predict \ modelruns/underwater/exp_n/weights/best.pt \ sourcetest_images/ \ imgsz960 \ augmentTrueaugmentTrue会启用TTA适合离线处理关键测试集。我还会把预测结果中的高置信度框转换成YOLO标签生成一份新的txt用于半自动标注。这些粗标数据等人工修正后再加入训练集相当于用模型自己帮自己扩充样本。之前我拿到一份水下数据集直接拿默认参数训练结果mAP不到0.15后来挨个查才发现是类别映射和漏标注叠加的问题。从那以后我每次拿到新水下数据集都强制先走一遍看classes.txt - 统计分布 - 画图抽查 - 检查空标签的流程再开始训练。这个小习惯帮我在工业项目里少熬了无数个夜。希望帮到你。本文还有配套的精品资源点击获取
返回列表