资讯动态

水下生物目标检测实战:Python+YOLO从数据集到训练全流程

发布时间:2026/10/5 2:55:55 来源:尧图企业网站定制
简介一份面向水下生物目标检测的Python深度学习资源基于YOLO框架与PyTorch环境适合有基础目标检测知识的开发者和研究者用于学习、训练及快速验证。资源共1830个文件压缩包约112.1MB其中包含910张jpg训练图像、448个xml原始标注和453个txt格式标签以及3份yaml配置、3个py脚本和3个pt权重文件可满足从数据划分、模型训练到PyQt图形界面识别的一整套流程。yaml文件定义了训练参数与类别信息pt权重可直接用于推理或迁移学习csv训练日志则方便对比指标并分析收敛情况。配套提供清晰的文件结构与缓存、预测可视化图片便于查看训练效果与错误样例。目前已有112人学习下载尤其适合需要直接体验水下生物检测完整链路并基于YOLO进行二次开发的读者。1. 水下生物目标检测从数据集到训练代码为什么Python和YOLO能直接上手做水下生物目标检测的团队十有八九不是搞算法的而是搞渔业的、搞生态监测的、搞潜水机器人的。你手里可能是一堆水下摄像机拍回来的视频一个池子几百条鱼人工看录像数数能数到怀疑人生。这就是这个标题要解决的问题用Python搭一套深度学习目标检测管线输入水下图像或者视频帧输出每个生物的位置框和类别顺带把数据集和代码都给你备齐不用自己从零标几百张图。适合谁两类人。一类是刚接触目标检测的Python用户想用现成数据集练手跑通一个完整的检测流程另一类是实际项目里被水下数据集搞烦了的工程师——水下数据不比陆地上光线烂、背景花、目标小直接用通用模型往往翻车。这个标题的落地路径很清晰准备数据集、整理标注格式、训练检测模型、评估效果、把模型用到视频推理上。下面我就按这条链路把能复现的部分一步步拆开。2. 水下数据集长什么样标注格式整理与类别筛选2.1 先认清你要检测的“水下生物”是哪几类水下目标检测和通用目标检测最大的差异在数据侧。常见的水下数据集包括鱼类、海参、海胆、扇贝、螃蟹等这些类别在标注时往往存在“同一个东西不同视角完全两个样”的情况鱼侧视是一条梭形俯视就是一条细线海参在泥沙背景上几乎和石头同色。所以拿到数据集后的第一步不是急着训模型而是把类别清单拉出来看每类的样本数和标注框质量。这一步常见做法是用脚本统计标注文件里每个类别的出现次数和标注框面积分布。如果某个类只有几十张图后续训练基本学不好要么放弃该类要么做数据增强补样本。还有一个容易被忽视的点水下数据集的图像尺寸通常不统一有些是1920x1080的截图有些是ROV采集的4K单帧直接丢给模型会导致训练时按长边缩放产生大量形变。2.2 VOC格式和YOLO格式互转标注文件处理脚本多数公开水下数据集给的是VOC格式的XML标注而YOLO系列训练需要的是TXT格式每行一个目标内容是“类别id x_center y_center width height”坐标全部归一化到0到1之间。转换时最常出错的不是坐标计算而是“格式名义上转了但坐标除以的是原图尺寸不是缩放后的尺寸”。下面这个脚本我一般直接放在项目根目录跑完不清理后续每次重新整理数据都用它。import os import xml.etree.ElementTree as ET from glob import glob def voc_to_yolo(xml_file, class_names, out_dir): tree ET.parse(xml_file) 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_names: continue # 跳过不在类别清单里的标注 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) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 防止标注越界导致训练时loss爆炸 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{class_names.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, os.path.basename(xml_file).replace(.xml, .txt)) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) return len(lines) # 用法把VOC XML目录下所有文件转到YOLO目录 # class_names顺序就是模型训练时的类别id务必和最终训练配置一致这段代码的关键有两处一是class_names列表的顺序直接决定每个类别在模型输出里的id一旦训练中途改了顺序之前转好的标注全部作废这是很多人训练到一半发现loss不降的隐藏原因。二是坐标归一化前做了上下界截断水下标注经常出现框画到图像边缘外的情况UDP、YOLO系列训练时遇到负数坐标会直接报错提前防御比后面排查省事得多。2.3 数据集划分不是随机切而是按“来源”切很多人在这个环节翻车。水下数据集往往是按视频片段组织的同一段视频的连续帧高度相似如果随机划分train/val那些高度相似的帧会同时出现在训练集和验证集里验证集mAP虚高。等模型部署到新环境时面对从没见过的水流、光照、泥沙背景精度断崖式下跌。正确做法是按视频来源或者采集环境划分同一个视频片段的所有帧必须进同一个集合。import os import random from glob import glob def split_by_source(image_dir, label_dir, train_ratio0.8): # 假设文件名格式: source_123_frame_0345.jpg按source前缀划分 image_paths glob(os.path.join(image_dir, *.jpg)) source_map {} for img_path in image_paths: filename os.path.basename(img_path) source_id filename.split(_)[0] # 按来源前缀分组 source_map.setdefault(source_id, []).append(img_path) train_list, val_list [], [] for source_id, paths in source_map.items(): random.shuffle(paths) split_idx int(len(paths) * train_ratio) train_list.extend(paths[:split_idx]) val_list.extend(paths[split_idx:]) return train_list, val_list我在实际项目中吃过这个亏第一次训练时mAP到0.92自己以为稳了结果放到实拍视频里几乎找不到鱼后来定位到问题就是数据划分没按来源分验证集本质上是在“开卷考试”。如果你手头数据来自多个不同的水下摄像头最好训练前直接打印一下每一帧的来源分布别急着开训。3. 模型选型与训练启动YOLO系列做水下检测的适配经验3.1 为什么首选YOLO系列而不是Faster R-CNN或DETR水下生物检测的最大特点是目标多、目标小、遮挡严重。Faster R-CNN在精度上有优势但检测速度在嵌入式设备上根本跑不起来DETR这类Transformer检测器对GPU显存要求高且小目标检测仍处于弱势。YOLO系列尤其是v8和v11这一代在速度和精度的平衡上最适合这个场景而且ultralytics库把数据加载、训练、验证、导出都封装好了适合0基础纯小白启动。如果你的机器只有CPU我也建议先装ultralytics跑通最小流程再考虑要不要换模型YOLO的CPU训练慢但能跑验证数据集准备有没有问题。另一个在选型时要考虑的点是模型体量水下场景建议优先尝试yolov8s和yolov8myolov8n对水下小目标过于勉强x和l体量在GTX 3060级别GPU上训练时间偏长日常迭代不方便。3.2 ultralytics环境配置Python版本和依赖装完先跑一个最小验证安装ultralytics需要Python 3.8到3.11之间太新的Python版本比如3.12、3.13会导致依赖的PyTorch轮子不全很多新手在这一步就卡住了。装完后别急着训练先跑一个最小推理脚本验证环境没问题# 创建环境并安装依赖国内用户用清华源加速 pip install ultralytics torch torchvision --index-url https://pypi.tuna.tsinghua.edu.cn/simple装完后跑一段最小推理from ultralytics import YOLO # 加载官方预训练权重第一次运行会自动下载到当前目录 model YOLO(yolov8n.pt) # 用一张普通图片验证推理流程正常 results model.predict(test.jpg) results[0].save(output.jpg) print(环境正常检测到, len(results[0].boxes), 个目标)这里的逻辑是先验证模型能跑通再替换成自己的数据集。如果你的机器下载预训练权重很慢可以手动下载yolov8n.pt放进当前目录代码会直接读取本地文件跳过下载。如果连首张图片推理都报错多半是CUDA版本和PyTorch不匹配而不是你的数据有问题。3.3 数据配置YAML水下场景最容易配错的三个字段使用ultralytics训练时需要一个data YAML文件里面声明训练集、验证集路径和类别名。水下项目里最常见的坑有三处。第一个是路径写成相对路径训练时工作目录一动就找不到数据建议直接用绝对路径。第二个是类别名顺序和转换标注格式时的class_names顺序不一致模型训练完类别名错位比如“fish”变成了“sea_cucumber”。第三个是忘记设置val字段有些版本会默认用训练集做验证最终打印的mAP完全不可信。下面是一个标准的水下检测数据配置文件# underwater.yaml path: /home/user/underwater_dataset # 数据集根目录绝对路径 train: images/train # 相对path的路径 val: images/val nc: 5 names: 0: fish 1: sea_cucumber 2: sea_urchin 3: scallop 4: crab注意事项nc是类别总数要和names里的条目数完全一致names是dict格式不是list0开头的编号写全训练时日志会输出每个类别的AP检查这个输出和你的预期类别是否能对上这是发现问题最快的方式。3.4 启动训练的推荐参数组合YOLO训练可以直接用命令行也可以用Python脚本。命令行方式适合快速迭代Python脚本适合你要在训练前后插入自定义复杂逻辑时用。先给命令行版本# 用yolov8s预训练权重迁移学习训练100轮 yolo train modelyolov8s.pt dataunderwater.yaml epochs100 batch16 imgsz640 patience15如果显存不够把batch降到8或者4如果图像里目标较小把imgsz提到960或1280能带来明显的小目标recall提升但训练时间相应增长。patience是早停参数验证集mAP连续15轮不涨就自动停止能省不少时间。用Python脚本训练时逻辑会更可控from ultralytics import YOLO model YOLO(yolov8s.pt) # 加载预训练权重 model.train( dataunderwater.yaml, epochs100, batch16, imgsz640, patience15, workers4, # 数据加载线程数Windows下建议设为0避免报错 device0, # 使用第一张GPUCPU训练填cpu pretrainedTrue, # 迁移学习保持True这能大幅提升收敛速度 )迁移学习对水下这种“类通用但不完全通用”的场景很关键预训练权重是在ImageNet或COCO上学过的保留了基础纹理和边缘特征水下生物虽然外观特殊但鱼的轮廓、海参的纹理边缘这些低层特征是可以复用的。我自己测试过从零训练和用COCO预训练权重起步同样100轮mAP能差出10个点以上。4. 训练结果评估用mAP和PR曲线判断模型好坏而不是只看loss4.1 先看PR曲线再看mAP最后才看loss很多人训练完只看最后的loss值觉得loss低于1就是好模型。这个习惯在目标检测里容易带偏方向。训练完成后ultralytics会在runs/train/目录下生成results.png、confusion_matrix.png和PR_curve.png。判断模型好坏我一般按这个顺序来先看PR曲线是不是整体往右上角顶起来了然后看验证集上的mAP0.5和mAP0.5:0.95前者反映“检测到大概位置”的能力后者反映“框得准不准”最后才看loss曲线有没有持续下降。水下场景有一个普遍现象值得单独说mAP0.5不低但mAP0.5:0.95很低这说明你的框大概位置找到了但边界框回归不准。原因大多是水下生物的轮廓模糊标注框本身就因人而异另一个原因是imgsz640对小鱼来说像素太少了框不准很正常。4.2 三个必看指标的计算和解读验证输出会在终端打出每个类别的AP这比总mAP更有排查价值。比如某个类别的AP0.5只有0.2别急着调模型先去看这个类别的训练样本数和标注质量。水下数据最容易出现的问题是某类样本数极少且集中在相似的几张图上模型学到的只是“记住图片”而不是“学会检测”。# 用ultralytics的验证接口评估训练好的模型 from ultralytics import YOLO model YOLO(runs/train/underwater/weights/best.pt) metrics model.val(dataunderwater.yaml, splitval) # 打印每个类别的AP for i, class_name in enumerate(model.names.values()): ap50 metrics.box.ap50[i] # 每个类别的AP0.5 ap metrics.box.ap[i] # 每个类别的AP0.5:0.95 print(f{class_name}: AP0.5{ap50:.4f}, AP0.5:0.95{ap:.4f})这里有个实用技巧metrics.box.ap50是按类别顺序输出的顺序和你的data YAML里的names顺序一致。如果输出的类别顺序和你的预期对不上那不是评估代码问题是训练时的类别id映射已经乱了回头去检查数据转换脚本的class_names顺序。4.3 模型欠拟合还是数据问题用训练集评估来分辨有时验证mAP上不去你不确定是模型没有学会还是验证集太难。一个简单的分辨方法是用训练好的模型去评估训练集本身。如果训练集上mAP很高比如0.9但验证集很低这是过拟合优先加数据增强或降低模型复杂度如果训练集本身mAP就低说明模型没学会优先检查数据标注质量再考虑加大epochs或调整学习率。model.val(dataunderwater.yaml, splittrain)不需要额外写代码直接用上面的评估接口。跑一次对比训练集和验证集的mAP差距这个操作能帮你省掉大量盲目调参时间。我在水下项目里遇到最多的情况是训练集mAP也就0.6左右说明标注本身问题很大——框和生物边缘对不齐、类别标错、漏标这些都会把模型上限锁死。5. 水下检测避坑指南五个高频问题的现象、原因和解决5.1 现象训练时loss正常下降验证mAP却始终徘徊在0.3原因最常见的是数据划分不当。前面提过水下数据集按视频帧组织如果随机切分验证集等于在背题loss看着正常但验证集mAP上不去。其次是数据标注框和生物实际边缘偏差过大模型学的是“大概在这里”而不是“精确在这里”。解决先按视频来源重新划分数据确保划分粒度是source级别然后随机抽100张训练图片人和标注框叠加可视化肉眼检查标注质量。如果发现很多框只框住鱼的一半身体需要重新标注或用半自动标注工具修正强行训练只是浪费时间。5.2 现象训练到一半报错“CUDA out of memory”调小batch后仍然报错原因大多不是batch太大而是显存碎片化尤其是你用多个GPU或者训练中途打开了其他程序。另外workers设置太高也会让显存看起来吃紧因为数据加载进程会预取图像到显存。解决先确保workers0Windows下必设再设batch4或batch2测试。如果连batch2都崩检查imgsz是不是被设置得过大水下图像普遍分辨率高imgsz1280在消费级显卡上很容易爆显存降到1024或960通常能解决同时用yolov8s而不是yolov81。5.3 现象模型检测出来的鱼边框比真实鱼大一圈或者偏移到尾巴上原因水下图像中的鱼头和鱼尾特征差异大标注框如果在不同帧里位置漂移有的标注从鱼头开始有的从鱼身中间开始模型学到的就是一个平均位置最终输出框会偏大或偏移。另一个原因是对图像做了不合适的Mosaic数据增强鱼被切到不同图块后标注信息被扭曲。解决检查训练配置里mosaic参数如果数据集目标较大且你的验证结果偏移严重把mosaic设为0.0或0.5试试。同时用标注可视化工具检查同一类别在不同帧里的标注一致性漂移明显的标注帧直接删除不要带病训练。5.4 现象训练正常单张图片推理也正常但跑视频时每隔几十帧就漏掉一群鱼原因水下视频单帧之间运动模糊严重鱼游动速度快时拖影导致目标外形在某一帧和正常形态差异巨大。模型在静态图片测试集上没怎么见过运动模糊样本一到视频推理就崩。解决准备约200-300帧含明显运动模糊的视频帧用训练好的模型做伪标注自动标注后人工修正把这些帧加入训练集重新微调模型。这个技巧我反复用比调整任何超参数都管用。5.5 现象训练数据只有3类鱼模型却把海藻、石头也框出来原因这不是模型的错是背景中某些纹理和鱼的纹理相似模型学到了错误的判别特征。水下场景中海藻的摆动和鱼游动的视频帧在单帧静态图上有时真的很难区分。这种问题最容易出现在夜间或浑浊水域因为纹理特征被噪声掩盖。解决最直接的办法是收集“负样本”——海藻、岩石、空荡荡的水面、潜水员等标注为背景类强制模型学习“这些东西不是目标”。YOLO系列部分版本支持纯背景图片不含标注的TXT文件作为额外训练数据把这些图片放到训练集目录里对应label目录放空TXT文件即可。6. 把模型用于视频推理批量跑水下视频并输出可视化结果训练完模型后最常见的落地场景是把模型跑在一个视频上检测每一帧的水下生物并保存结果视频。这一环节的坑也不少视频解码速度、目标框抖动、输出视频体积过大。常见做法是用cv2.VideoCapture逐帧读取视频对每一帧调用model.predict或model(frame)然后把结果可视化后写入VideoWriter。需要注意一点model(frame)的返回值是Results对象真正拿到可视化结果要调用results[0].plot()而不是直接把它当图像用。import cv2 from ultralytics import YOLO # 加载训练好的最佳权重 model YOLO(runs/train/underwater/weights/best.pt) # 打开输入视频 cap cv2.VideoCapture(underwater_video.mp4) fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 输出视频编码用mp4v保证兼容性 writer cv2.VideoWriter( output_video.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height), ) frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break # 对每一帧做检测conf指定置信度阈值 results model(frame, conf0.35, verboseFalse) # plot()返回画好框的图像 annotated_frame results[0].plot() writer.write(annotated_frame) frame_count 1 if frame_count % 100 0: print(f已处理 {frame_count} 帧) cap.release() writer.release() print(视频处理完成输出文件output_video.mp4)这段代码的执行逻辑是逐帧推理并写回输出视频关键的参数是conf0.35这个值决定了模型输出的框的最低置信度。水下场景置信度阈值值得反复调设高了漏检鱼的颜色和背景融合时置信度天然低设低了误检把石头当鱼框出来。我一般会先用0.25跑一遍看误检情况再逐渐调高到0.4左右找到临界点。verboseFalse用来关掉控制台的逐帧打印否则视频跑下来控制台日志刷到怀疑人生。还有一个细节写视频时cv2.VideoWriter的fps参数要尽量和源视频一致否则输出视频的播放速度不对。如果视频分辨率过大比如4K逐帧推理时可以先缩小再推理最后把框映射回原分辨率画出来这一步对速度提升非常明显代价是精度略微降低。找模型阈值时我习惯做一次快速扫描取视频前300帧分别用0.2、0.3、0.35、0.4四个阈值各跑一遍然后对比输出的视频片段选一个既能抓住目标又不至于背景乱跳的值。这个办法看起来原始但比纸上谈兵调参快得多也是我踩过无数坑之后沉淀下来的习惯。如果你刚拿到这个水下检测项目建议也先跑一段10秒的短视频看看输出效果等画质和检测结果都满意了再部署到长视频和实时流上。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑