在实际海洋生态监测和水产养殖项目中,传统的人工水下调查方式不仅成本高昂、效率低下,还面临安全风险。基于深度学习的计算机视觉技术为水下生物识别提供了新的解决方案,而 YOLOv8 作为当前最先进的目标检测框架之一,在实时性和准确性方面表现出色。本文将详细介绍如何从零构建一个完整的 YOLOv8 水下生物识别检测系统,能够准确识别海胆、海参、扇贝、海星和水草五类目标,并提供完整的项目源码、数据集和模型权重。
这个系统特别针对水下图像的光线散射、颜色失真、低对比度等挑战进行了优化,支持图片检测、视频检测和摄像头实时检测三种模式,并配备了直观的 PyQt5 图形界面。无论你是从事海洋研究的科研人员、水产养殖的技术工程师,还是想要学习 YOLOv8 实战应用的开发者,都能通过本文掌握完整的技术实现路径。
1. 理解 YOLOv8 在水下生物检测中的技术优势
1.1 为什么选择 YOLOv8 而不是传统图像处理方法
水下生物检测面临的核心挑战在于图像质量的严重退化。传统基于颜色阈值、边缘检测或模板匹配的方法在水下环境中往往失效,主要原因包括:
- 颜色失真:水体对不同波长光线的吸收程度不同,导致红色和黄色波段严重衰减,图像整体呈现蓝绿色调
- 雾化效应:水中悬浮颗粒造成的光线散射使图像对比度降低,细节模糊
- 光照不均:自然光在水下的穿透深度有限,加上人工光源的局部照射,形成强烈的明暗反差
- 动态模糊:水流运动和水生生物的活动导致拍摄图像模糊
YOLOv8 通过端到端的深度学习方式,能够从大量样本中学习到这些退化因素的不变特征。与两阶段检测器相比,YOLOv8 的单阶段设计在保持高精度的同时实现了更快的推理速度,这对于需要实时反馈的水下监测场景至关重要。
1.2 YOLOv8 模型家族的选型考量
YOLOv8 提供了从 Nano 到 Extra-Large 的多种规模模型,针对水下生物检测的具体需求,选型时需要权衡以下因素:
| 模型规格 | 参数量 | 适用场景 | 水下检测建议 |
|---|---|---|---|
| YOLOv8n | ~3.2M | 嵌入式设备、移动端 | 适合算力受限的水下机器人 |
| YOLOv8s | ~11.2M | 实时检测、边缘计算 | 推荐用于大部分水下监测场景 |
| YOLOv8m | ~25.9M | 平衡速度与精度 | 适合固定式高清监控设备 |
| YOLOv8l | ~43.7M | 高精度检测 | 用于科研级图像分析 |
| YOLOv8x | ~68.2M | 极致精度需求 | 计算资源充足的研究环境 |
对于一般的水下生物检测项目,YOLOv8s 在精度和速度之间提供了最佳平衡。如果部署在算力受限的水下机器人上,可以考虑 YOLOv8n;而对精度要求极高的科研应用,YOLOv8l 或 YOLOv8x 更为合适。
1.3 水下生物检测的特殊技术考量
与常规目标检测相比,水下生物检测需要特别关注以下几个方面:
- 小目标检测:许多水下生物在图像中占比较小,需要调整模型锚框尺寸和特征金字塔设计
- 遮挡处理:水草遮挡、生物间重叠是常见现象,模型需要具备较强的部分遮挡识别能力
- 多尺度适应性:不同拍摄距离和镜头焦距导致的目标尺度变化需要模型具备多尺度检测能力
- 类别不平衡:某些稀有物种的样本数量可能远少于常见物种,需要采用合适的采样策略
2. 项目环境配置与依赖管理
2.1 创建专用的 Python 虚拟环境
为每个深度学习项目创建独立的虚拟环境是避免依赖冲突的最佳实践。推荐使用 Anaconda 进行环境管理:
# 创建 Python 3.9 虚拟环境 conda create -n yolov8_underwater python=3.9 # 激活环境 conda activate yolov8_underwater选择 Python 3.9 的原因是它在稳定性与最新特性支持之间取得了良好平衡,且与主要深度学习框架的兼容性经过充分验证。
2.2 安装 PyTorch 及相关依赖
根据硬件配置选择合适的 PyTorch 版本。如果使用 NVIDIA GPU,需要先确认 CUDA 版本:
# 查看 CUDA 版本 nvidia-smi # 安装对应版本的 PyTorch(以 CUDA 11.8 为例) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118如果没有 GPU 或 CUDA 不可用,安装 CPU 版本:
pip install torch torchvision torchaudio2.3 安装项目特定依赖
创建requirements.txt文件,包含项目所需的所有依赖:
ultralytics==8.0.196 opencv-python==4.8.1.78 PyQt5==5.15.9 numpy==1.24.3 pillow==10.0.0 scipy==1.11.3 matplotlib==3.7.2 seaborn==0.12.2 pandas==2.0.3 albumentations==1.3.1 tqdm==4.65.0安装依赖包:
pip install -r requirements.txt2.4 验证环境配置
创建验证脚本env_check.py确认环境配置正确:
import torch import cv2 from PyQt5.QtWidgets import QApplication import sys def check_environment(): print("=== 环境配置验证 ===") # 检查 PyTorch print(f"PyTorch 版本: {torch.__version__}") print(f"CUDA 可用: {torch.cuda.is_available()}") if torch.cuda.is_available(): print(f"GPU 设备: {torch.cuda.get_device_name(0)}") # 检查 OpenCV print(f"OpenCV 版本: {cv2.__version__}") # 检查 PyQt5 app = QApplication(sys.argv) print("PyQt5 环境正常") print("=== 环境验证完成 ===") if __name__ == "__main__": check_environment()运行验证脚本,确保所有关键依赖正常工作。
3. 水下生物数据集准备与处理
3.1 数据集结构与标注规范
本项目使用的数据集包含 7600 张高质量水下图像,按照 7:2:1 的比例划分为训练集、验证集和测试集。数据集采用 YOLO 格式,目录结构如下:
underwater_dataset/ ├── images/ │ ├── train/ # 5320 张训练图像 │ ├── val/ # 1520 张验证图像 │ └── test/ # 760 张测试图像 └── labels/ ├── train/ # 对应的训练标注文件 ├── val/ # 验证标注文件 └── test/ # 测试标注文件每个标注文件为.txt格式,每行表示一个检测目标:
<class_id> <x_center> <y_center> <width> <height>其中坐标值均为归一化后的相对值(0-1 之间)。
3.2 数据集配置文件
创建data.yaml配置文件,定义数据集路径和类别信息:
# 数据集路径 path: /path/to/underwater_dataset # 数据集根目录 train: images/train # 训练集相对路径 val: images/val # 验证集相对路径 test: images/test # 测试集相对路径 # 类别数量 nc: 5 # 类别名称 names: 0: echinus # 海胆 1: holothurian # 海参 2: scallop # 扇贝 3: starfish # 海星 4: waterweeds # 水草3.3 数据增强策略针对水下环境优化
水下图像的特殊性要求定制化的数据增强策略。在train.py中配置针对性的增强参数:
from ultralytics import YOLO import albumentations as A from albumentations.pytorch import ToTensorV2 def get_underwater_augmentations(): """针对水下环境的增强策略""" return A.Compose([ # 颜色校正类增强 A.RandomGamma(gamma_limit=(80, 120), p=0.5), # 模拟不同水质能见度 A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, p=0.5), A.CLAHE(clip_limit=2.0, tile_grid_size=(8, 8), p=0.3), # 增强对比度 # 光学效应模拟 A.GaussNoise(var_limit=(10.0, 50.0), p=0.3), # 模拟水下颗粒噪声 A.MotionBlur(blur_limit=7, p=0.2), # 模拟水流运动模糊 # 几何变换 A.Rotate(limit=15, p=0.5), A.RandomScale(scale_limit=0.2, p=0.5), A.HorizontalFlip(p=0.5), # 遮挡模拟 A.CoarseDropout(max_holes=8, max_height=20, max_width=20, p=0.3), ToTensorV2() ], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))3.4 数据集质量检查
创建数据质量检查脚本,确保标注准确性和数据一致性:
import os import cv2 import yaml from pathlib import Path def validate_dataset(data_yaml_path): """验证数据集完整性""" with open(data_yaml_path, 'r') as f: data_cfg = yaml.safe_load(f) base_path = Path(data_cfg['path']) issues = [] for split in ['train', 'val', 'test']: image_dir = base_path / data_cfg[split] label_dir = base_path / 'labels' / split if not image_dir.exists(): issues.append(f"图像目录不存在: {image_dir}") continue if not label_dir.exists(): issues.append(f"标注目录不存在: {label_dir}") continue # 检查图像和标注文件对应关系 image_files = list(image_dir.glob('*.jpg')) + list(image_dir.glob('*.png')) for img_path in image_files: label_path = label_dir / f"{img_path.stem}.txt" if not label_path.exists(): issues.append(f"标注文件缺失: {label_path}") return issues # 运行检查 if __name__ == "__main__": issues = validate_dataset('data.yaml') if issues: print("发现以下问题:") for issue in issues: print(f"- {issue}") else: print("数据集验证通过")4. YOLOv8 模型训练与优化
4.1 模型训练配置
创建训练脚本train.py,配置训练参数:
from ultralytics import YOLO import os def train_model(): # 加载预训练模型 model = YOLO('yolov8s.pt') # 使用小模型平衡速度与精度 # 训练参数配置 results = model.train( data='data.yaml', # 数据集配置文件 epochs=500, # 训练轮数 batch=16, # 批次大小(根据GPU内存调整) imgsz=640, # 输入图像尺寸 device='0', # 使用GPU 0 workers=4, # 数据加载线程数 patience=50, # 早停耐心值 lr0=0.01, # 初始学习率 lrf=0.01, # 最终学习率 momentum=0.937, # 动量 weight_decay=0.0005, # 权重衰减 warmup_epochs=3.0, # 热身轮数 warmup_momentum=0.8, # 热身动量 box=7.5, # 框损失权重 cls=0.5, # 分类损失权重 dfl=1.5, # 分布焦点损失权重 save=True, # 保存检查点 exist_ok=True, # 覆盖现有输出 pretrained=True, # 使用预训练权重 optimizer='auto', # 自动选择优化器 verbose=True, # 输出详细信息 seed=42, # 随机种子 deterministic=True, # 确定性训练 single_cls=False, # 多类别检测 rect=False, # 矩形训练 cos_lr=True, # 余弦学习率调度 close_mosaic=10, # 最后10轮关闭马赛克增强 resume=False, # 不从检查点恢复 amp=True, # 自动混合精度 fraction=1.0 # 使用全部数据 ) return results if __name__ == '__main__': # 开始训练 print("开始训练 YOLOv8 水下生物检测模型...") results = train_model() print("训练完成!")4.2 训练过程监控与调优
训练过程中需要密切关注以下指标:
- 损失函数变化:确保训练损失和验证损失同步下降
- 精度指标:关注 mAP@0.5 和 mAP@0.5:0.95 的提升
- 学习率调整:观察学习率自动调整是否合理
创建训练监控脚本:
import matplotlib.pyplot as plt import pandas as pd from pathlib import Path def analyze_training_results(runs_dir='runs/detect'): """分析训练结果""" results_dir = Path(runs_dir) exp_dirs = [d for d in results_dir.iterdir() if d.is_dir() and d.name.startswith('train')] if not exp_dirs: print("未找到训练结果") return latest_exp = max(exp_dirs, key=lambda x: x.stat().st_mtime) results_file = latest_exp / 'results.csv' if results_file.exists(): df = pd.read_csv(results_file) # 绘制损失曲线 plt.figure(figsize=(15, 10)) # 损失子图 plt.subplot(2, 3, 1) plt.plot(df['epoch'], df['train/box_loss'], label='训练框损失') plt.plot(df['epoch'], df['val/box_loss'], label='验证框损失') plt.title('边界框损失') plt.legend() plt.subplot(2, 3, 2) plt.plot(df['epoch'], df['train/cls_loss'], label='训练分类损失') plt.plot(df['epoch'], df['val/cls_loss'], label='验证分类损失') plt.title('分类损失') plt.legend() plt.subplot(2, 3, 3) plt.plot(df['epoch'], df['train/dfl_loss'], label='训练DFL损失') plt.plot(df['epoch'], df['val/dfl_loss'], label='验证DFL损失') plt.title('DFL损失') plt.legend() # 精度子图 plt.subplot(2, 3, 4) plt.plot(df['epoch'], df['metrics/precision(B)'], label='精确率') plt.title('精确率') plt.legend() plt.subplot(2, 3, 5) plt.plot(df['epoch'], df['metrics/recall(B)'], label='召回率') plt.title('召回率') plt.legend() plt.subplot(2, 3, 6) plt.plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP@0.5') plt.plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP@0.5:0.95') plt.title('mAP指标') plt.legend() plt.tight_layout() plt.savefig('training_analysis.png', dpi=300, bbox_inches='tight') plt.show() # 输出最终指标 final_metrics = df.iloc[-1] print(f"最终训练结果:") print(f"mAP@0.5: {final_metrics['metrics/mAP50(B)']:.3f}") print(f"mAP@0.5:0.95: {final_metrics['metrics/mAP50-95(B)']:.3f}") print(f"精确率: {final_metrics['metrics/precision(B)']:.3f}") print(f"召回率: {final_metrics['metrics/recall(B)']:.3f}") if __name__ == "__main__": analyze_training_results()4.3 模型导出与优化
训练完成后,将模型导出为适合部署的格式:
from ultralytics import YOLO def export_model(): # 加载最佳模型 model = YOLO('runs/detect/train/weights/best.pt') # 导出为不同格式 export_formats = [ 'onnx', # ONNX格式,通用推理 'engine', # TensorRT引擎,NVIDIA GPU加速 'openvino', # OpenVINO格式,Intel硬件加速 'torchscript' # TorchScript,PyTorch原生部署 ] for fmt in export_formats: try: model.export(format=fmt, imgsz=640, optimize=True) print(f"成功导出为 {fmt.upper()} 格式") except Exception as e: print(f"导出 {fmt} 失败: {e}") if __name__ == "__main__": export_model()5. 图形界面开发与功能集成
5.1 PyQt5 界面设计与布局
创建主界面类,实现检测系统的核心功能:
import sys import os import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QPushButton, QSlider, QComboBox, QFileDialog, QMessageBox, QTableWidget, QTableWidgetItem, QHeaderView, QStatusBar, QProgressBar) from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QThread from PyQt5.QtGui import QImage, QPixmap, QIcon, QFont from ultralytics import YOLO import datetime class DetectionThread(QThread): """检测线程,避免界面卡顿""" finished = pyqtSignal(object) error = pyqtSignal(str) def __init__(self, model, image, conf_threshold, iou_threshold): super().__init__() self.model = model self.image = image self.conf_threshold = conf_threshold self.iou_threshold = iou_threshold def run(self): try: results = self.model.predict( self.image, conf=self.conf_threshold, iou=self.iou_threshold, verbose=False ) self.finished.emit(results) except Exception as e: self.error.emit(str(e)) class UnderwaterDetectionUI(QMainWindow): def __init__(self): super().__init__() self.model = None self.current_image = None self.current_results = None self.cap = None self.timer = QTimer() self.is_detecting = False self.detection_thread = None self.init_ui() self.setup_connections() def init_ui(self): self.setWindowTitle("YOLOv8 水下生物识别检测系统") self.setGeometry(100, 100, 1600, 900) # 设置窗口图标 self.setWindowIcon(QIcon('icon.ico') if os.path.exists('icon.ico') else QIcon()) # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout = QHBoxLayout(central_widget) main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(15) # 左侧布局 - 图像显示 left_layout = QVBoxLayout() left_layout.setSpacing(15) # 原始图像显示 self.original_group = self.create_image_group("原始图像", "等待加载图像...") left_layout.addWidget(self.original_group) # 检测结果显示 self.result_group = self.create_image_group("检测结果", "检测结果将显示在这里") left_layout.addWidget(self.result_group) main_layout.addLayout(left_layout, 3) # 右侧布局 - 控制面板 right_layout = QVBoxLayout() right_layout.setSpacing(15) # 模型控制组 right_layout.addWidget(self.create_model_group()) # 参数控制组 right_layout.addWidget(self.create_parameter_group()) # 功能按钮组 right_layout.addWidget(self.create_function_group()) # 结果表格组 right_layout.addWidget(self.create_results_table()) main_layout.addLayout(right_layout, 1) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) self.status_bar.addPermanentWidget(self.progress_bar) def create_image_group(self, title, placeholder_text): """创建图像显示组""" group = QGroupBox(title) group.setMinimumHeight(400) layout = QVBoxLayout() label = QLabel(placeholder_text) label.setAlignment(Qt.AlignCenter) label.setStyleSheet(""" QLabel { background-color: #F8F9FA; border: 2px dashed #DEE2E6; border-radius: 8px; color: #6C757D; font-size: 14px; padding: 20px; } """) label.setMinimumHeight(380) layout.addWidget(label) group.setLayout(layout) return group, label def create_model_group(self): """创建模型控制组""" group = QGroupBox("模型设置") layout = QVBoxLayout() # 模型选择 self.model_combo = QComboBox() self.model_combo.addItems(["yolov8s.pt", "yolov8m.pt", "yolov8l.pt"]) # 加载按钮 self.load_btn = QPushButton("加载模型") self.load_btn.setStyleSheet(self.get_button_style("primary")) layout.addWidget(QLabel("选择模型:")) layout.addWidget(self.model_combo) layout.addWidget(self.load_btn) layout.addSpacing(10) # 模型信息显示 self.model_info_label = QLabel("模型未加载") self.model_info_label.setStyleSheet("color: #6C757D; font-size: 12px;") layout.addWidget(self.model_info_label) group.setLayout(layout) return group def create_parameter_group(self): """创建参数控制组""" group = QGroupBox("检测参数") layout = QVBoxLayout() # 置信度阈值 conf_layout = QVBoxLayout() conf_layout.addWidget(QLabel("置信度阈值:")) self.conf_slider = QSlider(Qt.Horizontal) self.conf_slider.setRange(1, 99) self.conf_slider.setValue(25) self.conf_label = QLabel("0.25") self.conf_label.setAlignment(Qt.AlignCenter) self.conf_label.setStyleSheet("font-weight: bold; color: #007BFF;") conf_layout.addWidget(self.conf_slider) conf_layout.addWidget(self.conf_label) layout.addLayout(conf_layout) layout.addSpacing(15) # IoU阈值 iou_layout = QVBoxLayout() iou_layout.addWidget(QLabel("IoU阈值:")) self.iou_slider = QSlider(Qt.Horizontal) self.iou_slider.setRange(1, 99) self.iou_slider.setValue(45) self.iou_label = QLabel("0.45") self.iou_label.setAlignment(Qt.AlignCenter) self.iou_label.setStyleSheet("font-weight: bold; color: #007BFF;") iou_layout.addWidget(self.iou_slider) iou_layout.addWidget(self.iou_label) layout.addLayout(iou_layout) group.setLayout(layout) return group def create_function_group(self): """创建功能按钮组""" group = QGroupBox("检测功能") layout = QVBoxLayout() buttons = [ ("图片检测", "image", "primary"), ("视频检测", "video", "success"), ("摄像头检测", "camera", "info"), ("停止检测", "stop", "danger"), ("保存结果", "save", "warning") ] for text, name, style in buttons: btn = QPushButton(text) btn.setProperty("function", name) btn.setStyleSheet(self.get_button_style(style)) if name == "stop": btn.setEnabled(False) layout.addWidget(btn) group.setLayout(layout) return group def create_results_table(self): """创建结果表格""" group = QGroupBox("检测结果详情") layout = QVBoxLayout() self.results_table = QTableWidget() self.results_table.setColumnCount(5) self.results_table.setHorizontalHeaderLabels(["类别", "置信度", "数量", "位置", "尺寸"]) self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.results_table.setEditTriggers(QTableWidget.NoEditTriggers) layout.addWidget(self.results_table) group.setLayout(layout) return group def get_button_style(self, style_type): """获取按钮样式""" styles = { "primary": """ QPushButton { background-color: #007BFF; color: white; border: none; padding: 10px; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #0056B3; } QPushButton:disabled { background-color: #6C757D; } """, "success": """ QPushButton { background-color: #28A745; color: white; border: none; padding: 10px; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #1E7E34; } """, # ... 其他样式定义 } return styles.get(style_type, styles["primary"]) def setup_connections(self): """设置信号连接""" # 模型加载 self.load_btn.clicked.connect(self.load_model) # 参数更新 self.conf_slider.valueChanged.connect(self.update_conf_label) self.iou_slider.valueChanged.connect(self.update_iou_label) # 功能按钮 for btn in self.findChildren(QPushButton): if btn.property("function"): func_name = btn.property("function") if func_name == "image": btn.clicked.connect(self.detect_image) elif func_name == "video": btn.clicked.connect(self.detect_video) # ... 其他功能连接 def load_model(self): """加载模型""" try: model_path = self.model_combo.currentText() self.model = YOLO(model_path) self.model_info_label.setText(f"模型加载成功: {os.path.basename(model_path)}") self.status_bar.showMessage("模型加载成功", 3000) except Exception as e: QMessageBox.critical(self, "错误", f"模型加载失败: {str(e)}") def update_conf_label(self): """更新置信度标签""" conf = self.conf_slider.value() / 100 self.conf_label.setText(f"{conf:.2f}") def update_iou_label(self): """更新IoU标签""" iou = self.iou_slider.value() / 100 self.iou_label.setText(f"{iou:.2f}") def detect_image(self): """图片检测功能""" if not self.model: QMessageBox.warning(self, "警告", "请先加载模型") return file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "图片文件 (*.jpg *.jpeg *.png *.bmp);;所有文件 (*)" ) if file_path: self.process_image(file_path) def process_image(self, image_path): """处理单张图片""" try: # 读取并显示原始图像 image = cv2.imread(image_path) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) self.display_image(image_rgb, self.original_label) # 在子线程中进行检测 self.progress_bar.setVisible(True) self.progress_bar.setRange(0, 0) # 无限进度条 self.detection_thread = DetectionThread( self.model, image_rgb, self.conf_slider.value() / 100, self.iou_slider.value() / 100 ) self.detection_thread.finished.connect(self.on_detection_finished) self.detection_thread.error.connect(self.on_detection_error) self.detection_thread.start() except Exception as e: QMessageBox.critical(self, "错误", f"图像处理失败: {str(e)}") def on_detection_finished(self, results): """检测完成回调""" self.progress_bar.setVisible(False) # 显示检测结果 result_image = results[0].plot() self.display_image(result_image, self.result_label) # 更新结果表格 self.update_results_table(results[0]) self.status_bar.showMessage("检测完成", 3000) def on_detection_error(self, error_msg): """检测错误回调""" self.progress_bar.setVisible(False) QMessageBox.critical(self, "错误", f"检测失败: {error_msg}") def display_image(self, image, label): """在QLabel中显示图像""" h, w, ch = image.shape bytes_per_line = ch * w q_img = QImage(image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(q_img) # 缩放适应标签大小 scaled_pixmap = pixmap.scaled( label.width() - 20, label.height() - 20, Qt.KeepAspectRatio, Qt.SmoothTransformation ) label.setPixmap(scaled_pixmap) def update_results_table(self, results): """更新结果表格""" self.results_table.setRowCount(0) if not results.boxes: return boxes = results.boxes for i, (cls, conf, xyxy) in enumerate(zip(boxes.cls, boxes.conf, boxes.xyxy)): row = self.results_table.rowCount() self.results_table.insertRow(row) # 类别名称 class_name = results.names[int(cls)] self.results_table.setItem(row, 0, QTableWidgetItem(class_name)) # 置信度 self.results_table.setItem(row, 1, QTableWidgetItem(f"{conf:.3f}")) # 数量(同类目标计数) same_class_count = sum(1 for c in boxes.cls if int(c) == int(cls)) self.results_table.setItem(row, 2, QTableWidgetItem(str(same_class_count))) # 位置 x1, y1, x2, y2 = xyxy position = f"({x1:.0f}, {y1:.0f})" self.results_table.setItem(row, 3, QTableWidgetItem(position)) # 尺寸 width = x2 - x1 height = y2 - y1 size = f"{width:.0f}×{height:.0f}" self.results_table.setItem(row, 4, QTableWidgetItem(size)) def main(): app = QApplication(sys.argv) window = UnderwaterDetectionUI() window.show() sys.exit(app.exec_()) if __name__ == '__main__': main()5.2 实时检测功能实现
对于视频和摄像头实时检测,需要处理帧率和性能优化:
class VideoDetectionHandler: """视频检测处理器""" def __init__(self, model, conf_threshold=0.25, iou_threshold=0.45): self.model = model self.conf_threshold = conf_threshold self.iou_threshold = iou_threshold self.is_running = False self.current_frame = None self.results = None def process_frame(self, frame): """处理单帧""" if not self.is_running: return frame try: # 调整帧尺寸以提高性能 small_frame = cv2.resize(frame, (640, 640)) results = self.model.predict( small_frame, conf=self.conf_threshold, iou=self.iou_threshold, verbose=False ) # 将检测结果绘制到原尺寸帧上 result_frame = results[0].plot() result_frame = cv2.resize(result_frame, (frame.shape[1], frame.shape[0])) self.results = results[0] return result_frame except Exception as e: print(f"帧处理错误: {e}") return frame def start(self): self.is_running = True def stop(self): self.is_running = False self.results = None6. 系统部署与性能优化
6.1 生产环境部署配置
创建部署配置文件deploy_config.yaml:
# 部署配置 deployment: model_path: "runs/detect/train/weights/best.pt" input_sources: