简介:本资源面向计算机视觉学习者与舰船检测应用开发者,提供一套基于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 各种类型舰船检测 + PyQt 界面」,本质是一条完整的落地链路——数据进来,模型出去,界面兜底。它适合两类人:一类是手里已经有一批航拍或遥感舰船图、想快速验证检测可行性的人;另一类是做完模型发现没人会用、需要补一个桌面端交互入口的人。我做过几轮类似的船检项目,最深的体会是:模型精度到 0.85 之后,剩下的时间基本都花在数据清洗和界面联调上,而不是调网络结构。这一章先把整条链路的边界讲清楚,后面几章拆开讲数据、训练、界面和踩坑。
2. 舰船数据集怎么选、怎么洗、怎么转成 YOLOv5 能吃的格式
数据集是这条链路的地基。几千张标注图听起来不少,但如果类别分布畸形、框贴边、小目标密集,训出来的模型在真实场景里会集体翻车。舰船检测的数据来源常见有几类:遥感/卫星影像切片、无人机航拍、港口监控截图。不同来源的目标尺度差异极大,卫星图里一条船可能只有十几个像素,航拍图里能占半张图。选数据集之前先想清楚你的部署场景是近景还是远景,别拿遥感数据训一个要跑在码头摄像头上的模型。
2.1 舰船类别的划分粒度:别一上来就分十几类
很多公开船只数据集会把船分成航母、驱逐舰、护卫舰、补给舰、渔船、货轮、油轮、游艇等一大堆细类。我的建议是:第一版模型只分 2 到 4 类。原因是细类之间的视觉差异在低分辨率下几乎不可分,标注一致性也差——同一条船不同标注员可能标成不同类,模型学到的就是噪声。
一个务实的划分方式是按尺度 + 场景分:
| 划分方案 | 类别 | 适用场景 | 标注难度 |
|---|---|---|---|
| 二分类 | ship / 背景 | 只关心有没有船 | 低 |
| 三分类 | 大型舰船 / 小型船只 / 背景 | 港口监控、航道管理 | 中 |
| 四分类 | 军舰 / 货轮 / 渔船 / 小艇 | 需要粗粒度区分用途 | 中高 |
| 细粒度 | 十余种舰型 | 情报级识别 | 高,需专家标注 |
新手直接上细粒度,大概率得到一个每类 AP 都不到 0.5 的模型。先把二分类或三分类跑通,再考虑加类。
2.2 标注格式转换:VOC / COCO 转 YOLO 的脚本与四个边界坑
舰船数据集常见的标注格式是 Pascal VOC 的 XML 或 COCO 的 JSON,YOLOv5 要的是每张图一个.txt,每行class_id cx cy w h,且坐标是归一化到 0~1 的。下面这个脚本处理 VOC 转 YOLO,我把它当成标准模板用:
import os import xml.etree.ElementTree as ET # 类别映射:按你的数据集实际类别改,顺序决定 class_id CLASSES = ["warship", "cargo", "fishing", "boat"] def convert_voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() # 图片尺寸从 XML 的 size 节点取,别用 PIL 再读一遍,省 IO size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in CLASSES: continue # 未定义类别直接丢弃,避免 class_id 越界 cls_id = CLASSES.index(name) 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) # 边界裁剪:标注越界是舰船数据集的高频问题 xmin = max(0, min(xmin, w)) xmax = max(0, min(xmax, w)) ymin = max(0, min(ymin, h)) ymax = max(0, min(ymax, h)) if xmax - xmin < 2 or ymax - ymin < 2: continue # 过滤掉宽高小于 2 像素的废框 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: txt_name = os.path.splitext(xml_file)[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w") as f: f.write("\n".join(lines)) convert_voc_to_yolo("./annotations", "./images", "./labels")逻辑说明:脚本遍历 XML,按CLASSES列表把类别名映射成整数 id,坐标做归一化。四个边界坑分别是——类别越界(数据集里有你没定义的类,必须丢弃而不是硬塞)、坐标越界(标注框超出图片范围,不裁剪会导致归一化后出现负数或大于 1 的值,训练时 loss 直接 NaN)、废框(宽高过小的框是标注噪声,留着只会拉低精度)、空标签文件(一张图里所有框都被过滤后不要生成空 txt,否则 YOLOv5 会把它当负样本,但实际它可能只是标注漏了)。
参数上,CLASSES的顺序必须和后面训练时data.yaml里的names完全一致,错一位整个模型就废了。归一化保留 6 位小数足够,YOLOv5 内部会再处理。
2.3 数据集划分与 data.yaml:8:1:1 不是铁律
划分训练/验证/测试集时,舰船数据有个特殊点:同一片海域、同一时段的连续帧高度相似。如果随机划分,验证集里会出现和训练集几乎一样的图,指标虚高。正确做法是按场景或按航次划分,让验证集来自训练时没见过的海域。
data.yaml的写法:
path: /home/user/ship_dataset train: images/train val: images/val test: images/test nc: 4 names: ["warship", "cargo", "fishing", "boat"]nc是类别数,必须和names长度一致。path用绝对路径最稳,相对路径在不同工作目录下启动训练时经常找不到文件,这是新手最常见的翻车点之一。
3. YOLOv5 训练舰船检测模型:从环境配置到超参数怎么调
环境配置这一步,conda 建虚拟环境是主流做法,能避开系统 Python 的依赖冲突。YOLOv5 对 PyTorch 和 CUDA 版本比较敏感,版本对不上会报一堆看不懂的错。我一般先确认显卡驱动支持的 CUDA 上限,再选对应的 PyTorch。
3.1 环境配置与最小可跑命令
# 建环境,Python 版本选 3.8 或 3.9,兼容性最好 conda create -n ship_yolo python=3.9 -y conda activate ship_yolo # 装 PyTorch,CUDA 11.8 是当前比较通用的版本,按自己驱动改 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 拉 YOLOv5 源码并装依赖 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt装完先跑一次自检,确认环境和预训练权重都能用:
python detect.py --weights yolov5s.pt --source data/images --device 0这条命令会用 COCO 预训练的 yolov5s 跑一遍示例图。如果这一步就报错,别急着训自己的数据,先把环境问题解决掉。常见错误是torch.cuda.is_available()返回 False,多半是 PyTorch 版本和驱动不匹配,重装对应 CUDA 版本的 torch 即可。
3.2 训练命令与关键超参数
舰船检测我一般从 yolov5s 起步,数据量几千张的话,yolov5s 或 yolov5m 足够,上 l/x 容易过拟合且推理慢。
python train.py \ --data data/ship.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --hyp data/hyps/hyp.scratch-low.yaml \ --device 0 \ --project runs/train \ --name ship_v1参数逐个说:--img 640是输入分辨率,舰船小目标多的话可以提到 1024,但显存占用翻倍,batch 要相应降到 4 或 8。--batch 16在 8G 显存上跑 640 分辨率比较稳。--epochs 150是经验值,配合早停(YOLOv5 默认 patience=100)不会白跑。--hyp选hyp.scratch-low.yaml是因为舰船数据集通常不大,低增强配置能减少过拟合;如果数据量上万,可以换hyp.scratch-high.yaml加强增强。
训练过程中重点看三个指标:mAP@0.5、mAP@0.5:0.95和box_loss。如果 box_loss 一直不降,检查标签格式;如果 mAP 在验证集上远低于训练集,说明过拟合或验证集分布不一致。
3.3 舰船小目标的针对性调整
舰船检测最头疼的是小目标。640 分辨率下,一条 20 像素的船经过下采样后特征几乎消失。三个可操作的调整:
第一,提高输入分辨率到 1024 或 1280,代价是显存和推理时间。第二,调整 anchor,用python utils/autanchor.py在你的数据集上重新聚类 anchor,让先验框更贴合舰船的长宽比——舰船普遍是细长型,COCO 的默认 anchor 偏方正,匹配度差。第三,开启--multi-scale多尺度训练,让模型适应不同尺度的目标。
anchor 重聚类的命令:
python utils/autoanchor.py --data data/ship.yaml --weights yolov5s.pt它会输出当前 anchor 和数据集的最佳匹配度,如果 best possible recall 低于 0.98,就值得重新生成 anchor 并写回模型配置。
4. PyQt 界面怎么做:把训练好的模型包成能点的桌面工具
模型训完,.pt文件躺在 runs 目录里,除了你自己没人会用。PyQt 界面的价值就是把这层命令行封装掉,让同事双击就能选图、看结果、导出。别把界面做复杂,核心就三个功能:选图片/文件夹、跑推理、显示带框结果。
4.1 界面结构设计与线程处理
PyQt 做推理界面最大的坑是主线程卡死。推理是耗时操作,如果直接在主线程里调模型,界面会假死,用户以为程序崩了。正确做法是把推理放到 QThread 里,通过信号槽回传结果。
界面布局我一般用最简单的三段式:顶部一个按钮区(选图、选文件夹、开始检测),中间一个 QLabel 显示原图或结果图,底部一个 QTextEdit 显示检测日志(检测到几个目标、耗时多少)。
import sys import cv2 import torch from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, QTextEdit, QFileDialog) from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap class InferThread(QThread): # 信号:传回带框图像和日志文本 finished = pyqtSignal(object, str) def __init__(self, model, img_path): super().__init__() self.model = model self.img_path = img_path def run(self): # 推理在子线程执行,避免阻塞界面 results = self.model(self.img_path) rendered = results.render()[0] # 返回带框的 numpy 数组 log = f"检测到 {len(results.xyxy[0])} 个目标" self.finished.emit(rendered, log) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("舰船检测系统") self.model = torch.hub.load("ultralytics/yolov5", "custom", path="runs/train/ship_v1/weights/best.pt") self.model.conf = 0.25 # 置信度阈值,界面里可做成滑块 self.init_ui() def init_ui(self): central = QWidget() layout = QVBoxLayout() self.btn_open = QPushButton("选择图片") self.btn_open.clicked.connect(self.open_image) self.label = QLabel("等待选择图片") self.log = QTextEdit() self.log.setReadOnly(True) layout.addWidget(self.btn_open) layout.addWidget(self.label) layout.addWidget(self.log) central.setLayout(layout) self.setCentralWidget(central) def open_image(self): path, _ = QFileDialog.getOpenFileName(self, "选图", "", "Images (*.png *.jpg)") if not path: return self.thread = InferThread(self.model, path) self.thread.finished.connect(self.show_result) self.thread.start() def show_result(self, img, log_text): # OpenCV 是 BGR,Qt 要 RGB,转换顺序别搞反 h, w, c = img.shape qimg = QImage(img.data, w, h, w * c, QImage.Format_RGB888).rgbSwapped() self.label.setPixmap(QPixmap.fromImage(qimg)) self.log.append(log_text) if __name__ == "__main__": app = QApplication(sys.argv) win = MainWindow() win.show() sys.exit(app.exec_())逻辑说明:InferThread继承 QThread,把模型推理放在run()里,完成后通过finished信号把图像和日志传回主线程。torch.hub.load直接加载本地训练好的best.pt,conf是置信度阈值,做成界面滑块能让用户自己调松紧。rgbSwapped()处理 OpenCV 的 BGR 到 Qt 的 RGB 转换,这一步漏了颜色会反。
参数上,conf=0.25是通用起点,舰船检测如果误检多就提到 0.4,漏检多就降到 0.15。iou阈值默认 0.45,密集船只场景可以降到 0.3 减少框合并。
4.2 打包成 exe 与常见依赖问题
界面写完,用 PyInstaller 打包成 exe 给不装 Python 的人用:
pyinstaller --noconfirm --windowed --name ShipDetect \ --add-data "runs/train/ship_v1/weights/best.pt;weights" \ main.py--windowed去掉控制台窗口,--add-data把模型权重打进去。注意路径分隔符在 Windows 上是分号,Linux 上是冒号。打包后最常见的翻车是模型路径找不到——代码里要用sys._MEIPASS处理打包后的临时目录,不能写死相对路径。
5. 避坑与排查:舰船检测项目里最容易翻车的五件事
这一章是我踩过的坑里挑出来最有代表性的五条,每条按现象、原因、解决写。
坑一:训练 loss 正常但 mAP 一直是 0。现象是 box_loss 在降,但验证集 mAP 死活不动。原因九成是data.yaml里的names顺序和标签文件里的 class_id 对不上,或者nc写错了。解决方法是拿一张训练图手动跑detect.py,看输出的类别名对不对,再抽查几个 label txt 的第一列数字是否在[0, nc-1]范围内。
坑二:小目标全漏检。现象是近处大船检得到,远处小船一个都检不出。原因是输入分辨率太低,小目标下采样后特征消失。解决是把--img提到 1024,同时用 autoanchor 重聚类 anchor,让先验框匹配舰船的细长比例。
坑三:PyQt 界面点检测就卡死。现象是点按钮后窗口无响应,几秒后恢复。原因是推理跑在主线程。解决是把推理逻辑放进 QThread,用信号槽回传结果,主线程只负责 UI 更新。
坑四:打包后 exe 报找不到模型。现象是源码能跑,打包后启动就崩。原因是 PyInstaller 把文件解压到临时目录,代码里的相对路径失效。解决是用sys._MEIPASS拼接资源路径,并在打包时用--add-data把权重文件带上。
坑五:验证集指标虚高,上线就拉胯。现象是验证集 mAP 0.9,实际用起来一堆误检漏检。原因是数据集划分时同一场景的相似帧同时进了训练和验证集。解决是按海域或航次划分,确保验证集来自训练时没见过的场景。
6. 让舰船检测真正可用的两个进阶技巧
第一个技巧是用测试时增强(TTA)换精度。YOLOv5 推理时加--augment参数会做多尺度翻转增强再合并结果,小目标召回率通常能涨几个点,代价是推理时间翻倍。对离线分析场景值得,对实时视频流不划算。我一般先在验证集上对比开与不开的 mAP 差异,超过 2 个点才考虑上线开。
python detect.py --weights best.pt --source test_imgs --augment --conf 0.2第二个技巧是置信度阈值分场景调。港口监控场景误报代价高,conf提到 0.4 以上;搜救场景漏检代价高,conf降到 0.1 并配合 TTA。这个阈值不该写死在代码里,应该在 PyQt 界面上做成滑块,让使用者按任务自己调。我在界面里加了一个 conf 滑块和一个 iou 滑块,联调时发现比任何文档都管用——用户自己拖两下就知道什么值合适。
最后一个习惯:每次训完模型,别只看 mAP 数字,一定导出 20 张验证集的预测图肉眼过一遍。指标会骗人,尤其是小目标和密集场景,肉眼看一遍能发现一堆指标看不出的问题。这套链路我从数据清洗到界面联调走下来,真正花时间的从来不是模型本身,而是数据和交互。希望帮到你。
本文还有配套的精品资源,点击获取