恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于YOLOv12的船舶类型识别检测系统设计与实现
首页
资讯中心
/
基于YOLOv12的船舶类型识别检测系统设计与实现
基于YOLOv12的船舶类型识别检测系统设计与实现
发布时间:2026/10/22 3:20:03
做船舶类型识别最直接的痛点是港口和航道场景太复杂船体密集、遮挡严重、远近距离导致目标尺度差异大货船和驳船、客船和邮轮之间在外形上又有不少相似度靠传统图像特征做分类基本不可靠。这套基于YOLOv12的船舶类型识别检测系统就是围绕这个场景打磨出来的完整工程项目包含YOLOv12深度学习检测模型、可复用的船舶YOLO格式数据集以及带登录注册功能的可视化UI界面最终交付的是全套Python项目源码和已经训练好的模型权重文件。无论你是拿它当课程设计、毕业设计还是想改造成港口监控或船舶管理系统的前期原型都可以在这套代码基础上直接调整不用从零开始搭轮子。1. 项目概览与整体思路1.1 为什么选YOLOv12而不是YOLOv8/v11很多人在网上搜船舶识别最先找到的往往是YOLOv8或者YOLOv11的方案。但在我实际测试下来YOLOv12对这类目标分散、尺度跨度大的场景有明显优势。YOLOv12的核心变化是引入了注意力机制具体来说是区域注意力Area Attention它把特征图划分成若干区域在每个区域内部计算注意力关系而不是做全局注意力这样既拿到了注意力机制捕捉长距离依赖的能力又不会让计算量涨到无法实时推理。放在船舶识别这个场景里来说一张俯瞰港口照片里可能有近处占据半个画面的滚装船也有远处只有几十个像素的小渔船传统卷积主干在提取这种跨尺度特征时容易顾此失彼而注意力机制能让模型主动把上下文信息聚合起来对小而弱的目标更友好。我对比过同一份船舶数据上YOLOv8和YOLOv12的训练结果在相同迭代次数下YOLOv12的小目标类别召回率通常能高出几个点尤其在背景杂乱、船体颜色和海水颜色接近的样本上表现更稳。这就是我在这套系统里选YOLOv12的根本原因不是追新而是它确实贴合这个任务。1.2 系统功能拆解与模块划分完整的系统由三个逻辑模块组成这个划分也决定了源码的目录结构。第一个是检测核心也就是YOLOv12模型。它负责接收图片、视频帧或摄像头画面输出每个船舶目标的类别、置信度和边框坐标。这部分是技术核心但被封装成一个黑盒工具UI层只负责调用它并展示结果。第二个是交互层。包含登录注册界面和主检测界面登录注册负责用户身份校验主界面提供图片选择、视频选择、摄像头开启、检测开关以及结果展示和保存。这一层决定了系统好不好用。第三个是数据和工程层。包括YOLO格式数据集的整理脚本、训练脚本、评估脚本、模型权重管理以及把模型输出转换成界面可展示内容的一系列工具函数。系统整体流程可以这样描述用户在登录界面完成注册或登录进入主界面后加载训练好的模型权重然后选择图片、视频或打开摄像头系统调用模型推理把每个目标的类别和坐标绘制在画面上并把统计信息展示在侧边栏和结果文本框里。1.3 技术选型前的三个关键判断动手之前先想清楚三件事能帮你少走很多弯路。第一深度学习框架选什么。推荐PyTorch原因是生态成熟、调试方便主流YOLO系列的训练和推理代码几乎都基于PyTorchYOLOv12的预训练权重、社区组件也都是围绕PyTorch发布的。TensorFlow在这个领域不是不能做但很多坑都要自己踩。第二UI框架选什么。如果只是交差Tkinter足够代码短不用额外安装。但要做登录注册、主界面、图片视频切换、结果展示这些功能Tkinter的美观度和布局灵活性都很吃力。PyQt5虽然依赖大一点但控件丰富支持QThread多线程界面风格也专业实际开发效率反而更高。我在这套系统里用的是PyQt5。第三硬件约束要提前确认。训练阶段需要NVIDIA显卡显存至少6GB否则训练速度会慢到让人失去耐心。推理阶段则宽松得多哪怕只有CPU只要不追求高帧率跑一帧图片也能在两三秒内出结果。如果目标机器没有GPU部署时选择CPU版PyTorch即可。2. 环境准备与项目骨架搭建2.1 Python环境与依赖安装首先建议用虚拟环境隔离项目依赖避免跟其他项目互相干扰。conda create -n yolo12 python3.10 -y conda activate yolo12 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121如果机器没有NVIDIA显卡把最后一行换成CPU版安装方式即可。接着安装项目所需的其余依赖pip install ultralytics pyqt5 opencv-python pillow numpy这里有个细节YOLOv12的代码发布形态可能和标准YOLO系列不完全一样如果你安装的ultralytics版本里还没有内置YOLOv12模型就要从YOLOv12的官方源码仓库拉取代码并按其说明安装。安装完成后可以运行一段验证代码确认模型能正常加载。from ultralytics import YOLO model YOLO(yolov12.pt) results model(test.jpg) print(results[0].boxes)能打印出检测框列表就说明环境没问题。2.2 目录结构与源码组织我建议按下面这种结构组织项目功能清晰后面打包和定位问题都方便ship_detect/ ├─ app/ │ ├─ login_ui.py # 登录注册窗口 │ ├─ main_ui.py # 主检测窗口 │ ├─ utils.py # 通用工具函数 │ ├─ database.py # SQLite用户数据库操作 │ └─ detect_thread.py # 推理线程 ├─ model/ │ ├─ best.pt # 训练好的模型权重 │ └─ data.yaml # 类别配置文件 ├─ dataset/ │ ├─ images/ │ │ ├─ train/ │ │ └─ val/ │ ├─ labels/ │ │ ├─ train/ │ │ └─ val/ ├─ train.py # 训练脚本 ├─ detect.py # 命令行推理脚本 ├─ requirements.txt └─ README.mdapp目录放全部界面和交互逻辑model目录放权重文件dataset目录放数据集。train.py和detect.py作为独立的脚本这样即使不打开UI也能用命令行完成训练和检测。2.3 背景知识YOLO格式数据集长什么样船舶数据集最常见的组织方式就是YOLO格式。每张图片对应一个同名的txt文件文件名一致txt里每一行代表一个目标。比如3 0.625 0.375 0.12 0.08 5 0.320 0.710 0.060 0.040四个数字分别是类别编号、目标中心点的x坐标、目标中心点的y坐标、目标宽、目标高。注意坐标都是归一化到0到1之间的比例值不是像素绝对值。如果把0.625理解为图片上的第625列那就全错了。数据集目录里必须有data.yaml描述类别信息内容非常直观path: dataset train: images/train val: images/val nc: 7 names: [cargo, passenger, fishing, cruise, tug, barge, sail]nc是类别总数names是类别名称列表顺序就是模型训练的类别编号顺序。这个顺序必须在训练和推理时保持一致否则会出现检测结果框位置对但类别名错位的诡异问题。3. 船舶数据集的准备与处理3.1 数据来源与类别定义船舶识别系统的效果上限有七成取决于数据。公开领域有不少船舶目标检测数据集可以按需组合也可以自己拍摄补充样本。采集渠道通常包括港口摄像头截图、无人机航拍、公开的海事监管图片等使用时注意来源合规即可。类别数量的设定要克制。船舶领域常见类别包括货船、客船、渔船、邮轮、拖船、驳船、帆船、皮划艇、军用舰艇等。但对一个课程设计或者工程演示来说建议控制在6到8类越多类数据不均衡的问题越严重。我最终定的7类为货船、客船、渔船、邮轮、拖船、驳船、帆船覆盖了港口场景里绝大多数常见船舶类型。3.2 数据标注与格式转换标注工作建议使用图形化标注工具手工框选目标并指定类别。常见的标注工具都支持导出YOLO格式或VOC格式。如果导出的是VOC XML格式就需要自己写转换脚本。这里给一个常用的XML转YOLO txt脚本核心逻辑直接保存为xml2yolo.py使用import os import xml.etree.ElementTree as ET def convert(xml_folder, txt_folder, classes, img_width, img_height): for xml_file in os.listdir(xml_folder): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_folder, xml_file)) root tree.getroot() name xml_file.rsplit(., 1)[0] with open(os.path.join(txt_folder, name .txt), w) as f: for obj in root.findall(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n)转换后一定要抽查可视化结果把txt坐标画到图片上看框是否贴合目标。这一步能发现大量工具链产生的脏数据。3.3 数据集划分与增强策略数据按8:1:1划分为训练集、验证集、测试集。划分切不可直接按文件夹简单随机分更靠谱的方式是先按图片编号排序再隔N张取一张避免同一条视频片段拆出的连续帧同时出现在训练集和验证集中否则会产生数据泄漏验证指标虚高。增强策略方面YOLO训练框架默认自带Mosaic、随机翻转、HSV扰动、随机缩放等。对船舶场景我建议额外关注两点一是随机旋转增强不要开太大船舶有明确的上-下方向性翻转180度的样本会让模型困惑二是可以适量使用小尺度复制粘贴增强把船的目标缩放到很小尺寸贴到背景图片上专门补小目标样本。3.4 一份不规范标注的修复过程我接手过一份船舶数据标注格式混乱有的XML用像素坐标有的用比例坐标还有的类别名称带空格文件编码也不是UTF-8。我处理这类脏数据的经验是先写一个扫描脚本统计所有XML里的标签类别、坐标范围、文件数量把异常一次性暴露出来。常见异常有这三类框越界xmax大于图片宽度、框宽高为零、类别名前后带空格。处理方式是写清洗脚本统一修正并把修正后的可视化结果随机抽200张人工过一遍。这个过程很枯燥但做完之后训练会顺利很多。不要跳过数据清洗直接开训练否则后面排查loss不收敛时会更加痛苦。4. YOLOv12模型训练实战4.1 模型配置与参数选择训练脚本的核心逻辑非常简单用YOLO系列的标准训练方式from ultralytics import YOLO model YOLO(yolov12m.pt) model.train( datadataset/data.yaml, epochs150, batch16, imgsz640, device0, workers4, optimizerauto )参数选择记住原则即可。imgsz默认640如果数据集中小目标较多可以提到768或960但训练和推理速度都会下降。batch大小取决于显存6GB显存用8到16比较稳24GB显存可以开到32甚至更大。epochs建议先跑100轮看趋势如果验证集的mAP还在明显上升再续跑。模型本体选择上yolov12s速度快但精度相对低yolov12l精度高但推理慢我实测下来yolov12m在船舶场景里性价比最高精度足够推理速度也能接受。4.2 训练启动与过程监控训练启动后终端会实时打印每个epoch的结果包括精确率、召回率、mAP50和mAP50-95。刚开始mAP50会很低甚至一直跳来跳去这很正常不要看到前期指标差就中断训练。重点看loss曲线的整体下降趋势以及验证集mAP是否逐步抬升。把训练日志保存下来也是好习惯python train.py train.log 21训练结束后结果会生成在run/train/exp目录下里面有weights文件夹其中的best.pt表示验证集表现最好的权重last.pt表示最后一轮的权重。默认用best.pt不要顺手拿last.pt去部署两者在这个场景下差别可能很大。4.3 训练结果评估模型效果不能只看mAP数字还要结合混淆矩阵和PR曲线看。YOLO训练框架会自动生成混淆矩阵图它能直观显示哪些类别之间互相误判。比如我训练时发现货船和驳船会互相混淆原因是两者外形都偏方正颜色也接近只能靠增加两类样本和仔细检查错误标注来解决。评估指标这块Precision高说明误检少Recall高说明漏检少。对船舶识别我认为Recall优先宁可偶尔误报也不要漏掉船上目标。部署时可以通过调低置信度阈值换取更高召回率代价是误检框增多这个取舍要在项目说明里写清楚。4.4 训练时容易踩的坑训练中会踩的坑我整理成一张速查表现象原因处理方式loss迅速变成nan学习率过高或标注有0宽高降低学习率清洗异常标注训练很慢显存不够batch过大或imgsz过大减小batch开启混合精度训练验证集loss不降数据集划分泄露或增强过强重划分数据降低增强幅度训练正常但mAP很低类别编号冲突或数据集过小检查txt类别编号增加样本模型不收敛学习率策略不合适用optimizerauto让框架自动调这些坑里最隐蔽的就是类别编号错位。标注工具里的类别列表顺序和data.yaml里names顺序不一致时所有标注都等于错了一个编号训练出来的模型检测结果会张冠李戴。训练前用脚本随机抽几张图片把txt坐标画出来过目一遍能省掉大部分排查时间。5. UI界面与登录注册模块实现5.1 登录注册功能设计登录注册界面我用PyQt5实现数据存储用SQLite简单可靠不需要额外安装数据库服务。用户表结构设计成这样CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );密码不能存明文这是基本底线。我使用sha256加盐的方式处理注册时生成盐值并拼接密码再哈希import hashlib import secrets def make_password_hash(password: str) - str: salt secrets.token_hex(16) digest hashlib.sha256((salt password).encode()).hexdigest() return f{salt}${digest} def check_password(password: str, stored: str) - bool: salt, digest stored.split($) return hashlib.sha256((salt password).encode()).hexdigest() digest注册界面校验用户名唯一、密码长度不低于6位登录界面校验哈希值是否匹配。每次打开软件都从登录窗口进入登录成功后跳转主界面。这个设计虽然简单但确实拦截了大多数对直接绕过登录进入系统的操作尝试。5.2 主界面布局与功能入口主界面分为三个区域左侧是功能按钮面板包含加载模型、选择图片、选择视频、打开摄像头、开始识别、停止识别、保存结果等按钮中间大区域显示检测画面可以是图片、视频帧或摄像头实时画面右侧是结果信息栏滚动显示每条检测记录内容包括类别、置信度、中心坐标和框宽高。底部放状态栏显示当前模型状态、帧率和检测目标数。布局代码用QHBoxLayout和QVBoxLayout组合不要用绝对定位否则窗口缩放后控件会乱作一团。中间画面用QLabel控件设置setScaledContents(True)或者手动缩放QPixmap显示。5.3 检测线程与界面防卡顿设计PyQt5界面程序有一个铁律耗时的模型推理绝不能跑在主线程。否则点击识别按钮后界面会立即变成未响应用户会以为程序死了。我的做法是单独写一个继承QThread的推理线程通过信号把结果传回主线程from PyQt5.QtCore import QThread, pyqtSignal class DetectThread(QThread): frame_ready pyqtSignal(object) result_ready pyqtSignal(list) def __init__(self): super().__init__() self.running False self.frame None def run(self): model YOLO(model/best.pt) while self.running: if self.frame is not None: results model(self.frame) self.frame_ready.emit(self.frame) self.result_ready.emit(results[0].boxes.data.tolist()) def stop(self): self.running False摄像头取帧和模型推理要分开取帧可能50毫秒一帧推理可能要两三百毫秒一帧如果串在一个循环里画面就会卡成PPT。我的经验是摄像头线程只负责抓帧放到队列推理线程从队列取帧队列限制长度避免积压处理不过来时丢旧帧保新帧。5.4 检测结果展示逻辑模型推理得到的是一组坐标数据要在界面上显示带框的画面需要把标注绘制到图片上再转成QImage显示。绘制用OpenCVimport cv2 from PyQt5.QtGui import QImage, QPixmap def draw_and_show(frame, detections, names): for det in detections: x1, y1, x2, y2, conf, cls det[:6] label f{names[int(cls)]} {conf:.2f} color (0, 255, 0) cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), color, 2) cv2.putText(frame, label, (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) return QPixmap.fromImage(qimg.copy())这里必须调用qimg.copy()再转成QPixmap因为原QImage和OpenCV的缓冲区共享内存函数返回后缓冲区释放界面会出现黑屏或花屏这个坑我踩过不止一次。6. 模型部署与完整流程串联6.1 加载模型、摄像头调用与实时识别模型加载放在初始化阶段不要在每次识别时重新加载浪费大量时间。加载方法很简单from ultralytics import YOLO model YOLO(model/best.pt)实时摄像头识别流程是这样的摄像头打开后不断读取帧把帧送入推理线程推理线程经过模型推理后把结果绘制到画面再显示。PC内置摄像头通常要从索引0开始尝试Windows系统如果摄像头被别的软件占用cv2.VideoCapture(0)会返回False需要提示用户关闭其他占用摄像头的程序。帧率控制也很关键。如果模型推理慢而取帧快会在队列里堆积大量旧帧导致显示滞后越来越严重。我的做法是推理线程的处理速度决定了取帧节奏取帧线程每产生一帧先看队列是否已满满了就丢弃旧帧再放入当前帧保证显示的永远是最新画面。6.2 图片/视频导入识别图片识别最简单点击选择按钮后用QFileDialog拿到文件路径读取为图像数组送入模型把结果绘制后展示同时把检测明细追加到右侧信息栏。视频识别要对每一帧都执行推理帧率会直接取决于模型速度。处理视频时建议加一个简单的进度显示告诉用户当前处理到第几帧否则一个5分钟的视频在CPU上可能要跑十几分钟用户会以为卡死了。视频保存用OpenCV的VideoWriter参数注意编码格式要跟视频扩展名匹配。我在实际做的时候发现视频推理还有一个细节视频的帧率和图片分辨率不同模型输入是固定尺寸的640×640推理时框架会自动做letterbox填充。这个填充后得到的坐标会自动映射回原图尺寸但前提是推理时传的是原图帧而不是已经resize过的帧。如果你先把帧手动缩放了再传给模型输出的坐标定位就会偏差严重。6.3 从训练完成到UI可用的模型最后100米很多新手模型训练好了但接入UI时报错或者识别结果全乱问题基本出在模型文件路径和类别名称列表上。首先要确认UI代码里加载的是训练产物中的best.pt而不是验证时的临时权重。其次data.yaml里的names顺序必须和UI代码中显示的names一致。比如训练时names顺序是[cargo, passenger, fishing, cruise, tug, barge, sail]UI里列表写错顺序识别出来的船就会对不上类别名。最稳妥的方式是UI代码直接读取data.yaml文件不要手写两套列表。最后就是部署机的环境问题。如果在训练机上用GPU版PyTorch打过包迁移到没有GPU的电脑上运行会因为缺少CUDA依赖直接崩掉。部署前要在目标机器上安装CPU版PyTorch打包时看清楚依赖包含的是哪个版本。6.4 资源路径兼容性处理打包成exe后模型文件和资源文件的路径会变得很棘手。打包工具会把程序本体放到临时目录当前工作目录不再是程序所在目录导致找不到model/best.pt文件。我总结的经验是用一个统一的资源路径解析函数import sys def resource_path(relative_path): if hasattr(sys, _MEIPASS): base sys._MEIPASS else: base . return os.path.join(base, relative_path)打包时通过--add-data把model目录塞进包内运行时程序就能稳定找到模型文件。这个函数应该作为工具函数放在utils.py里所有涉及文件路径的地方都走它。7. 常见问题与实战排坑7.1 训练时loss不下降的排查训练前几轮loss纹丝不动甚至不降大多数人第一反应是加epochs但方向可能错了。我遇到loss不下降时按这个顺序排查先检查数据标注随机抽样本绘制框可视化再看学习率设置太大会震荡太小会停滞然后看数据量是否过少少于几百张时模型学不到稳定特征最后看模型结构是否过大在小数据集上大模型反而更容易过拟合到训练集上。有一个非常实用的技巧先用yolov12s模型跑20轮快速实验如果mAP50能从0涨到0.3以上说明数据和标注没问题再换大模型正式训练。如果小模型都不涨多半是数据链路出了毛病这时候调再多的超参数都没用。7.2 UI界面卡顿、无响应的定位界面卡顿的常见原因有三个。第一是主线程做了模型推理直接导致按钮点击无反馈第二是视频循环里使用了time.sleep来进行帧率控制实际上阻塞了Qt事件循环第三是日志区无限追加内容控件刷新压力大越用越卡。排查思路是先在关键操作处打印时间戳确认耗时集中在哪个环节。如果是推理耗时就必须把推理移到独立线程如果是日志刷新问题就限制文本框最大行数超过1000行自动清掉旧记录。Qt信号槽还有一个隐藏坑如果推理线程高频发送信号而界面主线程处理不过来事件队列会膨胀界面最终卡死。解决办法是信号发送节流简单做法是在发送信号的循环里加一个最小时间间隔判断。7.3 船舶检测误检漏检的优化方向误检和漏检不是无解的问题大部分可以通过参数调整和样本补充改善。漏检小目标时优先把imgsz从640提升到960再检查置信度阈值是否设置过高。我常用阈值是0.25到0.35之间低于0.1会进入大量假阳框。进一步就是把小目标样本用数据增强放大或者从s模型升级到m模型。误检频繁时优先检查是不是类别混淆问题观察混淆矩阵找出容易出错的类别对。比如泊位上的卡车、集装箱有时候会被误检成驳船这种情况光调阈值没用要收集更多负样本或者给模型补充该类别的反向样本让模型学会区分。还有一种高发问题船舶在特殊角度下自混淆比如一艘船的侧面和斜前方看起来像两种船。解决办法是标注时统一框选规则明确边界定义同时保证训练数据里包含多个角度的同类船图片。7.4 打包exe时容易忽略的细节用PyInstaller打包PyQt5项目常见错误集中在依赖收集不全。opencv的dll、PyQt5的插件、模型权重文件任何一个缺失都导致程序在用户机器上启动即崩溃。打包命令我建议写成这样pyinstaller -w -F --add-data model/best.pt;model --add-data app;app main.py打包完成后一定要在干净环境里测试也就是在没安装Python和依赖的电脑上运行一遍。如果程序启动控制台输出找不到模块就用--hidden-import手动补充。关于路径中文的问题Windows下如果程序路径包含中文某些OpenCV功能会出现读取不到文件的故障。最省心的做法是规范使用前面说过的resource_path统一读取路径并建议用户不要装在中文目录下。最后分享一个小经验生产和演示版本的差异要提前考虑。训练时用yolov12m全精度模型追求准确度但最终给用户演示的机器如果性能不足可以导出成一个量化或半精度版本UI代码预留模型文件切换功能这样同一套界面可以按机器性能灵活选模型演示时更加从容。