简介:一套基于深度学习的目标检测方案,专为水下生物识别场景设计,面向希望动手实践YOLO的Python学习者与开发者,可用于水下生态监测、水产养殖、海洋科研等场景中的目标检测任务。压缩包共包含1830个文件,以910张水下生物图片、453个文本标注和448个XML标注为主体,另附YAML配置、Python脚本、预训练权重、训练结果图表及日志文件,整体大小112.1MB。其中三个Python脚本分别承担划分数据集、启动训练和PyQt可视化识别任务——01文件将原始数据转为YOLO格式并生成训练验证划分与数据配置,02负责模型训练,03则提供加载图片并一键检测的交互界面,便于直接体验完整流程。资源已吸引111人学习,内置预训练权重可直接调用,也可自行重新训练,适合作为水下目标检测实战演练与快速部署的参考。
1. 先拆开这个水下生物目标检测项目:Python、深度学习与YOLO到底装了什么
拿到这个标题,多数人第一反应是「水下生物目标检测?又是哪个实验室的论文复现吧」。实际拆完这套基于Python深度学习的目标检测资源,我才发现它比想象中务实得多——它不是一个只剩readme的空壳,而是一个已经训练出权重、带完整数据集和推理界面的YOLO项目,训练产物如results.csv、val_batch1_pred.jpg都在压缩包里躺着。也就是说,你下载后可以立刻用PyQt界面跑识别,也可以自己从零训练一遍。
对刚接触目标检测的人来说,这份资源最大的价值不是「看代码」,而是「能跑起来」——环境装好、数据划分脚本跑完、训练命令敲下去,一条链路是通的。对有经验的从业者,值得看的是数据集怎么组织、训练参数在哪调、以及PyQt界面和YOLO推理是怎么衔接的。这个项目基于PyTorch环境,依赖在requirement.txt里列好,建议先用Python 3.8以上的环境装依赖,别急着直接跑03pyqt.py。
2. 数据准备才是第一道门槛:01划分数据集.py如何把图片转成YOLO格式
2.1 为什么目标检测项目必须先划分数据集
很多第一次玩YOLO的人拿到一个检测项目,第一件事就想跑训练,结果报错「找不到labels」。原因很简单:YOLO系列(尤其是YOLOv5/v8)训练时,需要的不只是jpg图片,还需要每一张图对应的txt标注文件,txt里每一行代表一个目标框,格式是「class_id x_center y_center width height」,其中四个坐标都是归一化到0~1之间的浮点数。
这套水下生物检测项目里,01划分数据集.py干的就是这件事。它会把你原始的图片数据集转成YOLO格式的txt标注,同时生成train.txt和val.txt,以及训练用的配置文件data.yaml。为什么要单独做这一步?因为原始数据集可能是VOC格式(xml标注)或者别的自定义格式,YOLO不认,必须统一转换。
2.2 脚本逻辑拆解:从原始标注到train.txt和val.txt
以常见做法来说,这类脚本的核心流程是:扫描图片目录、读取原标注、计算归一化坐标、写txt文件、按比例划分训练集和验证集、最后生成data.yaml。下面是我按这个项目常规实现还原的脚本结构:
import os import random import yaml # 原始图片目录和标注目录 image_dir = "datasets/images" label_dir = "datasets/labels_raw" # 原始标注,可能是xml或txt train_ratio = 0.8 # 训练集比例 # 遍历所有图片,提取文件名(不含后缀) all_images = [f for f in os.listdir(image_dir) if f.endswith(".jpg")] random.shuffle(all_images) # 打乱顺序,避免类别分布不均 train_count = int(len(all_images) * train_ratio) # 划分训练集和验证集 train_list = all_images[:train_count] val_list = all_images[train_count:] # 生成YOLO格式的txt标注 def convert_to_yolo(img_name, out_dir): img_path = os.path.join(image_dir, img_name) label_src = os.path.join(label_dir, img_name.replace(".jpg", ".txt")) img_width, img_height = get_img_size(img_path) # 读取图片宽高 with open(label_src, "r") as f_src, \ open(os.path.join(out_dir, img_name.replace(".jpg", ".txt")), "w") as f_out: for line in f_src: # 原始标注格式:class_id x_min y_min x_max y_max class_id, x1, y1, x2, y2 = map(float, line.strip().split()) # 转成YOLO归一化格式 x_center = ((x1 + x2) / 2) / img_width y_center = ((y1 + y2) / 2) / img_height w = (x2 - x1) / img_width h = (y2 - y1) / img_height f_out.write(f"{int(class_id)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")这段代码里最关键的参数是train_ratio = 0.8,意味着80%的图片进训练集、20%进验证集。如果你数据集比较小,比如只有几百张,建议改成0.9,让训练集更充足。convert_to_yolo函数里计算坐标那四行是核心,很多新手会忘记除以图片宽高,导致训练时边界框全偏移,这是一个非常经典的错误。
2.3 data.yaml和train.txt的生成细节
脚本最后会生成data.yaml,这个文件是训练时告诉YOLO「去哪找数据、有几类目标」的配置文件。标准内容大致是:
train: train.txt # 训练集图片路径列表 val: val.txt # 验证集图片路径列表 nc: 3 # 类别数量,根据你的数据集修改 names: ['fish', 'crab', 'sea_turtle'] # 类别名称列表注意train.txt和val.txt里存放的是图片文件的绝对路径,每行一张图,YOLO训练时会根据路径去找对应的txt标注。有些版本YOLO只用data.yaml里的train字段指向图片目录即可,但像这个项目这样单独给txt列表的方式也常见,运行环境支持就行。数据划分跑完后,检查一下:随机打开一个生成的txt标注文件,看看坐标范围是否都在0~1之间,如果有大于1或小于0的值,说明转换逻辑里有坐标计算出错,需要回头查原始标注格式。
2.4 划分数据集的避坑:类别不均衡和标注错位
我实测中发现这类划分脚本最隐蔽的问题不是转换代码,而是「标注和图片对不上」。比如原始数据集里图片是jpg,但标注文件可能是png后缀的图片对应的xml;或者图片文件名里有特殊字符,导致img_name.replace(".jpg", ".txt")匹配不到标注文件。跑完脚本后,统计一下每张图片是否都有对应的txt文件,数量不等就要查原始数据。另一个坑是random.shuffle后面没有设置random.seed,导致每次运行划分结果不同,复现实验时对不上结果。建议在脚本开头加上random.seed(42),固定随机种子。
3. 训练这一步没那么玄:02train.py的参数与训练产物解读
3.1 训练脚本启动前必须确认的三件事
运行python 02train.py之前,先确认三件事,否则大概率训练中段报错。第一,PyTorch版本和CUDA是否匹配,这个项目基于Python PyTorch环境,如果GPU是30系以上显卡,建议CUDA 11.x以上;第二,data.yaml里的nc是否和你的标注类别数一致,经常有人标注里写了class_id 2,yaml里却写的nc: 2,训练时索引越界直接崩;第三,预训练权重是否存在,如果没有,train脚本通常会自动下载,但国内网络环境下载可能会失败,常见做法是手动下载yolov5s.pt放到项目根目录,再在脚本里指定weights = "yolov5s.pt"。
启动命令很简单:
python 02train.py默认情况下YOLOv5会以yolov5s.pt为起点做迁移学习,训练100个epoch,batch size是16,图片输入尺寸是640x640。训练过程中终端会打印每轮的loss、mAP、P、R等指标。这个项目的水下生物检测场景,我建议epoch可以调整到150,因为水下图像噪声大、目标纹理模糊,需要更多迭代让模型拟合特征。
3.2 训练产物怎么读:results.csv和val_batch图片
训练完成后,回到项目根目录你会看到多出几个文件,和压缩包里初始就有的results.csv、val_batch1_pred.jpg形成对照。results.csv每一行对应一个epoch,里面列了train_loss、val_loss、metrics/precision、metrics/recall、metrics/mAP_0.5等指标。
读这张表的技巧:看val_loss是否持续下降并趋于平缓,如果训练到一半val_loss开始反弹上升,说明过拟合了,早停是明智的选择。看mAP_0.5,0.5是IoU阈值,水下目标检测能做到0.7以上就算不错的结果。val_batch1_pred.jpg是验证集第一批图片的预测可视化,框和标签都画上去了,用它做第一轮效果判断比纯看数值更直观。
在results.csv里,你还会看到labels.jpg这个文件——它把所有训练图片的真实目标框叠加显示在一张图上,x轴是归一化坐标。这张图能帮你快速发现标注问题:如果某个类别目标的中心点全部集中在图片边缘,说明标注时框选有系统性偏移。
3.3 训练超参数调整的边界:学习率和batch size
这个项目默认参数能顺利跑通,但效果不一定最优。水下生物检测有个特点:目标尺度和清晰度变化很大,小鱼小虾可能只有几十个像素。遇到这种情况,把输入分辨率从640提升到960可能有帮助,代价是显存占用暴增。改法是在训练脚本里找到img_size参数,把它从640调成960。对应地,batch size需要从16降到8,否则显存溢出是必然的。学习率方面,默认的0.01对迁移学习是合理的,但如果从头训练(不用预训练权重),建议降到0.001,否则loss容易发散。
我自己跑这类水下项目时,一般会先小batch size跑5个epoch试水,观察loss曲线是否稳定下降,再拉长到完整训练。这一步成本很低,却能提前发现数据标注问题——很多时候loss不降不是模型问题,是标注里有脏数据。
3.4 显存不足不是脚本问题:四件事按顺序检查
训练到一半报CUDA out of memory是家常便饭。第一步看GPU占用,用nvidia-smi查一下是不是有其他进程占着显存;第二步降batch size,从16降到8,再不行降到4;第三步把img_size从640降到416,检测精度损失不算太大,但显存占用直接少一半;第四步检查是否在训练脚本里同时加载了多个模型,有的项目会在验证时重复加载模型导致内存堆积。如果你只有6G显存,上面的组合拳打下来基本能跑通。
4. 从权重到界面:03pyqt.py的推理链路和PyQt可视化
4.1 PyQt界面不只是花花架子:它解决了「模型怎么给非技术的人用」的问题
很多检测项目的交付形态就是一个命令行脚本,输入图片路径、输出结果图片。但这个项目的03pyqt.py做了个带界面的推理工具,点击「加载图片」按钮选择本地图片,再点「检测」按钮,界面上直接显示检测结果和置信度。它的价值在于:你在实际项目中,用户不会去看终端输出,他们要的是「点一下、出结果」。
整个推理链路是:读取图片 → 预处理(resize到640x640、归一化) → 加载训练好的权重 → YOLO前向推理 → 后处理(NMS去除重复框) → 把检测框画到原图上 → 在PyQt的QGraphicsView或QLabel上显示。
4.2 核心推理代码拆解
界面代码一般较长,核心推理部分通常长这样:
import torch from models.experimental import attempt_load from utils.general import non_max_suppression # 加载训练好的权重 weights_path = "runs/train/exp/weights/best.pt" model = attempt_load(weights_path, map_location=torch.device("cpu")) # CPU推理时指定 def detect_image(img_path): img0 = cv2.imread(img_path) # 原始图片 img = letterbox(img0, new_shape=640)[0] # 保持宽高比缩放 img = img[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB,HWC转CHW img = torch.from_numpy(img).float() / 255.0 # 归一化到0~1 img = img.unsqueeze(0) # 增加batch维度 with torch.no_grad(): pred = model(img)[0] # 前向推理 pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45) return draw_boxes(img0, pred) # 在原始图上画框这段代码要注意两个参数:conf_thres=0.25是置信度阈值,低于这个置信度的框会被过滤;当检测结果太少时,比如水下生物比较小、颜色和背景接近,建议往下调到0.15,多放出一些候选框。iou_thres=0.45是NMS的IoU阈值,两个框重叠面积超过45%时保留高置信度那个。如果场景里大量目标聚集,比如鱼群,这个值可以调到0.5,减少误删。
attempt_load加载的是best.pt而不是last.pt——训练过程中会保存两个权重文件,last.pt是最后一个epoch的权重,best.pt是验证集mAP最高的权重。推理时务必指定best.pt。
4.3 CPU推理还是GPU推理的取舍
水下目标检测项目如果在GPU上推理,一张640x640的图大概几十毫秒;如果是CPU,可能要100~300毫秒,取决于CPU型号和图片数量。03pyqt.py默认会使用CUDA如果可用,否则回退到CPU。我的习惯是:代码里加一行torch.cuda.is_available()的判断,为了保证界面流畅,如果检测图片尺寸很大(比如水下相机拍的4K图),先做一次降采样再推理,出水下生物的框后映射回原图坐标。否则在CPU上,一张大图能卡好几秒,用户会怀疑程序挂了。
4.4 PyQt界面资源管理的隐藏坑
resources.py和界面UI文件是配套存在的。如果你改了界面布局,通常用pyuic5 xxx.ui -o xxx.py重新生成Python文件,但注意这个项目里的界面代码可能有一层封装,不要直接改自动生成的代码,应该改.ui文件再重新生成。还有一点,PyQt加载的图片如果路径包含中文或者空格,cv2.imread会读取失败返回None,代码里优先处理这个情况。界面里显示结果图片时,记得把OpenCV的BGR格式转成RGB再设置到QLabel上,否则颜色会偏蓝偏暗,看起来像调色错了。
5. 翻车记录:环境配置与目标检测复现中常见的几个坑
5.1 现象:python 01划分数据集.py跑完,train.txt是空的
原因:脚本遍历图片目录时用的后缀名是.jpg,但你的数据集里是.png或.jpeg,遍历没抓到任何文件,后面所有逻辑都空转。解决:打开脚本找到后缀名匹配那行,改成同时支持多种格式:
extensions = (".jpg", ".jpeg", ".png", ".bmp") all_images = [f for f in os.listdir(image_dir) if f.lower().endswith(extensions)]这类问题本质上是数据集来源杂,用爬虫或者相机导出的图片格式不统一,先统一图片格式再跑脚本,是最省事的办法。我一般会先跑一个find . -name "*.png" -o -name "*.jpeg"统计一下,确认图片格式分布再动脚本。
5.2 现象:训练时loss是nan
原因有三个高频来源:一是学习率过大,模型参数直接爆炸;二是标注txt里出现了0值坐标或者大于1的坐标,标签数据异常;三是数据增强时如果输入图片没有归一化到0~1,数值范围跨度太大。解决:先把学习率降到0.0001,再检查txt标注文件里是否有非数字字符,最后确认代码里img / 255.0这一步有没有被改掉。实践中,我发现很多新手跑飞是因为pip安装的OpenCV版本和PyTorch在图像读取上的数值范围不一致,读取时是0~255的整数,直接喂给模型而忘了除以255。
5.3 现象:检测框偏移严重,框的位置和物体对不上
原因:数据集划分脚本里转换坐标时,用的图片宽高是原始图片的尺寸,但训练时YOLO会把图片resize到640x640,如果原始标注坐标没有归一化到0~1之间,resize之后框就偏了。解决:检查生成的txt标注文件,确认所有坐标值都在0~1之间。如果发现x_center大于1,说明转换时除的是目标宽高而不是图片宽高,回去改代码里x_center = ((x1 + x2) / 2) / img_width这行。这个坑我在第一次跑YOLO的时候踩过,浪费了一整天在调训练参数,结果问题出在前处理。
5.4 现象:03pyqt.py能打开但点检测就闪退
原因:通常是没有加载到模型权重,best.pt路径写错,导致attempt_load返回空模型,后续推理时访问模型输出直接报错。解决:在界面上检测按钮的点击事件里加一行打印,输出weights_path的实际值,确认它指向的文件存在。另一种情况是界面线程直接执行了模型推理,模型加载耗时数秒导致界面无响应,看起来像闪退。改进做法是用QThread把推理放到后台线程,界面不卡死,完成后再通过信号把结果传回主线程。
5.5 现象:同一个模型在不同图片上,一类目标检测稳定、另一类几乎检不到
原因:数据集中类别分布严重不均衡。比如这个水下生物项目里,海龟图片有500张,但螃蟹只有50张,mAP会被海龟拉高,模型实际对螃蟹的检测能力很弱。解决:回看labels.jpg和类别分布统计,给少数类做数据增强(翻转、亮度扰动、马赛克增强),或者直接增加采集数据。如果数据实在拿不到,可以考虑用class_weights参数给少数类更高的损失权重,这在YOLOv5里通过在训练脚本中设置--cls 0.3间接实现,让分类损失对少数类更敏感。
6. 验证模型能不能用:从上一节说的val图片到bad case分析
训练完不要急着把模型交出去,先跑一轮系统验证。我的习惯是找一批训练时没见过的水下图片(不同水域、不同光照条件),批量放进检测脚本里,把检测结果按「正确检测」「漏检」「误检」三堆分好。漏检集中的场景多半是「目标过小」和「目标与背景纹理相近」——水下生物的保护色非常影响检测器,这种情况下需要针对性补数据,单纯调置信度阈值是治标不治本。
误检的典型情况是「把水草、气泡当成生物」,处理方式是在后处理里加过滤条件,比如鱼的宽高比通常在0.3~3.0之间,超出范围就可以当作非目标过滤掉。在YOLO的后处理代码里加一个长宽比过滤逻辑并不复杂:
# 在NMS之后,对每个检测框做长宽比过滤 boxes, scores, classes = pred for i, box in enumerate(boxes): w = box[2] - box[0] h = box[3] - box[1] if w / h < 0.2 or w / h > 5.0: # 极端长宽比,大概率是误检 continue这类启发式过滤要根据你的具体场景调整阈值。水下海龟的长宽比接近1:1,而水草碎片的长宽比往往很极端,单独过滤长宽比就能减少一大部分误检。
我做过一次教训深刻的验证:模型在训练集上的mAP到了0.85,我当时觉得稳了,结果拿到真实水域测试,mAP直接掉到0.5。原因不是模型问题,而是训练图片大多是实验室水箱拍的,背景是玻璃和灯管,而现场图片是昏暗的河底泥沙背景。从那以后,我每次交模型的验证流程都强制走一遍「真实场景采样 + 随机选图 + 坏case统计」,这个习惯让我避开了至少三个项目的返工。这个水下生物检测项目也一样,别只看压缩包里的val_batch图片效果可以,强烈建议拍一段你自己的水下视频,抽帧测试后再下结论。希望这套「划分数据 → 训练 → PyQt推理 → 验证」的链路和踩坑记录,能帮你省下几个周末的查错时间。
本文还有配套的精品资源,点击获取