基于PyQt和深度学习的羊群密集计数系统

一、研究背景与意义

羊群规模是畜牧业管理的重要指标,对牧场管理、疫病防控、饲养规划和经济核算都具有重要意义。传统的羊群计数方法主要依靠人工清点,不仅耗时费力,而且在大规模羊群面前往往难以保证准确性。特别是在密集分布的羊群中,由于个体重叠、遮挡和移动,人工计数的难度更大,误差也更明显。

随着计算机视觉和深度学习技术的快速发展,基于图像识别的目标检测和计数技术已经在多个领域展现出巨大潜力。将这些先进技术应用于羊群计数,能够有效解决传统方法面临的问题,实现高效、准确的羊群数量统计。

本课题拟开发一套基于PyQt和深度学习的羊群密集计数系统,通过对羊群图像或视频的智能分析,实现对密集分布羊群的自动检测和精确计数。该系统不仅能够解决人工计数的困难,还能为牧场管理提供更加科学、高效的技术支持。

研究意义主要体现在以下几个方面:

提高计数效率与准确性:系统能够快速处理大量图像数据,大幅提高羊群计数的效率和准确性,特别是在大规模密集羊群场景下具有明显优势。降低人力成本:减少繁重的人工计数工作,释放牧场管理人员,使其能够专注于更有价值的管理工作。支持科学决策:提供准确的羊群数量数据,为牧场饲养管理、疫病防控、经济核算等决策提供可靠依据。促进畜牧业信息化:推动畜牧业管理方式从传统经验型向数据驱动型转变,提升整体信息化和智能化水平。技术创新与应用:将深度学习技术与实际生产需求相结合,探索人工智能技术在畜牧业的创新应用。

二、国内外研究现状

国外研究现状

国外在基于计算机视觉的动物计数领域已有较多研究。澳大利亚CSIRO研究机构开发了基于无人机和机器视觉的牛群计数系统,准确率达到95%以上。英国爱丁堡大学研究团队提出了一种基于CNN(卷积神经网络)的家畜密度估计方法,在解决动物重叠问题方面取得了进展。美国加州大学的研究人员将密度图估计方法应用于野生动物监测,在处理大规模群体时表现出色。以色列的Agrovis公司已经商业化了一套基于深度学习的家畜计数系统,主要用于牛和猪的计数。

国内研究现状

国内在动物计数技术方面的研究起步相对较晚,但近年来发展迅速。中国农业大学研究团队开发了基于YOLOv3的家畜检测与计数系统,在牛群计数上取得了不错的效果。西北农林科技大学的研究人员提出了一种改进的Faster R-CNN模型用于羊群检测,解决了部分遮挡问题。内蒙古农业大学结合草原实际情况,研究了基于无人机航拍图像的牛羊群体计数方法。此外,华南农业大学开展了基于深度学习的家禽密度估计研究,在密集群体计数方面有所突破。

存在的问题与挑战

尽管已有研究取得了一定进展,但在羊群密集计数领域仍存在以下问题和挑战:

密集重叠问题:羊群常常密集分布,个体之间存在严重重叠,传统目标检测方法难以准确区分每只羊。环境复杂多变:野外环境下的光照、背景、天气等因素变化大,影响识别效果。算法泛化能力不足:许多模型在特定环境训练后,难以适应新的场景。实时性需求:实际应用中往往需要实时或近实时处理能力,对算法效率提出挑战。用户友好度不足:现有系统多为研究性质,缺乏面向非技术人员的友好界面。

本课题将针对上述问题,特别是密集重叠问题和用户友好度问题,提出相应的解决方案。

三、研究内容

本课题的主要研究内容包括:

羊群数据集的收集与处理

收集不同场景、不同密度的羊群图像和视频数据对数据进行标注和预处理构建训练、验证和测试数据集

深度学习模型设计与训练

研究适合密集目标计数的深度学习模型设计基于密度图估计的羊群计数算法探索针对重叠问题的改进方法模型训练、优化与评估

羊群计数系统开发

基于PyQt设计用户友好的图形界面实现图像和视频的导入、预处理和显示功能开发羊群检测和计数的核心功能模块实现计数结果的可视化和分析功能

系统测试与性能评估

设计测试方案,评估系统性能比较不同算法在不同场景下的计数准确率系统的实用性和易用性测试系统优化与改进

四、研究方案与技术路线

1. 技术路线

研究工作将按照以下技术路线展开:

前期准备阶段:文献调研、需求分析、数据收集数据处理阶段:数据标注、预处理、增强、分析模型研发阶段:模型设计、训练、测试、优化系统开发阶段:界面设计、功能实现、系统集成测试评估阶段:性能测试、用户测试、优化改进总结完善阶段:成果整理、论文撰写

2. 具体研究方案
2.1 数据集构建

数据来源:从公开数据集(如UAV Livestock Dataset)获取部分数据;与本地牧场合作采集真实场景数据数据标注:采用点标注方式标记每只羊的位置;使用密度图生成算法将点标注转换为密度图数据增强:通过旋转、翻转、缩放、亮度调整等方法扩充数据集数据划分:按70%、15%、15%的比例划分训练集、验证集和测试集

2.2 深度学习模型设计

本研究将采用基于密度图估计的方法进行羊群计数,主要考虑以下几种模型:

改进的CSRNet:针对羊群特点,对CSRNet网络结构进行优化VGG16-FCN:基于VGG16的全卷积网络,适合密度估计任务MCNN:多列卷积神经网络,能够处理不同尺度的目标SANet:注意力机制网络,提高对密集区域的识别能力

模型训练将使用PyTorch框架,采用Adam优化器,学习率初始设为1e-4,并使用学习率衰减策略。损失函数采用均方误差(MSE),评估指标包括MAE(平均绝对误差)和RMSE(均方根误差)。

2.3 系统开发

系统采用Python语言开发,基于PyQt5框架构建用户界面,主要功能模块包括:

图像/视频导入模块:支持多种格式的图像和视频文件导入预处理模块:提供图像增强、裁剪、滤波等预处理功能计数处理模块:调用训练好的深度学习模型进行羊群计数结果展示模块:通过热力图、标记等方式可视化计数结果数据管理模块:支持结果保存、导出和历史记录查询

系统将采用模块化设计,保证各功能模块之间的低耦合高内聚,便于后期维护和扩展。

五、预期成果与创新点

预期成果

一套针对羊群特点优化的密集目标计数算法一个基于PyQt的羊群密集计数系统羊群密集分布场景下的计数准确率达到90%以上相关研究成果的学术论文1-2篇完整的系统设计和使用文档

创新点

融合改进的密度估计方法:针对羊群密集重叠的特点,提出改进的密度图估计算法,提高计数准确率多场景适应性设计:开发适应不同场景(如草原、羊圈、运输车辆等)的自适应算法,提高模型泛化能力交互式计数校正机制:设计人机交互的计数结果校正机制,允许用户对系统结果进行修正,并将修正信息反馈给模型进行在线学习集成分析功能:系统不仅提供计数功能,还集成了羊群分布密度分析、时间序列变化分析等高级功能,为牧场管理提供更全面的数据支持轻量化模型设计:针对实际应用场景,对模型进行剪枝和量化处理,使系统能够在普通计算设备上高效运行

六、研究基础与可行性分析

研究基础

理论基础:已系统学习计算机视觉、深度学习和软件工程等相关理论知识,具备开展本研究的理论基础技术基础:熟练掌握Python编程,具有PyQt开发经验,熟悉TensorFlow/PyTorch等深度学习框架数据基础:已获取部分公开羊群数据集,并与当地牧场建立了合作关系,可获取真实场景数据设备基础:学校提供GPU服务器用于模型训练,个人计算机可满足系统开发需求

可行性分析

技术可行性:深度学习在密集目标计数领域已有成熟应用,如人群计数、车辆计数等,技术上可迁移至羊群计数数据可行性:通过公开数据集和实地采集,可获取足够的羊群图像数据实现可行性:PyQt提供完善的GUI开发工具,结合深度学习模型,可实现预期的系统功能应用可行性:系统解决了实际需求,有明确的应用场景和用户群体,具有应用推广价值

七、可能存在的问题与解决方案

可能的问题

数据不足问题:实际可获取的羊群图像数据可能有限,特别是密集分布场景模型泛化性问题:模型在测试场景表现良好,但在新场景可能效果下降计算资源限制:深度学习模型训练需要大量计算资源实时性挑战:系统处理视频流时可能面临实时性挑战

解决方案

数据问题解决:采用数据增强技术;使用迁移学习方法,从相似任务迁移知识泛化性问题解决:增加数据多样性;采用域适应技术;加入正则化方法计算资源解决:使用学校GPU服务器;采用模型剪枝和量化技术减少计算量实时性问题解决:优化算法效率;降低处理分辨率;考虑边缘计算解决方案

九、参考文献

Li, Y., Zhang, X., & Chen, D. (2018). CSRNet: Dilated convolutional neural networks for understanding the highly congested scenes. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 1091-1100).Sam, D. B., Surya, S., & Babu, R. V. (2017). Switching convolutional neural network for crowd counting. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 5744-5752).Zhang, Y., Zhou, D., Chen, S., Gao, S., & Ma, Y. (2016). Single-image crowd counting via multi-column convolutional neural network. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 589-597).Cao, X., Wang, Z., Zhao, Y., & Su, F. (2018). Scale aggregation network for accurate and efficient crowd counting. In Proceedings of the European Conference on Computer Vision (pp. 734-750).Andrew, A. M. (2000). An introduction to support vector machines and other kernel-based learning methods by Nello Christianini and John Shawe-Taylor. Robotica, 18(6), 687-689.Chollet, F. (2017). Deep learning with Python. Manning Publications Co.Summerfield, M. (2010). Rapid GUI programming with Python and Qt: the definitive guide to PyQt programming. Pearson Education.Redmon, J., & Farhadi, A. (2018). YOLOv3: An incremental improvement. arXiv preprint arXiv:1804.02767.He, K., Gkioxari, G., Dollár, P., & Girshick, R. (2017). Mask R-CNN. In Proceedings of the IEEE International Conference on Computer Vision (pp. 2961-2969).Wang, C., Zhang, H., Yang, L., Liu, S., & Cao, X. (2015). Deep people counting in extremely dense crowds. In Proceedings of the 23rd ACM International Conference on Multimedia (pp. 1299-1302).

核心设计部分(仅供学习和参考):

项目架构

项目结构

SheepCountingSystem/
│
├── main.py                 # 主程序入口
├── requirements.txt        # 依赖包列表
├── README.md               # 项目说明
│
├── models/                 # 深度学习模型
│   ├── model.py            # 模型定义
│   ├── train.py            # 模型训练
│   ├── predict.py          # 模型预测
│   └── checkpoints/        # 保存训练好的模型
│
├── data/                   # 数据
│   ├── images/             # 图像数据
│   ├── annotations/        # 标注数据
│   └── data_processor.py   # 数据预处理
│
├── ui/                     # 用户界面
│   ├── main_window.py      # 主窗口
│   ├── image_viewer.py     # 图像查看器
│   ├── video_player.py     # 视频播放器
│   ├── result_viewer.py    # 结果查看器
│   ├── settings_dialog.py  # 设置对话框
│   └── resources/          # UI资源(图标等)
│
└── utils/                  # 工具函数
    ├── logger.py           # 日志
    ├── image_utils.py      # 图像处理工具
    ├── video_utils.py      # 视频处理工具
    └── visualization.py    # 可视化工具

核心代码实现

1. 主程序入口 (main.py)

import sys
import os
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QCoreApplication, Qt
from ui.main_window import MainWindow
from utils.logger import setup_logger

# 设置高DPI支持
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)

def main():
    # 设置应用程序信息
    QCoreApplication.setApplicationName("羊群密集计数系统")
    QCoreApplication.setOrganizationName("大学名称")
    QCoreApplication.setApplicationVersion("1.0.0")
    
    # 确保目录存在
    os.makedirs('logs', exist_ok=True)
    os.makedirs('data/images', exist_ok=True)
    os.makedirs('data/annotations', exist_ok=True)
    os.makedirs('models/checkpoints', exist_ok=True)
    
    # 设置日志
    logger = setup_logger('main')
    logger.info('启动羊群密集计数系统')
    
    # 创建应用程序
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    
    # 创建主窗口
    window = MainWindow()
    window.show()
    
    # 运行应用程序
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

2. 主窗口界面 (ui/main_window.py)

import os
import time
from PyQt5.QtWidgets import (QMainWindow, QTabWidget, QAction, QMessageBox, 
                            QFileDialog, QMenu, QToolBar, QStatusBar, 
                            QLabel, QProgressBar, QDockWidget, QListWidget,
                            QVBoxLayout, QWidget)
from PyQt5.QtGui import QIcon, QPixmap, QImage
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings

from ui.image_viewer import ImageViewer
from ui.video_player import VideoPlayer
from ui.result_viewer import ResultViewer
from ui.settings_dialog import SettingsDialog
from models.predict import SheepCounter
from utils.logger import get_logger
from utils.image_utils import load_image
from utils.video_utils import extract_frames

class ProcessingThread(QThread):
    """后台处理线程,用于图像处理和羊群计数"""
    progress_updated = pyqtSignal(int)
    processing_finished = pyqtSignal(dict)
    error_occurred = pyqtSignal(str)
    
    def __init__(self, counter, file_path, is_video=False):
        super().__init__()
        self.counter = counter
        self.file_path = file_path
        self.is_video = is_video
        
    def run(self):
        try:
            results = {}
            if self.is_video:
                # 视频处理
                frames = extract_frames(self.file_path)
                total_frames = len(frames)
                
                for i, frame in enumerate(frames):
                    count, density_map = self.counter.predict(frame)
                    progress = int((i + 1) / total_frames * 100)
                    self.progress_updated.emit(progress)
                    results[i] = {
                        'frame': frame,
                        'count': count,
                        'density_map': density_map
                    }
            else:
                # 图像处理
                image = load_image(self.file_path)
                self.progress_updated.emit(50)
                count, density_map = self.counter.predict(image)
                self.progress_updated.emit(100)
                results = {
                    'image': image,
                    'count': count,
                    'density_map': density_map
                }
                
            self.processing_finished.emit(results)
        except Exception as e:
            self.error_occurred.emit(str(e))

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.logger = get_logger('main_window')
        self.settings = QSettings()
        self.counter = SheepCounter()
        self.init_ui()
        self.load_settings()
        
    def init_ui(self):
        self.setWindowTitle('羊群密集计数系统')
        self.setGeometry(100, 100, 1200, 800)
        
        # 创建中心部件
        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)
        
        # 创建各个视图
        self.image_viewer = ImageViewer()
        self.video_player = VideoPlayer()
        self.result_viewer = ResultViewer()
        
        # 添加标签页
        self.tabs.addTab(self.image_viewer, "图像查看")
        self.tabs.addTab(self.video_player, "视频查看")
        self.tabs.addTab(self.result_viewer, "结果分析")
        
        # 创建菜单栏
        self.create_menu_bar()
        
        # 创建工具栏
        self.create_tool_bar()
        
        # 创建状态栏
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        
        # 状态信息
        self.status_label = QLabel("就绪")
        self.status_bar.addWidget(self.status_label, 1)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setFixedWidth(200)
        self.progress_bar.hide()
        self.status_bar.addPermanentWidget(self.progress_bar)
        
        # 创建文件列表停靠窗口
        self.create_file_dock()
        
        # 连接信号
        self.tabs.currentChanged.connect(self.tab_changed)
        
        self.logger.info('主窗口UI初始化完成')
        
    def create_menu_bar(self):
        # 创建菜单栏
        menubar = self.menuBar()
        
        # 文件菜单
        file_menu = menubar.addMenu('文件')
        
        # 打开图像
        open_image_action = QAction(QIcon('ui/resources/image_icon.png'), '打开图像', self)
        open_image_action.setShortcut('Ctrl+O')
        open_image_action.triggered.connect(self.open_image)
        file_menu.addAction(open_image_action)
        
        # 打开视频
        open_video_action = QAction(QIcon('ui/resources/video_icon.png'), '打开视频', self)
        open_video_action.setShortcut('Ctrl+Shift+O')
        open_video_action.triggered.connect(self.open_video)
        file_menu.addAction(open_video_action)
        
        file_menu.addSeparator()
        
        # 保存结果
        save_action = QAction(QIcon('ui/resources/save_icon.png'), '保存结果', self)
        save_action.setShortcut('Ctrl+S')
        save_action.triggered.connect(self.save_results)
        file_menu.addAction(save_action)
        
        file_menu.addSeparator()
        
        # 退出
        exit_action = QAction('退出', self)
        exit_action.setShortcut('Alt+F4')
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
        
        # 编辑菜单
        edit_menu = menubar.addMenu('编辑')
        
        # 设置
        settings_action = QAction('设置', self)
        settings_action.triggered.connect(self.show_settings)
        edit_menu.addAction(settings_action)
        
        # 处理菜单
        process_menu = menubar.addMenu('处理')
        
        # 计数
        count_action = QAction('计数', self)
        count_action.setShortcut('F5')
        count_action.triggered.connect(self.count_sheep)
        process_menu.addAction(count_action)
        
        # 批量处理
        batch_action = QAction('批量处理', self)
        batch_action.triggered.connect(self.batch_process)
        process_menu.addAction(batch_action)
        
        # 帮助菜单
        help_menu = menubar.addMenu('帮助')
        
        # 关于
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)
    
    def create_tool_bar(self):
        # 创建工具栏
        self.toolbar = QToolBar("主工具栏")
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)
        
        # 添加工具按钮
        self.toolbar.addAction(QIcon('ui/resources/image_icon.png'), '打开图像', self.open_image)
        self.toolbar.addAction(QIcon('ui/resources/video_icon.png'), '打开视频', self.open_video)
        self.toolbar.addSeparator()
        self.toolbar.addAction(QIcon('ui/resources/count_icon.png'), '开始计数', self.count_sheep)
        self.toolbar.addAction(QIcon('ui/resources/save_icon.png'), '保存结果', self.save_results)
    
    def create_file_dock(self):
        # 创建文件列表停靠窗口
        self.file_dock = QDockWidget("文件列表", self)
        self.file_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        
        # 文件列表部件
        self.file_list = QListWidget()
        self.file_list.itemDoubleClicked.connect(self.file_item_clicked)
        
        # 添加列表到停靠窗口
        self.file_dock.setWidget(self.file_list)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.file_dock)
    
    def load_settings(self):
        # 加载应用设置
        size = self.settings.value("MainWindow/Size", self.size())
        pos = self.settings.value("MainWindow/Position", self.pos())
        state = self.settings.value("MainWindow/State", self.saveState())
        
        self.resize(size)
        self.move(pos)
        self.restoreState(state)
        
        # 加载模型设置
        model_path = self.settings.value("Model/Path", "models/checkpoints/default_model.pth")
        self.counter.load_model(model_path)
    
    def save_settings(self):
        # 保存应用设置
        self.settings.setValue("MainWindow/Size", self.size())
        self.settings.setValue("MainWindow/Position", self.pos())
        self.settings.setValue("MainWindow/State", self.saveState())
    
    def tab_changed(self, index):
        # 标签页切换处理
        if index == 0:  # 图像查看
            self.status_label.setText("图像查看模式")
        elif index == 1:  # 视频查看
            self.status_label.setText("视频查看模式")
        elif index == 2:  # 结果分析
            self.status_label.setText("结果分析模式")
    
    def open_image(self):
        # 打开图像文件
        file_path, _ = QFileDialog.getOpenFileName(
            self, "打开图像文件", "", 
            "图像文件 (*.png *.jpg *.jpeg *.bmp *.tif);;所有文件 (*)"
        )
        
        if file_path:
            self.logger.info(f"打开图像: {file_path}")
            self.status_label.setText(f"已加载图像: {os.path.basename(file_path)}")
            
            # 切换到图像查看标签页
            self.tabs.setCurrentIndex(0)
            
            # 加载图像到查看器
            self.image_viewer.load_image(file_path)
            
            # 添加到文件列表
            self.add_to_file_list(file_path)
            
            # 清除之前的结果
            self.result_viewer.clear_results()
    
    def open_video(self):
        # 打开视频文件
        file_path, _ = QFileDialog.getOpenFileName(
            self, "打开视频文件", "", 
            "视频文件 (*.mp4 *.avi *.mov *.wmv);;所有文件 (*)"
        )
        
        if file_path:
            self.logger.info(f"打开视频: {file_path}")
            self.status_label.setText(f"已加载视频: {os.path.basename(file_path)}")
            
            # 切换到视频查看标签页
            self.tabs.setCurrentIndex(1)
            
            # 加载视频到播放器
            self.video_player.load_video(file_path)
            
            # 添加到文件列表
            self.add_to_file_list(file_path)
            
            # 清除之前的结果
            self.result_viewer.clear_results()
    
    def add_to_file_list(self, file_path):
        # 添加文件到列表
        file_name = os.path.basename(file_path)
        
        # 检查是否已在列表中
        items = self.file_list.findItems(file_name, Qt.MatchExactly)
        if not items:
            self.file_list.addItem(file_name)
            
            # 存储完整路径为项目数据
            item = self.file_list.item(self.file_list.count() - 1)
            item.setData(Qt.UserRole, file_path)
    
    def file_item_clicked(self, item):
        # 文件列表项点击处理
        file_path = item.data(Qt.UserRole)
        file_ext = os.path.splitext(file_path)[1].lower()
        
        # 根据文件类型决定加载方式
        if file_ext in ['.png', '.jpg', '.jpeg', '.bmp', '.tif']:
            self.tabs.setCurrentIndex(0)
            self.image_viewer.load_image(file_path)
        elif file_ext in ['.mp4', '.avi', '.mov', '.wmv']:
            self.tabs.setCurrentIndex(1)
            self.video_player.load_video(file_path)
    
    def count_sheep(self):
        # 羊群计数处理
        current_tab = self.tabs.currentIndex()
        
        if current_tab == 0:  # 图像查看
            if not self.image_viewer.has_image():
                QMessageBox.warning(self, "警告", "请先加载图像")
                return
                
            file_path = self.image_viewer.current_image_path
            is_video = False
            
        elif current_tab == 1:  # 视频查看
            if not self.video_player.has_video():
                QMessageBox.warning(self, "警告", "请先加载视频")
                return
                
            file_path = self.video_player.current_video_path
            is_video = True
            
        else:
            QMessageBox.warning(self, "警告", "请先加载图像或视频")
            return
        
        # 显示进度条
        self.progress_bar.setValue(0)
        self.progress_bar.show()
        self.status_label.setText("正在处理...")
        
        # 创建处理线程
        self.processing_thread = ProcessingThread(self.counter, file_path, is_video)
        self.processing_thread.progress_updated.connect(self.update_progress)
        self.processing_thread.processing_finished.connect(self.processing_complete)
        self.processing_thread.error_occurred.connect(self.processing_error)
        
        # 启动线程
        self.processing_thread.start()
    
    def update_progress(self, value):
        # 更新进度条
        self.progress_bar.setValue(value)
    
    def processing_complete(self, results):
        # 处理完成
        self.progress_bar.hide()
        self.status_label.setText("处理完成")
        
        # 更新结果查看器
        self.result_viewer.update_results(results)
        
        # 切换到结果分析标签页
        self.tabs.setCurrentIndex(2)
        
        self.logger.info("羊群计数处理完成")
        
        # 显示计数结果
        if isinstance(results, dict) and 'count' in results:
            count = results['count']
            QMessageBox.information(self, "计数结果", f"检测到的羊的数量: {count}")
    
    def processing_error(self, error_msg):
        # 处理错误
        self.progress_bar.hide()
        self.status_label.setText("处理出错")
        
        self.logger.error(f"处理出错: {error_msg}")
        QMessageBox.critical(self, "错误", f"处理过程中出错:
{error_msg}")
    
    def save_results(self):
        # 保存结果
        if not self.result_viewer.has_results():
            QMessageBox.warning(self, "警告", "没有可保存的结果")
            return
        
        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存结果", "", 
            "CSV文件 (*.csv);;图像文件 (*.png *.jpg);;所有文件 (*)"
        )
        
        if file_path:
            self.result_viewer.save_results(file_path)
            self.status_label.setText(f"结果已保存到: {os.path.basename(file_path)}")
            self.logger.info(f"结果已保存到: {file_path}")
    
    def batch_process(self):
        # 批量处理
        folder_path = QFileDialog.getExistingDirectory(
            self, "选择包含图像的文件夹", ""
        )
        
        if not folder_path:
            return
            
        # 批量处理实现...
        QMessageBox.information(self, "批量处理", "批量处理功能待实现")
    
    def show_settings(self):
        # 显示设置对话框
        dialog = SettingsDialog(self)
        if dialog.exec_():
            # 应用新设置
            model_path = dialog.get_model_path()
            self.counter.load_model(model_path)
            self.settings.setValue("Model/Path", model_path)
            
            self.logger.info(f"已加载模型: {model_path}")
    
    def show_about(self):
        # 显示关于对话框
        QMessageBox.about(self, "关于羊群密集计数系统", 
                         "羊群密集计数系统 v1.0.0

"
                         "基于深度学习的羊群密集计数系统
"
                         "用于大规模羊群的自动计数

"
                         "作者: [学生姓名]
"
                         "指导教师: [教师姓名]
"
                         "© 2023 [大学名称] 本科毕业设计")
    
    def closeEvent(self, event):
        # 窗口关闭事件
        self.save_settings()
        self.logger.info("应用程序关闭")
        event.accept()

3. 图像查看器 (ui/image_viewer.py)

from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, 
                            QLabel, QScrollArea, QSizePolicy, QComboBox,
                            QSlider, QFileDialog)
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QColor
from PyQt5.QtCore import Qt, QRectF, pyqtSignal
import numpy as np
import cv2
from utils.logger import get_logger
from utils.image_utils import load_image

class ImageCanvas(QLabel):
    """自定义图像画布,支持缩放和标记"""
    
    def __init__(self):
        super().__init__()
        self.pixmap_original = None
        self.zoom_factor = 1.0
        self.annotations = []
        
        # 设置标签属性
        self.setAlignment(Qt.AlignCenter)
        self.setMinimumSize(600, 400)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setStyleSheet("background-color: #f0f0f0; border: 1px solid #d0d0d0;")
    
    def load_image(self, pixmap):
        """加载图像到画布"""
        self.pixmap_original = pixmap
        self.update_zoom()
    
    def update_zoom(self):
        """更新缩放"""
        if self.pixmap_original:
            scaled_pixmap = self.pixmap_original.scaled(
                int(self.pixmap_original.width() * self.zoom_factor),
                int(self.pixmap_original.height() * self.zoom_factor),
                Qt.KeepAspectRatio, Qt.SmoothTransformation
            )
            self.setPixmap(scaled_pixmap)
    
    def zoom_in(self):
        """放大图像"""
        self.zoom_factor *= 1.25
        self.update_zoom()
    
    def zoom_out(self):
        """缩小图像"""
        self.zoom_factor *= 0.8
        self.update_zoom()
    
    def reset_zoom(self):
        """重置缩放"""
        self.zoom_factor = 1.0
        self.update_zoom()
    
    def set_zoom(self, factor):
        """设置缩放因子"""
        self.zoom_factor = factor
        self.update_zoom()
    
    def add_annotation(self, rect, label="", color=Qt.red):
        """添加标注"""
        self.annotations.append({
            'rect': rect,
            'label': label,
            'color': color
        })
        self.update()
    
    def clear_annotations(self):
        """清除所有标注"""
        self.annotations = []
        self.update()
    
    def paintEvent(self, event):
        """绘制事件,添加标注"""
        super().paintEvent(event)
        
        if self.pixmap():
            painter = QPainter(self)
            painter.setRenderHint(QPainter.Antialiasing)
            
            # 计算图像在标签中的位置
            pixmap_rect = self.pixmap().rect()
            label_rect = self.rect()
            
            x = (label_rect.width() - pixmap_rect.width()) / 2
            y = (label_rect.height() - pixmap_rect.height()) / 2
            
            # 绘制标注
            for annotation in self.annotations:
                rect = annotation['rect']
                label = annotation['label']
                color = annotation['color']
                
                # 调整矩形位置
                adjusted_rect = QRectF(
                    rect.x() * self.zoom_factor + x,
                    rect.y() * self.zoom_factor + y,
                    rect.width() * self.zoom_factor,
                    rect.height() * self.zoom_factor
                )
                
                # 绘制矩形
                pen = QPen(color, 2)
                painter.setPen(pen)
                painter.drawRect(adjusted_rect)
                
                # 绘制标签
                if label:
                    painter.drawText(
                        adjusted_rect.x(), adjusted_rect.y() - 5,
                        label
                    )
            
            painter.end()

class ImageViewer(QWidget):
    """图像查看器组件"""
    
    image_loaded = pyqtSignal(str)
    
    def __init__(self):
        super().__init__()
        self.logger = get_logger('image_viewer')
        self.current_image_path = None
        self.current_image = None
        self.init_ui()
    
    def init_ui(self):
        """初始化UI"""
        # 主布局
        main_layout = QVBoxLayout()
        
        # 图像画布区域
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        
        self.canvas = ImageCanvas()
        self.scroll_area.setWidget(self.canvas)
        
        main_layout.addWidget(self.scroll_area)
        
        # 控制区域
        controls_layout = QHBoxLayout()
        
        # 缩放控制
        zoom_layout = QHBoxLayout()
        
        self.zoom_out_btn = QPushButton("-")
        self.zoom_out_btn.setFixedSize(30, 30)
        self.zoom_out_btn.clicked.connect(self.canvas.zoom_out)
        
        self.zoom_slider = QSlider(Qt.Horizontal)
        self.zoom_slider.setRange(10, 200)
        self.zoom_slider.setValue(100)
        self.zoom_slider.setTickPosition(QSlider.TicksBelow)
        self.zoom_slider.setTickInterval(10)
        self.zoom_slider.valueChanged.connect(self.zoom_value_changed)
        
        self.zoom_in_btn = QPushButton("+")
        self.zoom_in_btn.setFixedSize(30, 30)
        self.zoom_in_btn.clicked.connect(self.canvas.zoom_in)
        
        self.zoom_reset_btn = QPushButton("100%")
        self.zoom_reset_btn.clicked.connect(self.canvas.reset_zoom)
        
        zoom_layout.addWidget(self.zoom_out_btn)
        zoom_layout.addWidget(self.zoom_slider)
        zoom_layout.addWidget(self.zoom_in_btn)
        zoom_layout.addWidget(self.zoom_reset_btn)
        
        # 其他控制
        self.filter_combo = QComboBox()
        self.filter_combo.addItems(["原图", "灰度", "高斯模糊", "边缘检测"])
        self.filter_combo.currentIndexChanged.connect(self.apply_filter)
        
        self.save_btn = QPushButton("保存当前视图")
        self.save_btn.clicked.connect(self.save_current_view)
        
        controls_layout.addLayout(zoom_layout)
        controls_layout.addWidget(QLabel("滤镜:"))
        controls_layout.addWidget(self.filter_combo)
        controls_layout.addWidget(self.save_btn)
        
        main_layout.addLayout(controls_layout)
        
        self.setLayout(main_layout)
    
    def load_image(self, image_path):
        """加载图像"""
        try:
            self.current_image_path = image_path
            self.current_image = load_image(image_path)
            
            # 转换为QPixmap
            height, width, channel = self.current_image.shape
            bytes_per_line = 3 * width
            q_image = QImage(self.current_image.data, width, height, 
                           bytes_per_line, QImage.Format_RGB888)
            pixmap = QPixmap.fromImage(q_image)
            
            # 显示图像
            self.canvas.load_image(pixmap)
            self.canvas.clear_annotations()
            
            # 重置滤镜
            self.filter_combo.setCurrentIndex(0)
            
            # 发出信号
            self.image_loaded.emit(image_path)
            
            self.logger.info(f"已加载图像: {image_path}")
            return True
        
        except
© 版权声明

相关文章

暂无评论

none
暂无评论...