ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv8与PyQt5的水底垃圾生物检测识别系统设计与实现

基于YOLOv8与PyQt5的水底垃圾生物检测识别系统设计与实现 基于深度学习YOLOv8PyQt5水底海底垃圾生物探测器检测识别系统设计开篇先聊一个实际的工程问题水下场景的视觉检测并不像陆地目标检测那样“拿个目标检测模型就能跑”。水体散射、光照衰减、悬浮颗粒、鱼类游动造成的形变都会让普通检测模型精度大幅下降。加上水下机器人ROV/AUV通常算力有限模型既要准又要轻这种需求在垃圾清理、海底生物资源调查、海洋生态监测里非常常见。本文将完整拆解一套基于 YOLOv8 PyQt5 的水底目标检测识别系统覆盖数据集准备、YOLOv8模型训练、模型推理封装、PyQt5图形界面开发、以及常见问题排查。适合正在做毕业设计、课程项目或水下视觉工程落地的同学参考。1. 水底检测系统的核心概念与整体架构1.1 什么是水底目标检测识别系统水底目标检测识别系统简单来说就是利用水下摄像头采集视频或图像通过深度学习模型自动识别出画面中的两类目标一类是人为垃圾比如塑料瓶、渔网、金属罐、玻璃瓶另一类是海底生物比如鱼类、海参、螃蟹、贝类、珊瑚等。这和水面或陆地上做目标检测最大的差异在于图像质量。水下图像普遍存在以下问题颜色偏移尤其是红色通道衰减严重画面整体偏蓝绿色。对比度低远处物体几乎融入背景。悬浮颗粒和微小气泡会产生大量噪点容易造成误检。生物目标姿态多变且部分生物具备伪装色。因此一个工程上可用的水下检测系统通常除了目标检测模型本身还需要图像预处理模块、可交互的 GUI 界面模块、以及模型训练和评估模块。1.2 为什么选择 YOLOv8YOLOYou Only Look Once系列是目前工业界落地最广的目标检测算法之一核心思想是把目标检测当作回归问题单次前向传播同时输出目标类别和边界框。YOLOv8 是 Ultralytics 公司于 2023 年发布的目标检测模型相比前代版本它有以下优势网络结构上引入 C2f 模块增强了梯度流动特征提取能力更强。支持 Anchore-Free 检测头减少了锚框调参的麻烦。支持实例分割、姿态估计、分类、目标检测多种任务统一在同一框架内。训练、验证、导出推理链路完整搭配 ultralytics 库上手非常快。模型体积从 n/s/m/l/x 可选方便在嵌入式设备或普通 PC 上部署。对于水下检测这种对实时性有一定要求、算力往往受限的场景YOLOv8n 或 YOLOv8s 是很合适的选择。如果你实际追求更高的精度也可以选择 m 或 l 版本但推理速度会有所下降。1.3 为什么用 PyQt5 做交互界面目标检测模型本身只负责“识别”但要构成一个用户能用的系统还需要 GUI 来展示视频流、显示检测结果、控制摄像头、保存检测记录、查看统计信息等。PyQt5 是 Qt 框架的 Python 绑定是目前 Python 桌面开发中功能最完整、生态最成熟的 GUI 工具之一。用它实现水底检测系统主要有几个好处内置 QThread 多线程支持可以把视频采集和检测放进子线程避免界面卡死。QGraphicsView / QLabel 可以灵活渲染图像帧支持在画面中绘制检测框和标签。信号槽机制适合做“检测结果实时刷新”“识别记录表格更新”这类异步交互。打包成 exe 也比较容易方便交付给没有 Python 环境的用户。1.4 系统整体架构整个系统按功能可以拆成 4 层层级功能技术实现数据层水下图像/视频采集本地视频文件、摄像头、图片目录算法层目标检测推理YOLOv8 ONNX 或 PyTorch 模型服务层检测结果逻辑处理类别过滤、置信度阈值、统计逻辑表现层用户交互界面PyQt5包括视频显示、检测结果列表、统计面板整体流程为输入图像/视频帧 → 图像预处理 → YOLOv8 推理 → 后处理NMS、阈值过滤→ 绘制检测框和标签 → GUI 展示 → 保存结果到本地文件。2. 环境准备与版本说明2.1 开发环境概览本文示例以 Windows 11 系统为例实际 Linux 系统同理。推荐的基础环境如下操作系统Windows 10/11 或 Ubuntu 20.04/22.04Python 版本3.8 ~ 3.11推荐 3.9 或 3.10深度学习框架PyTorch 2.0 及以上YOLOv8 库ultralyticsGUI 框架PyQt5图像处理库OpenCV-Python开发工具PyCharm 或 VS Code如果你使用的是 NVIDIA 显卡建议安装 CUDA 和 cuDNN实际版本要根据你的 GPU 驱动和 PyTorch 支持情况来确定。如果电脑没有独立显卡也可以使用 CPU 推理只是速度会慢很多。2.2 安装命令建议先创建独立的虚拟环境避免包冲突。# 创建虚拟环境conda 示例 conda create -n underwater_yolo python3.9 # 激活虚拟环境 conda activate underwater_yolo # 安装 PyTorchCPU 版本GPU 版本请去 PyTorch 官网按实际环境生成命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 YOLOv8 相关库 pip install ultralytics # 安装 GUI 和图像处理库 pip install PyQt5 pip install opencv-python pip install numpy这里需要特别说明如果使用 GPU 训练或推理请务必根据你的 CUDA 版本安装对应版本的 PyTorch。CPU 版本虽然也能跑但在训练 YOLOv8 时速度会比较慢一般只建议用于验证流程。2.3 安装验证安装完成后可以运行下面这段代码检查关键依赖是否正常import torch import ultralytics import cv2 import PyQt5 print(PyTorch version:, torch.__version__) print(CUDA available:, torch.cuda.is_available()) print(Ultralytics version:, ultralytics.__version__) print(OpenCV version:, cv2.__version__) print(PyQt5 version:, PyQt5.QtCore.PYQT_VERSION_STR)如果输出没有报错说明环境基本正常。3. 水下数据集准备与处理3.1 数据采集思路一个检测系统的上限很大程度上取决于数据质量。水底检测数据可以通过以下渠道获取公开数据集比如 underwater object detection 相关的公开数据或自行搜索“underwater trash dataset”“deepfish dataset”等。水下机器人拍摄如果项目有 ROV 或水下摄像头可以自己去湖泊、近海区域采集视频再抽取帧。视频抽帧把视频按每秒 1~5 帧进行抽帧再用 LabelImg 或 Roboflow 标注。网络公开图片需要注意版权问题部分科考机构公开的数据允许学术使用。本文以自建小型数据集为例类别可以设为 6 类0: plastic_bottle塑料瓶 1: metal_can金属罐 2: fishing_net渔网 3: fish鱼类 4: crab螃蟹 5: sea_cucumber海参注意类别编号从 0 开始标注时不能跳号建议在 data.yaml 里按顺序定义。3.2 数据标注与格式转换YOLOv8 使用 YOLO TXT 格式标注文件。每张图片对应一个同名 .txt 文件文件每一行的格式为class_id x_center y_center width height其中 x_center、y_center、width、height 都是归一化到 0~1 之间的浮点数基于图片宽度和高度的比例。推荐使用 LabelImg 或 labelme 进行标注。标注完成后需要将数据按以下目录结构整理datasets/ └── underwater/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/3.3 编写数据集配置文件在项目根目录下创建data.yaml# 文件路径data.yaml path: datasets/underwater # 数据集根目录相对于运行目录 train: images/train val: images/val nc: 6 names: 0: plastic_bottle 1: metal_can 2: fishing_net 3: fish 4: crab 5: sea_cucumber如果你的训练数据很少比如每个类别只有几十张建议先做数据增强。YOLOv8 训练时会自动使用 Mosaic、翻转、颜色扰动等增强策略但真实水下数据还是建议专门补充一些低光照、浑浊水体、远距离小目标的样本这样可以显著提高模型在真实场景中的泛化能力。3.4 水下图像预处理水下图像普遍偏蓝绿色且对比度低。为了提升检测效果可以在推理之前对图像做预处理。常用方法有灰度世界算法Gray World对颜色通道做白平衡恢复真实颜色。直方图均衡化或 CLAHE增强局部对比度。暗通道先验去雾算法适合浑浊水体场景但计算量较大。下面给出一个基于 CLAHE 的预处理函数实测对水下图像边缘和纹理细节有一定帮助import cv2 def underwater_preprocess(image): 水下图像预处理 1. 对 LAB 空间的 L 通道做 CLAHE 增强 2. 对颜色进行简单的白平衡校正 # 白平衡灰度世界算法 result cv2.cvtColor(image, cv2.COLOR_BGR2LAB) l_channel, a_channel, b_channel cv2.split(result) clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8)) l_channel clahe.apply(l_channel) merged cv2.merge((l_channel, a_channel, b_channel)) corrected cv2.cvtColor(merged, cv2.COLOR_LAB2BGR) # 轻度锐化 kernel np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], dtypenp.float32) sharpened cv2.filter2D(corrected, -1, kernel) return sharpened不过需要说明的是图像预处理只有在和训练数据分布不一致的情况下才有明显正向作用。如果你训练时用的就是原始水下图像推理阶段尽量保持相同的处理逻辑不要随意加预处理否则可能反而降低精度。4. 基于 YOLOv8 的模型训练4.1 开始训练使用 ultralytics 库训练模型非常简单。在项目根目录下创建train.py# 文件路径train.py from ultralytics import YOLO def main(): # 加载预训练权重n 表示 nano 版本速度最快、体积最小 model YOLO(yolov8n.pt) # 开始训练 model.train( datadata.yaml, # 数据集配置文件 epochs150, # 训练轮数 imgsz640, # 输入图片尺寸 batch16, # 批大小显存不够就减小 device0, # 0 表示第一张 GPUCPU 可改为 cpu workers4, # 数据加载线程数 projectruns, # 结果保存目录 nameunderwater_yolov8, # 本次实验名称 patience20, # 早停轮数 pretrainedTrue, # 使用 COCO 预训练权重迁移学习 ) if __name__ __main__: main()这里有几个参数要重点解释epochs训练轮数。水下数据集如果比较小100~200 轮足够。轮数不是越多越好轮数过多并且没有早停机制很容易过拟合。imgsz输入图片尺寸。默认 640如果小目标比较多可以尝试 960 或 1280但训练和推理速度会明显变慢。batch批大小取决于显存一般 8~32 之间。patience如果验证集上的指标连续 20 轮没有提升训练会自动停止。这个机制可以帮你节省时间。训练过程中终端会实时输出每轮的损失值、精度Precision、召回率Recall、mAP50、mAP50-95 等指标。训练完成后结果保存在runs/underwater_yolov8目录下包括weights/best.pt验证集表现最好的权重文件。weights/last.pt最后一轮权重文件。results.png训练过程的损失曲线和指标曲线图。confusion_matrix.png混淆矩阵。val_batch0_pred.jpg验证集部分预测结果。4.2 模型评估训练完成后我们需要单独在验证集上评估模型效果。创建val.py# 文件路径val.py from ultralytics import YOLO def main(): model YOLO(runs/underwater_yolov8/weights/best.pt) metrics model.val(datadata.yaml, splitval) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map) print(Precision:, metrics.box.mp) print(Recall:, metrics.box.mr) if __name__ __main__: main()注意metrics.box.map是 mAP50-95这个指标是水下检测任务中比较有参考价值的因为水下目标往往尺度差异大、遮挡严重如果 mAP50-95 偏低说明模型对目标边界框的定位精度还不够好。4.3 模型导出为 ONNX实际部署到 GUI 系统时使用 PyTorch 权重文件也可以但如果要提升推理速度或者后续部署到 Jetson 等嵌入式平台建议导出 ONNX 格式from ultralytics import YOLO model YOLO(runs/underwater_yolov8/weights/best.pt) model.export(formatonnx, imgsz640, halfFalse, simplifyTrue)导出成功后会在同目录下生成best.onnx文件后面的推理模块可以优先使用 ONNX Runtime 加载它。5. 构建 YOLOv8 推理服务模块5.1 封装推理类为了让 GUI 代码更简洁我们把模型推理封装成一个独立的类。这样 GUI 只负责显示不关心模型细节。创建detector.py# 文件路径detector.py import cv2 import numpy as np from ultralytics import YOLO class UnderwaterDetector: 水下目标检测推理封装类 支持加载 YOLOv8 PyTorch 权重文件 def __init__(self, model_path: str, conf_thres: float 0.35, iou_thres: float 0.45): self.model YOLO(model_path) self.conf_thres conf_thres self.iou_thres iou_thres # 类别名称与颜色映射 self.class_names [ plastic_bottle, metal_can, fishing_net, fish, crab, sea_cucumber, ] self.class_colors { plastic_bottle: (0, 255, 255), metal_can: (0, 128, 255), fishing_net: (0, 0, 255), fish: (0, 255, 0), crab: (255, 0, 255), sea_cucumber: (255, 255, 0), } def predict(self, image: np.ndarray): 对单帧图像进行推理 返回处理后图像、检测结果列表 results self.model.predict( sourceimage, confself.conf_thres, iouself.iou_thres, verboseFalse, ) boxes results[0].boxes detections [] if boxes is not None and len(boxes) 0: xyxy boxes.xyxy.cpu().numpy() confs boxes.conf.cpu().numpy() cls_ids boxes.cls.cpu().numpy().astype(int) for i in range(len(xyxy)): x1, y1, x2, y2 xyxy[i] class_id cls_ids[i] class_name self.class_names[class_id] confidence float(confs[i]) detections.append({ bbox: [float(x1), float(y1), float(x2), float(y2)], class_id: int(class_id), class_name: class_name, confidence: confidence, }) return detections def draw_detections(self, image: np.ndarray, detections: list) - np.ndarray: 在图像上绘制检测框和标签 for det in detections: x1, y1, x2, y2 det[bbox] class_name det[class_name] confidence det[confidence] color self.class_colors.get(class_name, (255, 255, 255)) cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), color, 2) label f{class_name} {confidence:.2f} (text_width, text_height), baseline cv2.getTextSize( label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2 ) cv2.rectangle( image, (int(x1), int(y1) - text_height - baseline), (int(x1) text_width, int(y1)), color, -1, ) cv2.putText( image, label, (int(x1), int(y1) - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 2, ) return image5.2 推理并发与性能优化说明GUI 视频流推理有两个常见做法同步推理在视频帧回调函数里直接推理代码简单但是推理期间界面会卡顿。异步推理视频采集线程负责读帧推理线程负责跑模型通过队列传递图像帧。在后面的 PyQt5 界面中推荐使用 QThread 队列的方式保证视频播放的流畅性。如果单帧推理耗时超过 100ms用户体验会明显受到影响。6. PyQt5 水底检测系统界面完整实现6.1 界面功能设计我们的 GUI 系统计划实现以下功能打开本地视频文件并循环播放。打开摄像头实时检测。实时显示检测结果画面并绘制检测框、类别和置信度。显示检测统计信息比如各类别数量。支持调节置信度阈值。支持截图保存当前检测画面。检测结果记录到表格中。主窗口布局分为三块左侧是视频显示区域右侧是检测统计和控制面板底部是检测记录表格。6.2 主窗口与视频线程实现创建main_window.py# 文件路径main_window.py import sys import cv2 import numpy as np from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import ( QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QGroupBox, QTableWidget, QTableWidgetItem, QHeaderView, QSlider, QFileDialog, QMessageBox, QApplication, QSpinBox ) from detector import UnderwaterDetector class VideoThread(QThread): 视频采集与检测线程 负责读取视频帧、调用模型推理、把结果显示图像发回界面 change_pixmap_signal pyqtSignal(np.ndarray) update_stats_signal pyqtSignal(dict) update_log_signal pyqtSignal(dict) def __init__(self, parentNone): super().__init__(parent) self.cap None self.detector None self.running False self.conf_thres 0.35 def set_source(self, source_path, detector: UnderwaterDetector): self.cap cv2.VideoCapture(source_path) self.detector detector self.running True def set_conf_thres(self, conf): self.conf_thres conf def stop(self): self.running False self.wait() def run(self): while self.running: ret, frame self.cap.read() if not ret: # 视频播放完毕则重新回到第一帧 self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0) continue # 可选水底图像预处理 # frame underwater_preprocess(frame) # 推理 detections self.detector.predict(frame) # 更新统计 stats {total: len(detections)} for det in detections: class_name det[class_name] stats[class_name] stats.get(class_name, 0) 1 # 绘制检测框 annotated self.detector.draw_detections(frame.copy(), detections) # 发送到界面显示 self.change_pixmap_signal.emit(annotated) self.update_stats_signal.emit(stats) # 记录前几条结果到表格 if detections: self.update_log_signal.emit(detections[0]) self.cap.release()6.3 主窗口实现创建main_window.py的窗口部分这里会用到信号槽绑定把上面线程里产生的信号和界面显示函数连接起来。同时注意关闭窗口时需要先停止线程避免程序退出时崩溃。# 继续 main_window.py class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(水底海底垃圾与生物检测识别系统) self.setMinimumSize(1280, 720) self.detector UnderwaterDetector( model_pathruns/underwater_yolov8/weights/best.pt, conf_thres0.35 ) self.thread VideoThread() self.thread.change_pixmap_signal.connect(self.update_image) self.thread.update_stats_signal.connect(self.update_stats) self.thread.update_log_signal.connect(self.update_log) self.init_ui() self.current_source None def init_ui(self): central_widget QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout QHBoxLayout(central_widget) # 左侧视频显示区 left_widget QWidget() left_layout QVBoxLayout(left_widget) self.video_label QLabel(请打开视频或摄像头开始检测) self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setMinimumSize(800, 600) self.video_label.setStyleSheet( border: 2px solid #333; background-color: #000; color: #fff; font-size: 16px; ) left_layout.addWidget(self.video_label) main_layout.addWidget(left_widget, stretch3) # 右侧控制面板 right_widget QWidget() right_layout QVBoxLayout(right_widget) # 文件控制组 file_group QGroupBox(文件与视频控制) file_layout QVBoxLayout(file_group) self.btn_open_video QPushButton(打开视频文件) self.btn_open_video.clicked.connect(self.open_video) file_layout.addWidget(self.btn_open_video) self.btn_open_camera QPushButton(打开摄像头) self.btn_open_camera.clicked.connect(self.open_camera) file_layout.addWidget(self.btn_open_camera) self.btn_stop QPushButton(停止检测) self.btn_stop.clicked.connect(self.stop_detection) file_layout.addWidget(self.btn_stop) self.btn_save QPushButton(保存截图) self.btn_save.clicked.connect(self.save_screenshot) file_layout.addWidget(self.btn_save) right_layout.addWidget(file_group) # 置信度阈值控制 conf_group QGroupBox(检测参数) conf_layout QVBoxLayout(conf_group) conf_layout.addWidget(QLabel(置信度阈值)) self.slider_conf QSlider(Qt.Horizontal) self.slider_conf.setRange(10, 90) self.slider_conf.setValue(35) self.slider_conf.valueChanged.connect(self.on_conf_changed) conf_layout.addWidget(self.slider_conf) self.conf_label QLabel(当前阈值: 0.35) conf_layout.addWidget(self.conf_label) right_layout.addWidget(conf_group) # 统计信息组 stats_group QGroupBox(检测统计) stats_layout QVBoxLayout(stats_group) self.stats_label QLabel(目标总数: 0) stats_layout.addWidget(self.stats_label) self.detail_label QLabel(垃圾: 0\n生物: 0) stats_layout.addWidget(self.detail_label) right_layout.addWidget(stats_group) main_layout.addWidget(right_widget, stretch1) # 底部记录表格 bottom_widget QWidget() bottom_layout QVBoxLayout(bottom_widget) bottom_layout.addWidget(QLabel(检测记录)) self.table QTableWidget(0, 4) self.table.setHorizontalHeaderLabels([类别, 置信度, X1, Y1]) header self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.Stretch) bottom_layout.addWidget(self.table) main_outer_layout QVBoxLayout() main_outer_layout.addLayout(main_layout) main_outer_layout.addWidget(bottom_widget) main_outer_layout.setStretch(0, 4) main_outer_layout.setStretch(1, 1) # 注意上面这种 addLayout 到 central_widget 的方式需要调整。 # 为方便理解实际运行时建议用另一个 QWidget 包住整体。 central_widget.setLayout(main_outer_layout) # ------- 槽函数 ------- def open_video(self): path, _ QFileDialog.getOpenFileName( self, 选择视频文件, ., 视频文件 (*.mp4 *.avi *.mov *.mkv) ) if not path: return self.stop_detection() self.current_source path self.thread.set_source(path, self.detector) self.thread.start() def open_camera(self): self.stop_detection() self.thread.set_source(0, self.detector) self.thread.start() def stop_detection(self): if self.thread.isRunning(): self.thread.stop() def on_conf_changed(self, value): conf value / 100.0 self.conf_label.setText(f当前阈值: {conf:.2f}) self.thread.set_conf_thres(conf) def update_image(self, frame): rgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qt_image QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap QPixmap.fromImage(qt_image) # 缩放显示但保持宽高比 scaled_pixmap pixmap.scaled( self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.video_label.setPixmap(scaled_pixmap) def update_stats(self, stats): total stats.get(total, 0) self.stats_label.setText(f目标总数: {total}) # 简单区分垃圾和生物可以在这里按类别统计 # 这里仅做示例实际按项目类别名称区分 self.detail_label.setText(f检测到目标: {total}) def update_log(self, det): row self.table.rowCount() self.table.insertRow(row) self.table.setItem(row, 0, QTableWidgetItem(det[class_name])) self.table.setItem(row, 1, QTableWidgetItem(f{det[confidence]:.2f})) self.table.setItem(row, 2, QTableWidgetItem(f{det[bbox][0]:.1f})) self.table.setItem(row, 3, QTableWidgetItem(f{det[bbox][1]:.1f})) def save_screenshot(self): if self.video_label.pixmap().isNull(): QMessageBox.warning(self, 提示, 当前没有画面可保存) return path, _ QFileDialog.getSaveFileName( self, 保存截图, screenshot.png, PNG 图片 (*.png) ) if path: self.video_label.pixmap().save(path) QMessageBox.information(self, 完成, f截图已保存到 {path}) def closeEvent(self, event): self.stop_detection() event.accept()上面的代码是一个可运行的最小示例。但有一点需要说明为了让代码片段快速理解上面的中央布局写法做了简化。实际界面需要把左右布局和底部表格组合好你可以在工程里调整为QGridLayout或嵌套QSplitter效果更好。6.4 启动入口创建app.py# 文件路径app.py import sys from PyQt5.QtWidgets import QApplication from main_window import MainWindow def main(): app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_()) if __name__ __main__: main()运行方式python app.py如果没有报错界面应该可以正常打开。点击“打开视频文件”选择一个水下视频系统就会开始实时检测。7. 完整数据集训练与界面联调运行演示7.1 端到端流程回顾从零开始构建完整系统节奏可以按下面步骤走整理水下数据集划分 train/val。编写data.yaml。运行train.py训练模型得到best.pt。用val.py在验证集上评估模型记录 mAP50、mAP50-95 等指标。修改main_window.py中的model_path为训练好的权重路径。运行app.py打开视频或摄像头验证检测效果。7.2 运行效果预期在视频推理过程中你应该能看到视频画面流畅播放目标被彩色矩形框标出。每个检测框左上角显示类别名称和置信度。右侧统计区域显示当前帧目标总数。底部表格持续新增检测记录。如果某个类别频繁漏检可以考虑降低置信度阈值到 0.25 左右或者在训练集中补充该类别更多样本。8. 常见问题与排查思路在开发和调试水下 YOLOv8 PyQt5 系统的过程中最容易遇到的坑有以下几类。8.1 PyQt5 安装失败或者 import 报错问题现象常见原因解决思路ModuleNotFoundError: No module named PyQt5没有安装 PyQt5pip install PyQt5安装后from PyQt5.QtCore报错Python 版本与 PyQt5 版本不兼容使用 Python 3.8~3.10升级 pip 后重装界面运行时直接崩溃多个 Qt 库冲突检查是否有PySide2/PySide6同时存在建议只保留一个8.2 摄像头打开失败先确认摄像头是否被其他程序占用再检查 OpenCV 是否能打开import cv2 cap cv2.VideoCapture(0) ret, frame cap.read() print(ret, frame.shape) cap.release()如果上面代码能正常读到图像说明摄像头本身没问题。此时再把 PyQt5 的视频线程逻辑打开。8.3 视频播放不流畅或界面卡顿原因通常有两个模型推理耗时太长把模型从 YOLOv8m 换成 YOLOv8n或者启用 ONNX Runtime 推理。视频帧分辨率太高可以在读取视频后先用cv2.resize缩小到 640 或 960 宽度再送进模型。8.4 训练时 loss 不下降或 mAP 为 0这种情况优先检查数据集标注data.yaml中 names 是否从 0 开始连续编号。labels/train里的 txt 文件格式是否正确是否存在越界坐标。图片与标注文件是否一一对应有没有存在没有标注文件的图片。类别数量是否特别少模型的 mAP 在数据量很少时波动极大。8.5 PyQt5 窗口启动后没有画面可能的坑是信号没有正确连接。检查VideoThread的实例是否只创建了一次change_pixmap_signal是否在set_source之前或之后连接。建议在窗口初始化时先self.thread VideoThread()在启动视频前再调用set_source。9. 最佳实践与工程化建议9.1 数据与标注管理数据管理是水下目标检测项目中最容易被低估的一环。建议使用 Roboflow 或 LabelImg 作为标注工具每次训练前做数据划分并固定随机种子保证实验可复现。水下数据的标注规范尽量统一比如被遮挡超过 50% 的生物是否标注需要提前约定不然模型很难学稳定。9.2 模型部署方式如果最终系统要交付给测试人员使用建议把 YOLOv8 权重导出为 ONNX并使用 ONNX Runtime 推理这样可以减少依赖体积、提升推理速度。把检测器封装成 onnx 推理类后GUI 层几乎不需要改动。下面是一个简单的 ONNX Runtime 推理封装示例思路import onnxruntime as ort import numpy as np class ONNXDetector: def __init__(self, onnx_path): self.session ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.input_shape self.session.get_inputs()[0].shape def preprocess(self, image): # letterbox resize 保持宽高比 # 归一化、通道转换 HWC - CHW pass def predict(self, image): # 预处理 - session.run - 后处理NMS - 返回检测结果 pass9.3 多线程与资源释放在 PyQt5 中使用 QThread 时必须在窗口关闭事件里先停止线程再退出程序。否则容易出现QThread: Destroyed while thread is still running崩溃。建议在closeEvent中使用self.thread.stop()和self.thread.wait()确保线程安全退出。9.4 日志与检测结果保存正式项目中检测结果不能只显示在界面上。建议增加以下能力把每一帧的检测结果写入 CSV 文件包含时间戳、类别、置信度、坐标。检测到目标时自动保存当前帧原始图像便于后续分析误检和漏检。使用 Python 标准库logging记录推理耗时和异常信息。9.5 安全性与合规边界如果系统后续用于真实海域或水产养殖环境需要注意三点第一检测模型的输出只作为辅助决策不能直接代替人工对生态或捕捞行为做判断第二在采集水下数据时应遵守当地海洋生态保护法规避免对敏感区域造成干扰第三涉及商业部署时对模型精度需要有完整的验证报告和测试样本集。10. 总结与学习路线本文从项目背景、系统架构、数据准备、YOLOv8 模型训练、PyQt5 界面开发到常见问题排查完整梳理了一套水底海底垃圾生物检测识别系统的设计思路。核心收获有三点YOLOv8 适合作为水下目标检测的基础模型配合迁移学习和数据增强在数据量有限的情况下依然能获得可用精度。PyQt5 的 QThread 信号槽机制可以稳定承载视频流与检测推理之间的并发交互。水下视觉系统的难点不仅在模型数据质量、图像预处理和工程封装同样决定最终交付效果。接下来你可以继续深入学习的方向包括使用 SAHI 切片推理解决水下小目标检测问题。尝试 YOLOv8-seg 做水下目标实例分割输出目标轮廓而不是矩形框。把模型导出为 TensorRT 引擎部署到 NVIDIA Jetson 系列设备上做实时检测。结合跟踪算法如 ByteTrack实现水下目标的持续计数和轨迹分析。如果有条件建议自己采集一段真实水下视频按本文流程完整训练一次。只有亲自动手跑一遍才能真正理解损失曲线、mAP 变化和界面联调过程中的各种细节。希望这篇文章能帮你少走一些弯路快速搭建出属于自己的水下检测识别系统。
返回列表