简介:本资源面向计算机视觉学习者与舰船检测应用开发者,提供一套基于YOLOv5的完整舰船检测方案,涵盖训练好的多类别检测权重、PR曲线与loss曲线等训练过程记录,mAP可达90%以上,并附带数千张标注好的船只检测数据集,标签同时提供txt与xml两种格式,覆盖舰艇、游轮、帆船、军舰等多个类别。资源包共159个文件,以py与pyc代码、yaml配置、jpg与png图像、pt权重、ui界面文件及xml标注为主,压缩包约769.62MB,采用PyTorch框架,可与YOLOv5共用同一环境,配置完成后即可加载模型直接测试。配套PyQt界面支持图片、视频检测及摄像头调用,并提供相应选择项,便于快速验证效果。目前已有1013人学习下载,适合希望快速上手舰船检测、复用数据集与权重或搭建可视化演示界面的读者参考。
1. 舰船检测项目落地:从几千张标注数据到 PyQt 可视化界面
做海面目标检测的同行多半遇到过这种局面:手头攒了几千张舰船图片,标注也做完了,但真要跑一个能演示、能交付、能继续迭代的检测系统,卡点往往不在模型本身,而在数据组织、类别定义、训练配置和界面串联这四件事上。这个标题讲的正是这样一套完整链路——用 YOLOv5 做多类型舰船检测,配套几千张标注好的船只数据集,最后用 PyQt 搭一个能直接操作的桌面界面。它解决的不是「模型能不能跑」的问题,而是「从数据到界面能不能一次串通」的问题。适合已经了解 YOLOv5 基础、手里有或准备整理舰船数据、需要交付一个可演示系统的从业者。下面按数据、训练、界面、避坑、进阶的顺序,把这条链路拆开讲清楚。
2. 舰船数据集怎么整理:类别定义、标注格式与划分策略
2.1 舰船类别的划分粒度决定模型上限
舰船检测和通用目标检测最大的区别在于类别体系。通用 COCO 里船就是一个类,但实际业务中往往要区分驱逐舰、护卫舰、航母、补给舰、渔船、货轮等。类别怎么定,直接决定后面标注成本、模型收敛难度和界面展示逻辑。
我一般按两个维度定类别:一是任务是否需要区分军用/民用,二是类间外观差异是否足够大。如果只是做海面目标计数,把船统一成一个类反而更稳,因为舰船在远距离下像素极少,强行分细类会导致标注一致性差、模型学不动。如果业务明确要识别舰型,那至少要保证每个类有 300 到 500 个实例,否则小类召回率会非常难看。
几千张标注数据听起来不少,但分摊到多个类别后,每类可能只有几百个目标。这时候要做的是先统计实例数,而不是只看图片数。常见做法是用脚本统计 labels 目录下每个类别的框数量,再决定是否合并稀有类。
import os from collections import Counter label_dir = "datasets/ship/labels/train" counter = Counter() for txt in os.listdir(label_dir): if not txt.endswith(".txt"): continue with open(os.path.join(label_dir, txt), "r") as f: for line in f: parts = line.strip().split() if len(parts) >= 5: cls_id = int(parts[0]) counter[cls_id] += 1 # 输出每个类别的实例数,用于判断是否需要合并稀有类 for cls_id, num in sorted(counter.items()): print(f"class {cls_id}: {num} instances")这段脚本遍历 YOLO 格式的标签文件,每行第一个字段是类别 id,统计后能直观看到类别分布。参数上注意 label_dir 要指向划分后的 train 目录,如果数据集还没划分,先对整个 labels 目录跑一遍。统计结果里如果某个类实例数低于 200,就要考虑合并或补充数据,否则训练时该类基本学不出来。
2.2 YOLO 格式标注的四个边界坑
舰船数据集的标注格式通常要求 YOLO 的归一化中心点格式:class_id x_center y_center width height,全部是相对图像宽高的 0 到 1 之间的浮点数。从其他格式转换时,最容易翻车的地方有四个。
第一是坐标越界。舰船目标经常贴着图像边缘,标注框可能超出边界,转换后 x_center 或 width 出现负数或大于 1。YOLOv5 训练时虽然会做裁剪,但越界框会导致损失异常。转换脚本里必须加 clamp。
第二是空标签文件。有些图片里没有舰船,但标注时留了空 txt,这本身没问题,YOLOv5 会当作背景图。但如果空文件比例过高,比如超过 30%,模型会偏向预测背景,召回率下降。要统计空标签比例。
第三是类别 id 从 1 开始。很多标注工具默认从 1 编号,而 YOLOv5 要求从 0 开始。这个错位不会报错,但会让所有类别整体偏移,训练出来的模型类别全错。
第四是图片和标签文件名不一致。YOLOv5 按文件名匹配图片和标签,如果图片是 jpg 而标签是 png 对应的 txt,匹配不上就会被忽略。批量检查文件名前缀是否一致。
import os img_dir = "datasets/ship/images/train" lbl_dir = "datasets/ship/labels/train" img_names = {os.path.splitext(f)[0] for f in os.listdir(img_dir)} lbl_names = {os.path.splitext(f)[0] for f in os.listdir(lbl_dir)} # 找出有图无标签、有标签无图的情况 no_label = img_names - lbl_names no_img = lbl_names - img_names print(f"images without labels: {len(no_label)}") print(f"labels without images: {len(no_img)}") # 统计空标签文件比例 empty = 0 for f in os.listdir(lbl_dir): if os.path.getsize(os.path.join(lbl_dir, f)) == 0: empty += 1 print(f"empty label ratio: {empty / max(len(lbl_names), 1):.2%}")这段检查脚本是训练前必跑的。no_label 和 no_img 数量应该为 0,空标签比例建议控制在 10% 以内。如果空标签过多,要么补充正样本,要么在数据配置里调整。
2.3 训练集验证集划分与 data.yaml 配置
几千张舰船数据按 8:1:1 或 7:2:1 划分比较常见。舰船数据有个特点:同一艘船在不同角度、不同光照下可能被拍多次,如果随机划分,同一艘船可能同时出现在训练集和验证集,导致验证指标虚高。更严谨的做法是按拍摄批次或场景划分,但实操中如果数据来源单一,随机划分也能接受,只是心里要清楚验证集指标偏乐观。
划分完成后写 data.yaml,这是 YOLOv5 训练的数据入口。路径建议用绝对路径,避免相对路径在不同工作目录下失效。
# datasets/ship/data.yaml train: /data/ship/images/train val: /data/ship/images/val test: /data/ship/images/test nc: 6 names: ['destroyer', 'frigate', 'carrier', 'supply', 'fishing', 'cargo']nc 是类别数,names 顺序必须和标注里的 class_id 严格对应。如果标注时驱逐舰是 0,这里第一个就必须是 destroyer。改类别顺序而不重新映射标签,是新手最容易犯的错之一。
3. YOLOv5 舰船检测模型训练:环境、超参与收敛判断
3.1 环境配置与 conda 隔离
YOLOv5 的环境配置本身不复杂,但舰船检测常涉及 GPU 训练和 PyQt 界面,依赖容易冲突。我一般用 conda 建独立环境,Python 选 3.8 或 3.9,PyTorch 按 CUDA 版本装。
conda create -n ship_yolo python=3.9 -y conda activate ship_yolo # 按本机 CUDA 版本选择,这里以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 YOLOv5 依赖 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt装完后用python -c "import torch; print(torch.cuda.is_available())"验证 GPU 是否可用。如果返回 False,先查 CUDA 版本和 PyTorch 是否匹配,不要急着改代码。舰船数据训练动辄几小时,CPU 训练基本不可接受。
3.2 舰船检测的必调超参
YOLOv5 默认超参是给 COCO 调的,直接套到舰船数据上往往效果一般。舰船目标的特点是:小目标多、长宽比极端、背景单一但海杂波干扰大。针对这些特点,有几个参数必须调。
| 参数 | 默认值 | 舰船场景建议 | 原因 |
|---|---|---|---|
| imgsz | 640 | 640 或 896 | 远距离舰船像素少,增大输入提升小目标召回 |
| batch-size | 16 | 按显存 8-32 | 舰船数据背景相似,大 batch 有助于稳定梯度 |
| epochs | 300 | 100-200 | 几千张数据容易过拟合,早停比堆 epoch 有效 |
| lr0 | 0.01 | 0.005-0.01 | 小数据集降低初始学习率,减少震荡 |
| anchor | COCO 默认 | 重新聚类 | 舰船长宽比和 COCO 差异大,重聚类提升明显 |
anchor 重聚类是舰船检测里性价比最高的一步。YOLOv5 自带 kmeans 脚本,用训练集标签跑一遍,把生成的 anchor 写进模型配置。
python utils/autanchor.py --data datasets/ship/data.yaml --img-size 640 --thr 4.0 --save-dir ./anchors跑完后会输出一组新的 anchor,替换 models/yolov5s.yaml 里的 anchors 字段。注意 autanchor 脚本在不同版本里路径可能略有差异,核心是用训练集标签做 kmeans,不要用验证集。
3.3 训练命令与收敛判断
训练命令本身简单,关键是看日志判断收敛。
python train.py \ --data datasets/ship/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 150 \ --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/train \ --name ship_exp1参数说明:weights 用预训练权重,舰船数据几千张,从头训容易欠拟合;hyp 选 low 增强版本,舰船数据背景单一,过强增强反而有害;project 和 name 决定输出目录。
训练过程中重点看三个指标:box_loss 是否稳定下降、mAP@0.5 是否持续上升、val box_loss 是否在某个 epoch 后开始上升。如果 val loss 上升而 train loss 继续下降,就是过拟合,该早停或加数据增强。舰船检测里 mAP@0.5 到 0.85 以上通常可用,但更要看小目标召回,因为远距离舰船才是业务难点。
4. PyQt 界面串联:从模型推理到可视化交互
4.1 PyQt 界面与 YOLOv5 推理的线程分离
PyQt 做检测界面最大的坑是界面卡死。YOLOv5 推理是计算密集型,如果直接在主线程里跑,点一下按钮界面就无响应。正确做法是把推理放在 QThread 里,通过信号槽把结果传回主线程更新界面。
from PyQt5.QtCore import QThread, pyqtSignal import cv2 import torch class DetectThread(QThread): finished = pyqtSignal(object, object) # 原图, 检测结果 def __init__(self, model, img_path): super().__init__() self.model = model self.img_path = img_path def run(self): img = cv2.imread(self.img_path) # YOLOv5 推理,size 与训练时保持一致 results = self.model(img, size=640) self.finished.emit(img, results)这段代码把推理封装进 QThread,finished 信号携带原图和结果。参数上 size 要和训练 imgsz 一致,否则小目标检测效果会掉。model 在主线程加载一次即可,不要每次推理都重新加载,否则显存和耗时都受不了。
4.2 检测结果绘制与类别统计面板
推理结果拿到后,需要在界面上画框、标类别、显示置信度,同时统计各类舰船数量。YOLOv5 的 results 对象自带 pandas 格式的输出,直接取用比手动解析方便。
import pandas as pd def update_ui(self, img, results): df = results.pandas().xyxy[0] # 列: xmin,ymin,xmax,ymax,confidence,class,name for _, row in df.iterrows(): x1, y1, x2, y2 = int(row['xmin']), int(row['ymin']), int(row['xmax']), int(row['ymax']) label = f"{row['name']} {row['confidence']:.2f}" cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) # 类别统计 counts = df['name'].value_counts().to_dict() self.count_label.setText(str(counts)) # 转 RGB 后显示到 QLabel img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) self.show_image(img_rgb)这里的关键是 results.pandas().xyxy[0] 直接给出结构化结果,省去手动解析。counts 字典用于界面右侧统计面板。注意 OpenCV 是 BGR,Qt 显示要转 RGB,这个转换漏掉会导致颜色异常,是 PyQt 图像显示的经典坑。
4.3 界面功能布局与模型切换
一个能交付的舰船检测界面至少要有:图片选择、检测按钮、原图/结果对比、类别统计、置信度阈值调节。置信度阈值建议做成滑块,因为舰船检测里低置信度目标可能是远距离小船,也可能是海杂波,让用户自己调比固定阈值实用。
模型切换功能也值得加。YOLOv5s 速度快适合演示,YOLOv5m 或 l 精度高适合正式检测。界面上放一个下拉框,切换时重新加载模型权重即可。注意切换模型时要释放旧模型显存,否则连续切换几次就 OOM。
def load_model(self, weights): self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights) self.model.conf = self.conf_slider.value() / 100.0 self.model.eval()torch.hub.load 这种方式加载方便,但依赖网络或本地缓存。离线部署时建议把 YOLOv5 仓库和权重一起打包,用本地路径加载,避免现场拉取失败。
5. 舰船检测落地避坑:五条血泪经验
5.1 验证集 mAP 很高但实际检测漏船
现象:训练日志里 mAP@0.5 到 0.9,但拿新图片测试时远距离舰船大量漏检。
原因:验证集和训练集同分布,且验证集里小目标比例低。舰船数据如果按随机划分,验证集里大目标占多数,mAP 被拉高,掩盖了小目标问题。
解决:单独统计验证集里小目标(面积小于 32x32)的比例,并在测试时专门找远距离海面图片验证。训练时开启 mosaic 和 copy-paste 增强,提升小目标样本比例。
5.2 类别 id 错位导致所有舰船识别成同一类
现象:模型能框出船,但所有类别都是 destroyer,其他类完全不出现。
原因:标注时类别从 1 开始编号,data.yaml 里 names 从 0 开始对应,整体错位一位。
解决:检查标签文件里出现的最大 class_id 是否等于 nc-1。如果最大 id 是 nc,说明编号从 1 开始,需要批量减 1。
# 批量将类别 id 减 1 python -c " import os d='datasets/ship/labels/train' for f in os.listdir(d): p=os.path.join(d,f) lines=open(p).readlines() new=[] for l in lines: parts=l.split() if parts: parts[0]=str(int(parts[0])-1) new.append(' '.join(parts)+'\n') open(p,'w').writelines(new) "5.3 PyQt 界面打包后模型加载失败
现象:源码运行正常,PyInstaller 打包成 exe 后提示找不到模型或 YOLOv5 模块。
原因:PyInstaller 不会自动打包 torch.hub 动态加载的模块和权重文件。
解决:把 YOLOv5 仓库和权重文件作为数据文件打包,代码里用 sys._MEIPASS 定位资源路径。或者改用 ONNX 导出模型,用 onnxruntime 推理,依赖更少,打包更稳。
5.4 海杂波被误检为舰船
现象:海面浪花、云层边缘被框成舰船,置信度还不低。
原因:训练数据里负样本不足,模型没学过海杂波特征。
解决:收集一批纯海面、无舰船的图片作为背景图加入训练集,标签留空。比例控制在正样本的 10% 到 20%。同时推理时适当提高置信度阈值,但根本解法还是补负样本。
5.5 训练中断后恢复导致学习率异常
现象:用 --resume 恢复训练后,loss 突然飙升,模型效果变差。
原因:YOLOv5 恢复训练时如果 optimizer 状态和 epoch 不匹配,学习率调度会错乱。
解决:尽量一次训完。如果必须中断,用 --resume 时确认 last.pt 完整,且不要手动改 hyp 文件。恢复后观察前几个 epoch 的 loss,异常就回退到最近的 best.pt 重新微调。
6. 舰船检测进阶:用测试时增强和 ONNX 导出压榨最后几个点
模型训完、界面跑通之后,如果还想再提精度和部署效率,有两个方向值得做:测试时增强和 ONNX 导出。
测试时增强(TTA)是在推理阶段对同一张图做翻转、缩放,把多次预测结果融合。YOLOv5 自带 TTA 接口,一行代码就能开。代价是推理耗时增加约 3 倍,适合对精度要求高、对速度不敏感的离线检测场景。
# 开启 TTA 推理 results = model(img, size=640, augment=True)augment=True 会做水平翻转和缩放融合。舰船检测里这个操作对远距离小目标召回有可见提升,我实测在自建舰船验证集上 mAP@0.5 能涨 1 到 2 个点。但注意 TTA 对密集排列的舰船可能产生重复框,需要 NMS 的 iou 阈值配合调整,一般从 0.45 调到 0.5 到 0.55。
ONNX 导出则是为了部署。PyQt 界面如果直接用 PyTorch 推理,打包体积大、启动慢。导出 ONNX 后用 onnxruntime 推理,体积能小一半以上,CPU 推理速度也更快,适合交付给没有 GPU 的客户。
python export.py --weights runs/train/ship_exp1/weights/best.pt --include onnx --img 640 --batch 1导出后验证 ONNX 和 PyTorch 输出是否一致,用 onnxruntime 跑一张测试图,对比框数量和类别。如果差异大,检查导出时的 opset 版本,舰船检测里 opset 12 或 13 比较稳。
import onnxruntime as ort import numpy as np sess = ort.InferenceSession("best.onnx") input_name = sess.get_inputs()[0].name # 预处理要和训练一致:resize 640, 归一化, NCHW blob = np.random.randn(1, 3, 640, 640).astype(np.float32) outputs = sess.run(None, {input_name: blob}) print([o.shape for o in outputs])这段只是验证 ONNX 能跑通,实际预处理要按 YOLOv5 的 letterbox 方式做,否则框位置会偏。ONNX 输出的解码逻辑和 PyTorch 不同,需要自己写 NMS 后处理,这是导出部署里最费时间的部分。
最后说个习惯:舰船检测项目里,我永远会在训练前把数据集统计脚本跑一遍,把类别分布、空标签比例、图片标签匹配情况打印出来存档。这个习惯帮我省过至少三次通宵排查——有一次就是类别 id 从 1 开始,训练了两天才发现所有类别全错。数据这关过了,后面模型和界面都是顺水推舟的事。希望帮到你。
本文还有配套的精品资源,点击获取