简介:面向船舶目标检测与监控场景,这套YOLOv5资源提供可直接使用的检测模型、训练权重以及配套船舶数据集,适合目标检测入门者和船舶识别项目开发者参考。数据集部分包含748张jpg原图、742个xml与740个txt标注文件,类别统一为boat,可同时用于VOC和YOLO两种格式的训练流程;代码部分提供31个py脚本、42个yaml配置以及2个pt权重文件,配合pyc、sh等辅助文件,完整覆盖数据准备、模型训练、验证与推理的常用环节。压缩包共2357个文件,大小约101.5MB,并附带训练日志、results.csv指标记录和批次预测可视化样例,便于理解训练过程与效果。目前已有615人学习,结合作者博文中的检测效果参考,可以快速验证模型表现,进而迁移到自有船舶数据做微调,节省从零搭建与调参的时间。整体目录结构清晰,既能作为课程项目实战素材,也可作为业务系统预研的基础工具包。
1. 水域目标检测为什么绕不开YOLOv5
船舶检测和常规目标检测最大的不同,在于画面里目标尺度跨度极大。港区摄像头下,近处的船能占半幅画面,航道远处的船只有二三十个像素,再加上水面波纹、岸线反光和桥墩遮挡,传统背景差分与帧间滤波很难稳住召回率。YOLOv5把CSP结构、PANet和Mosaic增强整合成一套可以直接迁移训练的工程化方案,配合预训练权重做微调,几百张标注图也能在单卡上训练出可用的船舶检测模型。
这份工程包含三样东西:标注好的船舶检测数据集(标签同时提供xml和txt两种格式,类别名统一为boat)、训练好的权重文件、以及YOLOv5-6.0版本完整代码。拿到手就能做数据检查、复现训练和推理验证,适合智慧港口、内河航道监控和海事安防项目做基线,也适合毕业设计直接在此基础上迭代。
2. 船舶样本的难点和YOLOv5-6.0的C3/PANet应对
2.1 船舶样本的尺度跨度与遮挡干扰
船舶目标有一个容易被忽略的特点:长宽比不稳定。货轮细长、拖轮短宽、快艇更接近方形,同一个boat类别里外形差异很大。这会导致锚框聚类时分布比较分散,如果沿用COCO的默认anchor,召回率会明显吃亏。
另一个干扰来自遮挡。港口场景里,船体被岸吊、集装箱、相邻船舶遮挡是常态,船头船尾露出来但船身被盖住的情况经常出现。水面波纹本身也是很强的纹理噪声,会让浅层特征图产生大量假激活。综合下来,船舶检测模型需要同时具备较强的语义特征提取能力,以及不依赖完整轮廓的判断能力,这正是C3和PANet结构擅长的方向。
2.2 C3模块对船舶特征提取的实际作用
YOLOv5-6.0的backbone使用CSPDarknet,核心是C3模块。C3把输入特征图分成两个分支,一个分支经过几个残差子模块提取深层语义,另一个分支直接跳接,最后在通道维度拼接。这个设计有两个实际收益:一是梯度回传路径更短,训练深层的船舶纹理特征不会梯度消失;二是计算量比同深度的ResNet块更小,可以留出算力把输入图像分辨率提高一档。
船舶检测中比较依赖C3的是对船体边缘和甲板纹理的响应。水面反光区域纹理很强,但语义上和一个完整的船体完全不同,C3通过残差结构让高层语义反过来约束浅层纹理响应,减小水面对检测头的干扰。这也是同一张图片用YOLOv5比用早期YOLOv3在近岸场景误检更少的主要原因。
2.3 PANet和三尺度输出如何服务小目标检测
Neck部分使用PANet结构,在FPN自顶向下融合语义信息的基础上,增加了一条自底向上的路径。自底向上路径的作用是保留空间位置信息,让深层特征图也能获得高分辨率的浅层特征补充。远处的小船在浅层特征图上还有响应,但经过几次下采样后可能只剩几个像素,没有PANet这条路径的话,小目标的梯度信号很难传回检测头。
Head部分输出80×80、40×40、20×20三组特征图,分别负责小、中、大目标。每个格点预测5个坐标值加类别数,本工程只有boat一个类别,所以每个格点输出(5+1)×3=18个值。三尺度输出意味着同一个船体可以同时被不同尺度的特征图感知,模型在推理时会根据尺寸自动分配检测头,这种设计在船舶远近尺度跨度大的场景里非常实用。
| 技术模块 | 在船舶检测中的作用 | 常见替代方案 |
|---|---|---|
| C3残差块 | 抑制水面纹理噪声,保留船体语义 | ResNet块、DenseNet块 |
| PANet | 小目标特征回传,提升远处小船召回 | FPN、BiFPN |
| Mosaic增强 | 四图拼接,模拟多船密集场景 | CutMix、CopyPaste |
| 三尺度Head | 远近船体同时检出 | 单尺度检测头 |
提示:如果想在6.0基础上继续提升小目标能力,可以尝试把PANet替换成BiFPN,但要注意这会增加concat权重参数,小数据集上容易过拟合,需要配合更强的正则化项。
3. 船舶数据集的目录组成与XML/TXT标签转换
3.1 数据集目录结构
拿到工程后,第一步不是急着训练,而是把数据集结构摸清楚。常见做法是把图片和标签按功能分目录存放,训练、验证、测试三个集合彼此隔离,避免训练脚本在划分数据时混入重复样本。
datasets/ship/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_001.txt │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... └── annotations/ ├── img_001.xml └── ...图片文件存放在images目录下,对应标签放在labels目录下,文件名保持一致,只是扩展名不同。annotations目录里保留原始的Pascal VOC格式XML文件,这是为了方便人工审查标签边界,因为XML的可读性比一行数字的txt高很多。
3.2 XML标签到YOLO格式TXT的转换脚本
YOLOv5的训练管线读取的是TXT格式标签,每行内容为“类别ID 中心点x 中心点y 宽度 高度”,坐标都归一化到0到1之间。而标注工具如LabelImg默认输出Pascal VOC格式的XML,因此需要先做一个转换脚本把XML解析成TXT。
import xml.etree.ElementTree as ET import os def convert_xml_to_yolo(xml_path, out_dir, class_map={'boat': 0}): 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.findall('object'): name = obj.find('name').text if name not in class_map: continue bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.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 lines.append(f"{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") txt_path = os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] + '.txt') with open(txt_path, 'w') as f: f.write('\n'.join(lines)) xml_files = [f for f in os.listdir('datasets/ship/annotations') if f.endswith('.xml')] for f in xml_files: convert_xml_to_yolo(os.path.join('datasets/ship/annotations', f), 'datasets/ship/labels/train')这段脚本的关键是坐标归一化。XML里记录的是左上角和右下角的绝对像素坐标,而YOLO需要的是中心点坐标和宽高在图片尺寸上的比例,所以常见的错误是忘记除以图片宽高,直接把像素值写进TXT,导致训练时所有标注框都落在图片外面。脚本里用img_w和img_h做了统一归一化,避免这个问题。另外类别映射用的是字典,如果后续要增加类别,只需要在class_map里加一项。
3.3 数据拆分与损坏文件筛除
数据拆分要在转换完成之后做,并且保证图片和标签同步移动。这里给出一个按比例拆分的脚本,同时对缺失标签的图片做过滤,避免训练中途因为找不到对应TXT文件而崩溃。
import os import random image_dir = 'datasets/ship/images' label_dir = 'datasets/ship/labels' imgs = [f for f in os.listdir(image_dir) if f.endswith(('.jpg', '.jpeg', '.png'))] random.seed(42) random.shuffle(imgs) train_ratio, val_ratio = 0.8, 0.15 n = len(imgs) train_imgs = imgs[:int(n * train_ratio)] val_imgs = imgs[int(n * train_ratio):int(n * (train_ratio + val_ratio))] test_imgs = imgs[int(n * (train_ratio + val_ratio)):] def write_list(path, img_list): with open(path, 'w') as f: for name in img_list: txt_path = os.path.join(label_dir, name.replace('.jpg', '.txt').replace('.jpeg', '.txt').replace('.png', '.txt')) if not os.path.exists(txt_path): continue f.write(os.path.join(image_dir, name) + '\n') write_list('ship_train.txt', train_imgs) write_list('ship_val.txt', val_imgs) write_list('ship_test.txt', test_imgs)这个脚本的核心思路是先打乱图片列表,再按比例切分。写文件时检查对应TXT是否存在,不存在的图片自动跳过,这样就把数据质量问题的风险挡在训练之前。随机种子固定为42,保证每次拆分结果一致,方便复现实验。复制划分后建议手动抽查几个TXT标签,确认坐标值都在0到1之间,且目标框数量不为零。
提示:如果发现某个图片和标签文件没有对齐,优先查看是不是文件名带了空格或中文,YOLOv5的DataLoader在Windows下解析这类路径容易出问题,统一改成英文字母加下划线的命名风格最稳妥。
4. 单类别训练脚本的配置与超参数调整
4.1 数据配置文件ship.yaml的写法
YOLOv5通过YAML文件描述数据集路径和类别信息,训练前要把上一步生成的三个TXT文件路径写进去。单类别场景的配置非常简单,只需要指定类别数和类别名两个核心字段。
train: ship_train.txt val: ship_val.txt test: ship_test.txt nc: 1 names: ['boat']train和val字段对应训练集和验证集的图片路径列表,test字段可以留空,也可以在推理阶段指定测试图片列表。nc表示类别数量,这里只有boat一个类别,所以为1。names列表的顺序必须和训练时TXT标签里的类别ID一一对应,ID为0对应boat,如果后续新增类别,ID顺序不能随意调整。
4.2 训练命令与关键超参数说明
训练推荐从官方预训练权重出发做迁移学习,而不是随机初始化。预训练权重是在COCO数据集上训练出来的,已经学会了通用的边缘、纹理和形状特征,船舶检测任务只需要微调最后几层,训练速度更快,收敛也更稳定。
python train.py \ --data ship.yaml \ --weights yolov5s.pt \ --img 640 \ --epochs 100 \ --batch-size 16 \ --device 0train.py是YOLOv5-6.0的训练入口,--data指定数据集配置,--weights指定预训练权重路径,--img设置输入图片尺寸为640×640,--epochs设置训练轮数为100,--batch-size设置每批样本数量为16,--device指定使用第一块GPU。
这几个参数中最影响训练结果的是--img和--batch-size。图片分辨率越大,小目标的像素越多,但显存占用也越高,如果GPU显存只有8G,建议把img降到512或者把batch-size降到8。batch-size偏小时BatchNorm的统计量不稳定,容易导致训练震荡,解决办法是开启--sync-bn或者用更大的batch配合梯度累积。
| 参数 | 推荐值 | 职责说明 | 显存不足时的调整 |
|---|---|---|---|
| --img | 640 | 输入分辨率,影响小目标检出 | 降到512或416 |
| --batch-size | 16 | 每批样本数,影响BN与收敛 | 降到8,开启梯度累积 |
| --epochs | 100 | 训练轮数 | 小数据集可以降到50 |
| --weights | yolov5s.pt | 预训练权重起点 | 换成yolov5n.pt |
| --device | 0 | GPU编号 | CPU训练时改为cpu |
4.3 权重保存与断点续训注意点
训练过程中会自动在runs/train/exp目录下保存last.pt和best.pt两个权重。last.pt是每一轮结束时的最新权重,best.pt是验证集mAP最高的权重。实际推理时应该优先用best.pt,因为last.pt可能已经过拟合而你没注意到。
如果训练中途因为断电或显存溢出中断,不需要重新开始,YOLOv5直接支持从断点继续训练。
python train.py --resume runs/train/exp2--resume参数接收上一次训练的exp目录,脚本会自动读取目录下的last.pt和优化器状态,从断点处继续训练。需要特别注意的是,断点续训时不能再传--weights和--epochs,否则会跟原来的训练配置冲突。恢复训练后先观察前两轮的loss曲线是否延续中断前的走势,如果出现明显跳变,说明优化器状态没有正确加载,建议删掉exp目录重新训练。
5. 推理验证和漏检误检的优化手段
5.1 用训练好的权重跑推理
训练完成后,用detect.py对测试集图片做推理,验证模型的泛化效果。推理时可以通过--conf-thres和--iou-thres两个阈值来调节检测行为的松紧程度。
python detect.py \ --weights runs/train/exp/weights/best.pt \ --source datasets/ship/images/test \ --conf-thres 0.35 \ --iou-thres 0.45 \ --save-txt \ --save-conf--weights指定训练好的权重,--source指定测试图片目录,--conf-thres是置信度阈值,低于这个值的检测框会被丢弃,--iou-thres是NMS去重阈值,两个框的IoU超过这个值会合并。--save-txt表示把检测结果保存为TXT文件,--save-conf表示同时保存每个框的置信度。
船舶检测场景里,置信度阈值建议从0.35开始调。如果漏检多,把conf-thres调低,但要注意误检会随之增多;如果误检多,把conf-thres调高。NMS阈值也可以配合调整,船舶密集交错时,适当调低iou-thres可以减少重复框,但过度调低会导致同一艘船被拆成多个框。
5.2 从results.csv判断模型是否收敛
YOLOv5训练过程中会把每个epoch的loss和指标写入results.csv,这个文件是判断模型是否收敛、是否过拟合的重要依据。可以用一个简单的脚本把关键指标画出来。
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/train/exp/results.csv') df = df.rename(columns=lambda x: x.strip()) plt.figure(figsize=(10, 5)) plt.plot(df['epoch'], df['train/box_loss'], label='box_loss') plt.plot(df['epoch'], df['val/box_loss'], label='val_box_loss') plt.legend() plt.xlabel('epoch') plt.ylabel('loss') plt.savefig('loss_curve.png')results.csv的列名里带空格,读取后需要strip掉才能正常按列名索引。红色曲线和蓝色曲线分别是训练集和验证集的box loss,验证集loss在训练后期如果出现持续上升而训练集loss还在下降,基本可以判断过拟合已发生,早停的epoch就是当前最优解。
5.3 针对船舶场景的调优方向
船舶检测的调优有一个鲜明的方向性:多数项目不差大船的检出率,差的是远处小船和遮挡场景的召回率。先说远处小船,如果你的验证集里小目标占比高,建议跑一次validation脚本,查看按尺寸分层的AP值。YOLOv5的val.py会输出不同尺寸目标的AP,如果小目标AP明显低于中目标和大目标,说明模型对小目标响应不足。常见处理是增加高分辨率贴片训练,把原图切成四块并放大到640作为额外训练样本,代价是训练时间接近翻倍。
再说遮挡场景,多船并靠时检测框容易互相吞并,NMS把两个紧邻的船的框合并成一个。这种情况可以尝试把--iou-thres在推理时调低到0.4,并且检查训练时使用的anchor是否需要重新聚类。YOLOv5提供了自动聚类anchor的脚本,运行python utils/autoanchor.py --data ship.yaml会在训练前计算最优anchor,贴一下结果对比默认anchor的召回率差异,如果提升明显,值得在训练配置中加上--noautoanchor=False保持自动聚类结果。
最后是一个容易被忽略的点:测试图片的分布是否和训练集一致。如果训练集全是港口货轮,测试集混进海面快艇,模型的类别内泛化能力会明显下降。这种情况优先补充快艇角度的数据,而不是继续调超参数。逻辑很直接,模型没见过足够多的形态,任何参数调整都无法弥补分布差异带来的损失。
提示:调优阶段每次改动只调整一个变量。同时改分辨率、置信度阈值和数据增强策略,确实会有可见的变化,但你会不知道设备包含第5章最终提示的边界,无法形成可复用的调试结论。
本文还有配套的精品资源,点击获取