YOLOv11与OpenCV 联动实战:读取摄像头实时视频流并用 YOLOv11 进行检测(三)

🎪 摸鱼匠:个人主页

🎒 个人专栏:《YOLOv11实战专栏

🥇 没有好的理念,只有脚踏实地!


文章目录

        • [6.4.5 全局错误处理器](#6.4.5 全局错误处理器)
        • [6.4.6 系统状态监控](#6.4.6 系统状态监控)
        • [6.4.7 主程序中的错误处理](#6.4.7 主程序中的错误处理)
      • [6.5 用户界面设计](#6.5 用户界面设计)
        • [6.5.1 基础UI组件](#6.5.1 基础UI组件)
        • [6.5.2 控制面板实现](#6.5.2 控制面板实现)
        • [6.5.3 状态面板实现](#6.5.3 状态面板实现)
        • [6.5.4 增强的可视化器](#6.5.4 增强的可视化器)
        • [6.5.5 集成UI到主程序](#6.5.5 集成UI到主程序)
    • 七、部署与优化
      • [7.1 系统性能测试](#7.1 系统性能测试)
        • [7.1.1 性能测试框架](#7.1.1 性能测试框架)

YOLOv11与OpenCV 联动实战:读取摄像头实时视频流并用 YOLOv11 进行检测(二)

6.4.5 全局错误处理器

我们实现一个全局错误处理器,用于捕获未处理的异常:

python 复制代码
# src/utils/global_error_handler.py

import sys
import traceback
from typing import Optional

from src.utils.logger import logger

class GlobalErrorHandler:
    """
    全局错误处理器
    """
    def __init__(self):
        """初始化全局错误处理器"""
        self.original_excepthook = sys.excepthook
        sys.excepthook = self.handle_exception
    
    def handle_exception(self, exc_type, exc_value, exc_traceback):
        """
        处理未捕获的异常
        
        参数:
            exc_type: 异常类型
            exc_value: 异常值
            exc_traceback: 异常跟踪
        """
        if issubclass(exc_type, KeyboardInterrupt):
            # 允许KeyboardInterrupt正常退出
            self.original_excepthook(exc_type, exc_value, exc_traceback)
            return
        
        # 记录异常
        logger.critical("Unhandled exception:", exc_info=(exc_type, exc_value, exc_traceback))
        
        # 调用原始异常处理器
        self.original_excepthook(exc_type, exc_value, exc_traceback)
    
    def restore(self):
        """恢复原始异常处理器"""
        sys.excepthook = self.original_excepthook

# 全局错误处理器实例
global_error_handler = GlobalErrorHandler()
6.4.6 系统状态监控

我们实现一个系统状态监控器,用于监控系统运行状态并在出现问题时采取适当的行动:

python 复制代码
# src/utils/system_monitor.py

import time
import threading
import psutil
from typing import Dict, Callable, Optional

from src.utils.logger import logger

class SystemMonitor:
    """
    系统状态监控器
    """
    def __init__(self, check_interval: float = 5.0):
        """
        初始化系统监控器
        
        参数:
            check_interval: 检查间隔(秒)
        """
        self.check_interval = check_interval
        self.running = False
        self.thread = None
        
        # 监控阈值
        self.cpu_threshold = 90.0  # CPU使用率阈值(%)
        self.memory_threshold = 90.0  # 内存使用率阈值(%)
        self.disk_threshold = 90.0  # 磁盘使用率阈值(%)
        
        # 回调函数
        self.callbacks = {
            'high_cpu': [],
            'high_memory': [],
            'high_disk': [],
            'error': []
        }
        
        # 状态历史
        self.status_history = []
        self.max_history = 100
    
    def start(self) -> None:
        """启动监控"""
        if self.running:
            logger.warning("System monitor is already running")
            return
        
        self.running = True
        self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.thread.start()
        logger.info("System monitor started")
    
    def stop(self) -> None:
        """停止监控"""
        if not self.running:
            logger.warning("System monitor is not running")
            return
        
        self.running = False
        if self.thread:
            self.thread.join()
        logger.info("System monitor stopped")
    
    def _monitor_loop(self) -> None:
        """监控循环"""
        while self.running:
            try:
                # 获取系统状态
                status = self._get_system_status()
                
                # 添加到历史记录
                self.status_history.append(status)
                if len(self.status_history) > self.max_history:
                    self.status_history.pop(0)
                
                # 检查阈值
                self._check_thresholds(status)
                
                # 等待下一次检查
                time.sleep(self.check_interval)
                
            except Exception as e:
                logger.error(f"Error in system monitor: {e}")
                self._trigger_callbacks('error', {'error': str(e)})
                time.sleep(self.check_interval)
    
    def _get_system_status(self) -> Dict:
        """获取系统状态"""
        # CPU使用率
        cpu_percent = psutil.cpu_percent(interval=1)
        
        # 内存使用率
        memory = psutil.virtual_memory()
        memory_percent = memory.percent
        
        # 磁盘使用率
        disk = psutil.disk_usage('/')
        disk_percent = disk.percent
        
        return {
            'timestamp': time.time(),
            'cpu_percent': cpu_percent,
            'memory_percent': memory_percent,
            'disk_percent': disk_percent,
            'memory_available': memory.available,
            'disk_free': disk.free
        }
    
    def _check_thresholds(self, status: Dict) -> None:
        """检查阈值并触发回调"""
        # 检查CPU使用率
        if status['cpu_percent'] > self.cpu_threshold:
            self._trigger_callbacks('high_cpu', status)
        
        # 检查内存使用率
        if status['memory_percent'] > self.memory_threshold:
            self._trigger_callbacks('high_memory', status)
        
        # 检查磁盘使用率
        if status['disk_percent'] > self.disk_threshold:
            self._trigger_callbacks('high_disk', status)
    
    def _trigger_callbacks(self, event_type: str, data: Dict) -> None:
        """触发回调函数"""
        if event_type in self.callbacks:
            for callback in self.callbacks[event_type]:
                try:
                    callback(data)
                except Exception as e:
                    logger.error(f"Error in callback for {event_type}: {e}")
    
    def add_callback(self, event_type: str, callback: Callable[[Dict], None]) -> None:
        """
        添加回调函数
        
        参数:
            event_type: 事件类型
            callback: 回调函数
        """
        if event_type in self.callbacks:
            self.callbacks[event_type].append(callback)
        else:
            logger.warning(f"Unknown event type: {event_type}")
    
    def remove_callback(self, event_type: str, callback: Callable[[Dict], None]) -> None:
        """
        移除回调函数
        
        参数:
            event_type: 事件类型
            callback: 回调函数
        """
        if event_type in self.callbacks and callback in self.callbacks[event_type]:
            self.callbacks[event_type].remove(callback)
        else:
            logger.warning(f"Callback not found for event type: {event_type}")
    
    def get_status_history(self, count: Optional[int] = None) -> list:
        """
        获取状态历史
        
        参数:
            count: 返回的记录数,None表示全部
        
        返回:
            状态历史列表
        """
        if count is None:
            return self.status_history.copy()
        else:
            return self.status_history[-count:]
    
    def get_current_status(self) -> Optional[Dict]:
        """
        获取当前状态
        
        返回:
            当前状态字典
        """
        if self.status_history:
            return self.status_history[-1].copy()
        return None

# 全局系统监控器实例
system_monitor = SystemMonitor()
6.4.7 主程序中的错误处理

最后,我们在主程序中集成错误处理和日志系统:

python 复制代码
# src/main.py(添加错误处理的部分)

import os
import sys
import argparse
import time
import signal
from typing import Optional

# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from src.config_manager import config_manager
from src.detection.yolo_detector import YOLODetector
from src.tracking.multi_tracker import MultiObjectTracker
from src.zones.zone_manager import ZoneManager
from src.alerts.alert_system import AlertSystem
from src.recording.detection_recorder import DetectionRecorder
from src.visualization.result_visualizer import ResultVisualizer
from src.utils.camera_utils import setup_camera, test_camera
from src.utils.logger import logger
from src.utils.global_error_handler import global_error_handler
from src.utils.system_monitor import system_monitor
from src.utils.error_handling import handle_exceptions
from src.utils.exceptions import DetectionSystemError, CameraError, ModelLoadError

class DetectionApp:
    """
    检测应用程序主类(带错误处理)
    """
    def __init__(self, config_path: Optional[str] = None):
        """
        初始化检测应用
        
        参数:
            config_path: 自定义配置文件路径
        """
        # 加载配置
        if config_path:
            config_manager.load_config(config_path)
        
        # 初始化组件
        self.detector = None
        self.tracker = None
        self.zone_manager = None
        self.alert_system = None
        self.recorder = None
        self.visualizer = None
        self.camera = None
        self.performance_monitor = None
        
        # 运行状态
        self.running = False
        self.error_count = 0
        self.max_errors = 10  # 最大错误次数
        
        # 注册信号处理器
        signal.signal(signal.SIGINT, self.signal_handler)
        signal.signal(signal.SIGTERM, self.signal_handler)
        
        # 添加系统监控回调
        system_monitor.add_callback('high_cpu', self._handle_high_cpu)
        system_monitor.add_callback('high_memory', self._handle_high_memory)
        system_monitor.add_callback('high_disk', self._handle_high_disk)
        system_monitor.add_callback('error', self._handle_system_error)
    
    @handle_exceptions(exception_types=(DetectionSystemError, CameraError, ModelLoadError), 
                      default_return=False, reraise=False)
    def initialize(self) -> bool:
        """
        初始化所有组件(带错误处理)
        
        返回:
            是否成功初始化
        """
        try:
            logger.info("Initializing detection system...")
            
            # 启动系统监控
            system_monitor.start()
            
            # 初始化性能监控
            self.performance_monitor = PerformanceMonitor()
            
            # 初始化摄像头
            camera_index = config_manager.get('camera.index', 0)
            camera_width = config_manager.get('camera.width', 640)
            camera_height = config_manager.get('camera.height', 480)
            camera_fps = config_manager.get('camera.fps', 30)
            
            self.camera = setup_camera(
                index=camera_index,
                width=camera_width,
                height=camera_height,
                fps=camera_fps
            )
            
            if not self.camera:
                raise CameraError("Failed to initialize camera")
            
            # 测试摄像头
            if not test_camera(self.camera, duration=2):
                logger.warning("Camera test failed, but continuing...")
            
            # 初始化检测器
            model_path = config_manager.get('model.path', 'models/yolov11n.pt')
            input_size = config_manager.get('model.input_size', 640)
            conf_threshold = config_manager.get('model.conf_threshold', 0.5)
            iou_threshold = config_manager.get('model.iou_threshold', 0.45)
            device = config_manager.get('model.device', 'auto')
            
            self.detector = YOLODetector(
                model_path=model_path,
                input_size=input_size,
                conf_threshold=conf_threshold,
                iou_threshold=iou_threshold,
                device=device
            )
            
            # 初始化跟踪器
            if config_manager.get('tracking.enabled', True):
                max_age = config_manager.get('tracking.max_age', 40)
                min_hits = config_manager.get('tracking.min_hits', 3)
                iou_threshold = config_manager.get('tracking.iou_threshold', 0.3)
                
                self.tracker = MultiObjectTracker(
                    max_age=max_age,
                    min_hits=min_hits,
                    iou_threshold=iou_threshold
                )
            
            # 初始化区域管理器
            if config_manager.get('zones.enabled', True):
                zones_file = config_manager.get('zones.file_path', 'data/zones/default_zones.json')
                self.zone_manager = ZoneManager((camera_height, camera_width))
                
                if not self.zone_manager.load_zones(zones_file):
                    logger.warning("No zones file found, zones will be empty")
            
            # 初始化报警系统
            if config_manager.get('alerts.enabled', True):
                rules_file = config_manager.get('alerts.file_path', 'data/alert_rules/default_rules.json')
                self.alert_system = AlertSystem(rules_file)
            
            # 初始化记录器
            if config_manager.get('recording.enabled', True):
                output_dir = config_manager.get('recording.output_dir', 'outputs/recordings')
                self.recorder = DetectionRecorder(output_dir=output_dir)
            
            # 初始化可视化器
            self.visualizer = ResultVisualizer()
            
            logger.info("Detection system initialized successfully")
            return True
            
        except Exception as e:
            logger.error(f"Error initializing detection system: {e}")
            return False
    
    @handle_exceptions(exception_types=DetectionSystemError, 
                      default_return=None, reraise=False)
    def run(self) -> None:
        """运行检测系统(带错误处理)"""
        if not self.initialize():
            logger.error("Failed to initialize detection system")
            return
        
        logger.info("Starting detection system...")
        self.running = True
        
        # 主循环
        try:
            while self.running:
                try:
                    # 记录开始时间
                    start_time = time.time()
                    
                    # 从摄像头读取帧
                    ret, frame = self.camera.read()
                    if not ret:
                        logger.error("Failed to read frame from camera")
                        self.error_count += 1
                        
                        # 尝试重新初始化摄像头
                        if self.error_count >= 3:
                            logger.warning("Attempting to reinitialize camera...")
                            self.camera.release()
                            camera_index = config_manager.get('camera.index', 0)
                            self.camera = setup_camera(index=camera_index)
                            self.error_count = 0
                        
                        if self.error_count >= self.max_errors:
                            logger.error(f"Too many errors ({self.error_count}), stopping...")
                            break
                        
                        continue
                    
                    # 重置错误计数
                    self.error_count = 0
                    
                    # 更新性能监控
                    self.performance_monitor.update()
                    
                    # 运行检测
                    detections = self.detector.detect(frame)
                    
                    # 运行跟踪
                    tracked_objects = None
                    if self.tracker:
                        dets_for_tracking = [
                            [det['bbox'][0], det['bbox'][1], det['bbox'][2]-det['bbox'][0], det['bbox'][3]-det['bbox'][1], det['confidence']]
                            for det in detections
                        ]
                        tracked_objects = self.tracker.update(dets_for_tracking)
                    
                    # 区域过滤
                    if self.zone_manager and config_manager.get('zones.filter_enabled', True):
                        detections, _ = self.zone_manager.filter_detections_by_zones(detections)
                    
                    # 检查报警
                    if self.alert_system:
                        self.alert_system.check_alerts(detections, tracked_objects, self.zone_manager, frame)
                    
                    # 记录检测结果
                    if self.recorder:
                        self.recorder.record_detections(self.performance_monitor.frame_count, detections, tracked_objects)
                    
                    # 可视化结果
                    result_frame = self.visualizer.visualize(
                        frame, 
                        detections, 
                        tracked_objects, 
                        self.zone_manager,
                        self.performance_monitor
                    )
                    
                    # 显示结果
                    cv2.imshow('YOLOv11 Detection', result_frame)
                    
                    # 处理按键
                    key = cv2.waitKey(1) & 0xFF
                    if key == ord('q'):
                        self.running = False
                    elif key == ord('z') and self.zone_manager:
                        # 编辑区域
                        saved = self.zone_manager.edit_zones(frame)
                        if saved:
                            zones_file = config_manager.get('zones.file_path')
                            self.zone_manager.save_zones(zones_file)
                    elif key == ord('a') and self.alert_system:
                        # 切换报警系统
                        config_manager.set('alerts.enabled', not config_manager.get('alerts.enabled'))
                        logger.info(f"Alerts {'enabled' if config_manager.get('alerts.enabled') else 'disabled'}")
                    
                    # 控制帧率
                    max_fps = config_manager.get('detection.max_fps', 30)
                    if max_fps > 0:
                        elapsed = time.time() - start_time
                        delay = max(1, int((1/max_fps - elapsed) * 1000))
                        cv2.waitKey(delay)
                
                except Exception as e:
                    logger.error(f"Error in main loop: {e}")
                    self.error_count += 1
                    
                    if self.error_count >= self.max_errors:
                        logger.error(f"Too many errors ({self.error_count}), stopping...")
                        break
        
        except KeyboardInterrupt:
            logger.info("Detection system interrupted by user")
        except Exception as e:
            logger.error(f"Unexpected error in detection system: {e}")
        finally:
            self.cleanup()
    
    def cleanup(self) -> None:
        """清理资源"""
        logger.info("Cleaning up resources...")
        
        # 停止系统监控
        system_monitor.stop()
        
        if self.camera:
            self.camera.release()
        
        if self.recorder:
            self.recorder.save_statistics_to_db()
            self.recorder.export_to_csv()
            self.recorder.generate_report()
        
        cv2.destroyAllWindows()
        
        logger.info("Detection system stopped")
    
    def signal_handler(self, signum, frame):
        """信号处理器"""
        logger.info(f"Received signal {signum}, stopping...")
        self.running = False
    
    def _handle_high_cpu(self, data):
        """处理高CPU使用率"""
        logger.warning(f"High CPU usage: {data['cpu_percent']:.1f}%")
        
        # 可以采取一些缓解措施,如降低检测频率
        current_max_fps = config_manager.get('detection.max_fps', 30)
        if current_max_fps > 5:
            new_max_fps = max(5, current_max_fps - 5)
            config_manager.set('detection.max_fps', new_max_fps)
            logger.info(f"Reduced max FPS to {new_max_fps} due to high CPU usage")
    
    def _handle_high_memory(self, data):
        """处理高内存使用率"""
        logger.warning(f"High memory usage: {data['memory_percent']:.1f}%")
        
        # 可以采取一些缓解措施,如减少历史记录长度
        if self.tracker:
            # 这里可以添加减少跟踪历史长度的逻辑
            pass
    
    def _handle_high_disk(self, data):
        """处理高磁盘使用率"""
        logger.warning(f"High disk usage: {data['disk_percent']:.1f}%")
        
        # 可以采取一些缓解措施,如清理旧日志文件
        if self.recorder:
            # 这里可以添加清理旧记录的逻辑
            pass
    
    def _handle_system_error(self, data):
        """处理系统错误"""
        logger.error(f"System error: {data['error']}")

def parse_arguments():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description='YOLOv11 Real-Time Detection System')
    parser.add_argument('--config', type=str, help='Path to config file')
    parser.add_argument('--model', type=str, help='Path to model file')
    parser.add_argument('--camera', type=int, help='Camera index')
    parser.add_argument('--no-tracking', action='store_true', help='Disable tracking')
    parser.add_argument('--no-zones', action='store_true', help='Disable zones')
    parser.add_argument('--no-alerts', action='store_true', help='Disable alerts')
    parser.add_argument('--no-recording', action='store_true', help='Disable recording')
    parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], 
                       default='INFO', help='Set logging level')
    
    return parser.parse_args()

def main():
    """主函数"""
    # 解析命令行参数
    args = parse_arguments()
    
    # 设置日志级别
    logger.logger.setLevel(args.log_level)
    
    # 更新配置
    if args.config:
        config_manager.load_config(args.config)
    
    if args.model:
        config_manager.set('model.path', args.model)
    
    if args.camera is not None:
        config_manager.set('camera.index', args.camera)
    
    if args.no_tracking:
        config_manager.set('tracking.enabled', False)
    
    if args.no_zones:
        config_manager.set('zones.enabled', False)
    
    if args.no_alerts:
        config_manager.set('alerts.enabled', False)
    
    if args.no_recording:
        config_manager.set('recording.enabled', False)
    
    # 创建并运行检测应用
    try:
        app = DetectionApp()
        app.run()
    except Exception as e:
        logger.critical(f"Fatal error in main: {e}")
        global_error_handler.restore()
        raise

if __name__ == "__main__":
    main()

通过实现这些错误处理和日志记录机制,我们的系统现在能够:

  1. 优雅地处理错误:捕获并记录各种异常,防止系统崩溃。
  2. 提供详细的日志信息:记录系统运行状态和错误信息,便于调试和监控。
  3. 自动恢复:在某些错误情况下尝试自动恢复,如重新初始化摄像头。
  4. 监控系统资源:监控CPU、内存和磁盘使用率,并在超过阈值时采取相应措施。
  5. 记录性能统计:跟踪系统性能指标,帮助优化系统。

这些机制大大提高了系统的健壮性和可维护性,使其能够在生产环境中稳定运行。

6.5 用户界面设计

用户界面(UI)是实时目标检测系统与用户交互的重要部分。一个好的UI不仅能够直观地展示检测结果,还能提供便捷的控制选项,使用户能够轻松地配置和操作系统。本节将介绍如何为我们的YOLOv11实时检测系统设计一个功能丰富且用户友好的界面。

6.5.1 基础UI组件

首先,我们实现一些基础的UI组件,用于显示信息、按钮和控件:

python 复制代码
# src/visualization/ui_components.py

import cv2
import numpy as np
from typing import List, Tuple, Dict, Optional, Callable

from src.config_manager import config_manager
from src.utils.logger import logger

class UIComponent:
    """UI组件基类"""
    def __init__(self, x: int, y: int, width: int, height: int):
        """
        初始化UI组件
        
        参数:
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
        """
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.visible = True
        self.enabled = True
        self.hover = False
        self.pressed = False
    
    def contains_point(self, x: int, y: int) -> bool:
        """
        检查点是否在组件内
        
        参数:
            x: X坐标
            y: Y坐标
        
        返回:
            点是否在组件内
        """
        return (self.x <= x <= self.x + self.width and 
                self.y <= y <= self.y + self.height)
    
    def handle_mouse_event(self, event: int, x: int, y: int, flags: int) -> bool:
        """
        处理鼠标事件
        
        参数:
            event: 事件类型
            x: X坐标
            y: Y坐标
            flags: 事件标志
        
        返回:
            是否处理了事件
        """
        if not self.visible or not self.enabled:
            return False
        
        if event == cv2.EVENT_MOUSEMOVE:
            self.hover = self.contains_point(x, y)
            return self.hover
        
        elif event == cv2.EVENT_LBUTTONDOWN:
            if self.contains_point(x, y):
                self.pressed = True
                return True
        
        elif event == cv2.EVENT_LBUTTONUP:
            if self.pressed and self.contains_point(x, y):
                self.pressed = False
                self.on_click()
                return True
            self.pressed = False
        
        return False
    
    def on_click(self):
        """点击事件处理"""
        pass
    
    def draw(self, image: np.ndarray) -> None:
        """
        绘制组件
        
        参数:
            image: 要绘制的图像
        """
        pass

class Button(UIComponent):
    """按钮组件"""
    def __init__(self, x: int, y: int, width: int, height: int, text: str, 
                 callback: Optional[Callable] = None, bg_color: Tuple[int, int, int] = (50, 50, 50),
                 text_color: Tuple[int, int, int] = (255, 255, 255)):
        """
        初始化按钮
        
        参数:
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
            text: 按钮文本
            callback: 点击回调函数
            bg_color: 背景颜色
            text_color: 文本颜色
        """
        super().__init__(x, y, width, height)
        self.text = text
        self.callback = callback
        self.bg_color = bg_color
        self.text_color = text_color
        self.hover_color = tuple(min(255, c + 30) for c in bg_color)
        self.pressed_color = tuple(max(0, c - 30) for c in bg_color)
    
    def on_click(self):
        """点击事件处理"""
        if self.callback:
            self.callback()
    
    def draw(self, image: np.ndarray) -> None:
        """绘制按钮"""
        if not self.visible:
            return
        
        # 确定颜色
        if self.pressed:
            color = self.pressed_color
        elif self.hover:
            color = self.hover_color
        else:
            color = self.bg_color
        
        # 绘制按钮背景
        cv2.rectangle(image, (self.x, self.y), 
                     (self.x + self.width, self.y + self.height), 
                     color, -1)
        
        # 绘制按钮边框
        cv2.rectangle(image, (self.x, self.y), 
                     (self.x + self.width, self.y + self.height), 
                     (200, 200, 200), 1)
        
        # 绘制文本
        font_scale = 0.5
        text_size = cv2.getTextSize(self.text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0]
        text_x = self.x + (self.width - text_size[0]) // 2
        text_y = self.y + (self.height + text_size[1]) // 2
        
        cv2.putText(image, self.text, (text_x, text_y), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, self.text_color, 1)

class CheckBox(UIComponent):
    """复选框组件"""
    def __init__(self, x: int, y: int, size: int, text: str, 
                 checked: bool = False, callback: Optional[Callable[[bool], None]] = None):
        """
        初始化复选框
        
        参数:
            x: X坐标
            y: Y坐标
            size: 复选框大小
            text: 复选框文本
            checked: 是否选中
            callback: 状态改变回调函数
        """
        super().__init__(x, y, size, size)
        self.text = text
        self.checked = checked
        self.callback = callback
        self.box_color = (200, 200, 200)
        self.check_color = (0, 255, 0)
        self.text_color = (255, 255, 255)
    
    def on_click(self):
        """点击事件处理"""
        self.checked = not self.checked
        if self.callback:
            self.callback(self.checked)
    
    def draw(self, image: np.ndarray) -> None:
        """绘制复选框"""
        if not self.visible:
            return
        
        # 绘制复选框
        cv2.rectangle(image, (self.x, self.y), 
                     (self.x + self.width, self.y + self.height), 
                     self.box_color, 2)
        
        # 如果选中,绘制勾
        if self.checked:
            pt1 = (self.x + 5, self.y + self.height // 2)
            pt2 = (self.x + self.width // 2 - 2, self.y + self.height - 5)
            pt3 = (self.x + self.width - 5, self.y + 5)
            
            cv2.line(image, pt1, pt2, self.check_color, 2)
            cv2.line(image, pt2, pt3, self.check_color, 2)
        
        # 绘制文本
        font_scale = 0.5
        text_size = cv2.getTextSize(self.text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0]
        text_x = self.x + self.width + 10
        text_y = self.y + (self.height + text_size[1]) // 2
        
        cv2.putText(image, self.text, (text_x, text_y), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, self.text_color, 1)

class Slider(UIComponent):
    """滑块组件"""
    def __init__(self, x: int, y: int, width: int, height: int, 
                 min_value: float = 0.0, max_value: float = 1.0, 
                 initial_value: float = 0.5, text: str = "",
                 callback: Optional[Callable[[float], None]] = None):
        """
        初始化滑块
        
        参数:
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
            min_value: 最小值
            max_value: 最大值
            initial_value: 初始值
            text: 滑块文本
            callback: 值改变回调函数
        """
        super().__init__(x, y, width, height)
        self.min_value = min_value
        self.max_value = max_value
        self.value = initial_value
        self.text = text
        self.callback = callback
        self.dragging = False
        
        # 计算滑块位置
        self._update_handle_position()
    
    def _update_handle_position(self):
        """更新滑块位置"""
        ratio = (self.value - self.min_value) / (self.max_value - self.min_value)
        self.handle_x = self.x + int(ratio * (self.width - 20)) + 10
    
    def handle_mouse_event(self, event: int, x: int, y: int, flags: int) -> bool:
        """处理鼠标事件"""
        if not self.visible or not self.enabled:
            return False
        
        if event == cv2.EVENT_LBUTTONDOWN:
            # 检查是否点击了滑块手柄
            handle_rect = (self.handle_x - 5, self.y - 5, 
                          self.handle_x + 5, self.y + self.height + 5)
            if (handle_rect[0] <= x <= handle_rect[2] and 
                handle_rect[1] <= y <= handle_rect[3]):
                self.dragging = True
                return True
        
        elif event == cv2.EVENT_MOUSEMOVE:
            if self.dragging:
                # 更新滑块值
                new_x = max(self.x + 10, min(x, self.x + self.width - 10))
                ratio = (new_x - self.x - 10) / (self.width - 20)
                self.value = self.min_value + ratio * (self.max_value - self.min_value)
                self._update_handle_position()
                
                if self.callback:
                    self.callback(self.value)
                
                return True
        
        elif event == cv2.EVENT_LBUTTONUP:
            if self.dragging:
                self.dragging = False
                return True
        
        return super().handle_mouse_event(event, x, y, flags)
    
    def draw(self, image: np.ndarray) -> None:
        """绘制滑块"""
        if not self.visible:
            return
        
        # 绘制滑块轨道
        cv2.rectangle(image, (self.x + 10, self.y + self.height // 2 - 2), 
                     (self.x + self.width - 10, self.y + self.height // 2 + 2), 
                     (100, 100, 100), -1)
        
        # 绘制滑块手柄
        cv2.rectangle(image, (self.handle_x - 5, self.y - 5), 
                     (self.handle_x + 5, self.y + self.height + 5), 
                     (200, 200, 200), -1)
        
        # 绘制文本
        if self.text:
            font_scale = 0.5
            text_size = cv2.getTextSize(self.text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0]
            text_x = self.x + self.width // 2 - text_size[0] // 2
            text_y = self.y - 10
            
            cv2.putText(image, self.text, (text_x, text_y), 
                       cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), 1)
        
        # 绘制值
        value_text = f"{self.value:.2f}"
        font_scale = 0.4
        text_size = cv2.getTextSize(value_text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0]
        text_x = self.x + self.width + 10
        text_y = self.y + self.height // 2 + text_size[1] // 2
        
        cv2.putText(image, value_text, (text_x, text_y), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), 1)

class Panel:
    """面板容器"""
    def __init__(self, x: int, y: int, width: int, height: int, 
                 title: str = "", bg_color: Tuple[int, int, int] = (40, 40, 40),
                 title_color: Tuple[int, int, int] = (255, 255, 255)):
        """
        初始化面板
        
        参数:
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
            title: 面板标题
            bg_color: 背景颜色
            title_color: 标题颜色
        """
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.title = title
        self.bg_color = bg_color
        self.title_color = title_color
        self.visible = True
        self.components = []
    
    def add_component(self, component: UIComponent) -> None:
        """
        添加组件到面板
        
        参数:
            component: 要添加的组件
        """
        self.components.append(component)
    
    def handle_mouse_event(self, event: int, x: int, y: int, flags: int) -> bool:
        """
        处理鼠标事件
        
        参数:
            event: 事件类型
            x: X坐标
            y: Y坐标
            flags: 事件标志
        
        返回:
            是否处理了事件
        """
        if not self.visible:
            return False
        
        # 从上到下处理组件(后添加的在上层)
        for component in reversed(self.components):
            if component.handle_mouse_event(event, x, y, flags):
                return True
        
        return False
    
    def draw(self, image: np.ndarray) -> None:
        """
        绘制面板和所有组件
        
        参数:
            image: 要绘制的图像
        """
        if not self.visible:
            return
        
        # 绘制面板背景
        cv2.rectangle(image, (self.x, self.y), 
                     (self.x + self.width, self.y + self.height), 
                     self.bg_color, -1)
        
        # 绘制面板边框
        cv2.rectangle(image, (self.x, self.y), 
                     (self.x + self.width, self.y + self.height), 
                     (100, 100, 100), 1)
        
        # 绘制标题
        if self.title:
            font_scale = 0.6
            text_size = cv2.getTextSize(self.title, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0]
            text_x = self.x + (self.width - text_size[0]) // 2
            text_y = self.y + 25
            
            cv2.putText(image, self.title, (text_x, text_y), 
                       cv2.FONT_HERSHEY_SIMPLEX, font_scale, self.title_color, 1)
            
            # 绘制标题下划线
            cv2.line(image, (self.x + 20, text_y + 10), 
                    (self.x + self.width - 20, text_y + 10), 
                    (100, 100, 100), 1)
        
        # 绘制所有组件
        for component in self.components:
            component.draw(image)
6.5.2 控制面板实现

接下来,我们实现一个控制面板,包含各种控制选项,如开关、滑块和按钮:

python 复制代码
# src/visualization/control_panel.py

import cv2
import numpy as np
from typing import Dict, List, Optional

from src.visualization.ui_components import Panel, Button, CheckBox, Slider
from src.config_manager import config_manager
from src.utils.logger import logger

class ControlPanel:
    """
    控制面板类
    """
    def __init__(self, x: int, y: int, width: int, height: int):
        """
        初始化控制面板
        
        参数:
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
        """
        self.panel = Panel(x, y, width, height, "Control Panel")
        self.buttons = {}
        self.checkboxes = {}
        self.sliders = {}
        
        # 初始化UI组件
        self._init_components()
    
    def _init_components(self) -> None:
        """初始化UI组件"""
        # 添加按钮
        self._add_button("save_config", 10, 40, 100, 30, "Save Config", self._save_config)
        self._add_button("load_config", 120, 40, 100, 30, "Load Config", self._load_config)
        self._add_button("edit_zones", 230, 40, 100, 30, "Edit Zones", self._edit_zones)
        self._add_button("reset_stats", 10, 80, 100, 30, "Reset Stats", self._reset_stats)
        self._add_button("export_data", 120, 80, 100, 30, "Export Data", self._export_data)
        
        # 添加复选框
        self._add_checkbox("show_fps", 10, 130, 20, "Show FPS", 
                          config_manager.get('visualization.show_fps', True), 
                          self._toggle_show_fps)
        self._add_checkbox("show_confidence", 10, 160, 20, "Show Confidence", 
                          config_manager.get('visualization.show_confidence', True), 
                          self._toggle_show_confidence)
        self._add_checkbox("show_tracking", 10, 190, 20, "Show Tracking", 
                          config_manager.get('visualization.show_tracking', True), 
                          self._toggle_show_tracking)
        self._add_checkbox("show_zones", 10, 220, 20, "Show Zones", 
                          config_manager.get('visualization.show_zones', True), 
                          self._toggle_show_zones)
        self._add_checkbox("enable_alerts", 10, 250, 20, "Enable Alerts", 
                          config_manager.get('alerts.enabled', True), 
                          self._toggle_alerts)
        self._add_checkbox("enable_recording", 10, 280, 20, "Enable Recording", 
                          config_manager.get('recording.enabled', True), 
                          self._toggle_recording)
        
        # 添加滑块
        self._add_slider("conf_threshold", 10, 330, 200, 20, 
                        0.1, 1.0, config_manager.get('model.conf_threshold', 0.5), 
                        "Confidence", self._update_conf_threshold)
        self._add_slider("iou_threshold", 10, 370, 200, 20, 
                        0.1, 1.0, config_manager.get('model.iou_threshold', 0.45), 
                        "IOU Threshold", self._update_iou_threshold)
        self._add_slider("max_fps", 10, 410, 200, 20, 
                        1, 60, config_manager.get('detection.max_fps', 30), 
                        "Max FPS", self._update_max_fps)
    
    def _add_button(self, name: str, x: int, y: int, width: int, height: int, 
                   text: str, callback) -> None:
        """
        添加按钮
        
        参数:
            name: 按钮名称
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
            text: 按钮文本
            callback: 点击回调函数
        """
        button = Button(x, y, width, height, text, callback)
        self.buttons[name] = button
        self.panel.add_component(button)
    
    def _add_checkbox(self, name: str, x: int, y: int, size: int, text: str, 
                     checked: bool, callback) -> None:
        """
        添加复选框
        
        参数:
            name: 复选框名称
            x: X坐标
            y: Y坐标
            size: 大小
            text: 复选框文本
            checked: 是否选中
            callback: 状态改变回调函数
        """
        checkbox = CheckBox(x, y, size, text, checked, callback)
        self.checkboxes[name] = checkbox
        self.panel.add_component(checkbox)
    
    def _add_slider(self, name: str, x: int, y: int, width: int, height: int, 
                   min_value: float, max_value: float, initial_value: float, 
                   text: str, callback) -> None:
        """
        添加滑块
        
        参数:
            name: 滑块名称
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
            min_value: 最小值
            max_value: 最大值
            initial_value: 初始值
            text: 滑块文本
            callback: 值改变回调函数
        """
        slider = Slider(x, y, width, height, min_value, max_value, initial_value, text, callback)
        self.sliders[name] = slider
        self.panel.add_component(slider)
    
    # 按钮回调函数
    def _save_config(self) -> None:
        """保存配置"""
        config_manager.save_config()
        logger.info("Configuration saved")
    
    def _load_config(self) -> None:
        """加载配置"""
        config_manager.load_config()
        self._update_components_from_config()
        logger.info("Configuration loaded")
    
    def _edit_zones(self) -> None:
        """编辑区域"""
        # 这个函数需要从外部设置
        if hasattr(self, 'edit_zones_callback'):
            self.edit_zones_callback()
    
    def _reset_stats(self) -> None:
        """重置统计"""
        # 这个函数需要从外部设置
        if hasattr(self, 'reset_stats_callback'):
            self.reset_stats_callback()
    
    def _export_data(self) -> None:
        """导出数据"""
        # 这个函数需要从外部设置
        if hasattr(self, 'export_data_callback'):
            self.export_data_callback()
    
    # 复选框回调函数
    def _toggle_show_fps(self, checked: bool) -> None:
        """切换显示FPS"""
        config_manager.set('visualization.show_fps', checked)
    
    def _toggle_show_confidence(self, checked: bool) -> None:
        """切换显示置信度"""
        config_manager.set('visualization.show_confidence', checked)
    
    def _toggle_show_tracking(self, checked: bool) -> None:
        """切换显示跟踪"""
        config_manager.set('visualization.show_tracking', checked)
    
    def _toggle_show_zones(self, checked: bool) -> None:
        """切换显示区域"""
        config_manager.set('visualization.show_zones', checked)
    
    def _toggle_alerts(self, checked: bool) -> None:
        """切换报警"""
        config_manager.set('alerts.enabled', checked)
    
    def _toggle_recording(self, checked: bool) -> None:
        """切换记录"""
        config_manager.set('recording.enabled', checked)
    
    # 滑块回调函数
    def _update_conf_threshold(self, value: float) -> None:
        """更新置信度阈值"""
        config_manager.set('model.conf_threshold', value)
    
    def _update_iou_threshold(self, value: float) -> None:
        """更新IOU阈值"""
        config_manager.set('model.iou_threshold', value)
    
    def _update_max_fps(self, value: float) -> None:
        """更新最大FPS"""
        config_manager.set('detection.max_fps', int(value))
    
    def _update_components_from_config(self) -> None:
        """从配置更新组件状态"""
        # 更新复选框
        for name, checkbox in self.checkboxes.items():
            config_key = f"visualization.{name}" if name.startswith("show_") else f"{name}.enabled"
            checkbox.checked = config_manager.get(config_key, False)
        
        # 更新滑块
        conf_threshold = self.sliders.get("conf_threshold")
        if conf_threshold:
            conf_threshold.value = config_manager.get('model.conf_threshold', 0.5)
            conf_threshold._update_handle_position()
        
        iou_threshold = self.sliders.get("iou_threshold")
        if iou_threshold:
            iou_threshold.value = config_manager.get('model.iou_threshold', 0.45)
            iou_threshold._update_handle_position()
        
        max_fps = self.sliders.get("max_fps")
        if max_fps:
            max_fps.value = config_manager.get('detection.max_fps', 30)
            max_fps._update_handle_position()
    
    def handle_mouse_event(self, event: int, x: int, y: int, flags: int) -> bool:
        """
        处理鼠标事件
        
        参数:
            event: 事件类型
            x: X坐标
            y: Y坐标
            flags: 事件标志
        
        返回:
            是否处理了事件
        """
        return self.panel.handle_mouse_event(event, x, y, flags)
    
    def draw(self, image: np.ndarray) -> None:
        """
        绘制控制面板
        
        参数:
            image: 要绘制的图像
        """
        self.panel.draw(image)
    
    def set_edit_zones_callback(self, callback) -> None:
        """
        设置编辑区域回调函数
        
        参数:
            callback: 回调函数
        """
        self.edit_zones_callback = callback
    
    def set_reset_stats_callback(self, callback) -> None:
        """
        设置重置统计回调函数
        
        参数:
            callback: 回调函数
        """
        self.reset_stats_callback = callback
    
    def set_export_data_callback(self, callback) -> None:
        """
        设置导出数据回调函数
        
        参数:
            callback: 回调函数
        """
        self.export_data_callback = callback
6.5.3 状态面板实现

我们实现一个状态面板,用于显示系统状态和性能信息:

python 复制代码
# src/visualization/status_panel.py

import cv2
import numpy as np
import time
from typing import Dict, List, Optional

from src.visualization.ui_components import Panel
from src.config_manager import config_manager
from src.utils.logger import logger

class StatusPanel:
    """
    状态面板类
    """
    def __init__(self, x: int, y: int, width: int, height: int):
        """
        初始化状态面板
        
        参数:
            x: X坐标
            y: Y坐标
            width: 宽度
            height: 高度
        """
        self.panel = Panel(x, y, width, height, "System Status")
        
        # 状态信息
        self.fps = 0
        self.inference_time = 0
        self.detection_count = 0
        self.tracking_count = 0
        self.alert_count = 0
        self.frame_count = 0
        self.start_time = time.time()
        
        # 性能历史
        self.fps_history = []
        self.max_history = 100
    
    def update_status(self, fps: float, inference_time: float, 
                     detection_count: int, tracking_count: int, 
                     alert_count: int, frame_count: int) -> None:
        """
        更新状态信息
        
        参数:
            fps: 帧率
            inference_time: 推理时间
            detection_count: 检测数量
            tracking_count: 跟踪数量
            alert_count: 报警数量
            frame_count: 帧计数
        """
        self.fps = fps
        self.inference_time = inference_time
        self.detection_count = detection_count
        self.tracking_count = tracking_count
        self.alert_count = alert_count
        self.frame_count = frame_count
        
        # 更新FPS历史
        self.fps_history.append(fps)
        if len(self.fps_history) > self.max_history:
            self.fps_history.pop(0)
    
    def draw(self, image: np.ndarray) -> None:
        """
        绘制状态面板
        
        参数:
            image: 要绘制的图像
        """
        if not self.panel.visible:
            return
        
        # 绘制面板背景
        cv2.rectangle(image, (self.panel.x, self.panel.y), 
                     (self.panel.x + self.panel.width, self.panel.y + self.panel.height), 
                     self.panel.bg_color, -1)
        
        # 绘制面板边框
        cv2.rectangle(image, (self.panel.x, self.panel.y), 
                     (self.panel.x + self.panel.width, self.panel.y + self.panel.height), 
                     (100, 100, 100), 1)
        
        # 绘制标题
        if self.panel.title:
            font_scale = 0.6
            text_size = cv2.getTextSize(self.panel.title, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0]
            text_x = self.panel.x + (self.panel.width - text_size[0]) // 2
            text_y = self.panel.y + 25
            
            cv2.putText(image, self.panel.title, (text_x, text_y), 
                       cv2.FONT_HERSHEY_SIMPLEX, font_scale, self.panel.title_color, 1)
            
            # 绘制标题下划线
            cv2.line(image, (self.panel.x + 20, text_y + 10), 
                    (self.panel.x + self.panel.width - 20, text_y + 10), 
                    (100, 100, 100), 1)
        
        # 绘制状态信息
        y_offset = 60
        line_height = 25
        font_scale = 0.5
        text_color = (200, 200, 200)
        
        # FPS
        fps_text = f"FPS: {self.fps:.1f}"
        cv2.putText(image, fps_text, (self.panel.x + 10, self.panel.y + y_offset), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, 1)
        y_offset += line_height
        
        # 推理时间
        inference_text = f"Inference: {self.inference_time:.1f}ms"
        cv2.putText(image, inference_text, (self.panel.x + 10, self.panel.y + y_offset), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, 1)
        y_offset += line_height
        
        # 检测数量
        detection_text = f"Detections: {self.detection_count}"
        cv2.putText(image, detection_text, (self.panel.x + 10, self.panel.y + y_offset), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, 1)
        y_offset += line_height
        
        # 跟踪数量
        tracking_text = f"Tracking: {self.tracking_count}"
        cv2.putText(image, tracking_text, (self.panel.x + 10, self.panel.y + y_offset), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, 1)
        y_offset += line_height
        
        # 报警数量
        alert_text = f"Alerts: {self.alert_count}"
        cv2.putText(image, alert_text, (self.panel.x + 10, self.panel.y + y_offset), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, 1)
        y_offset += line_height
        
        # 运行时间
        elapsed_time = time.time() - self.start_time
        hours = int(elapsed_time // 3600)
        minutes = int((elapsed_time % 3600) // 60)
        seconds = int(elapsed_time % 60)
        time_text = f"Runtime: {hours:02d}:{minutes:02d}:{seconds:02d}"
        cv2.putText(image, time_text, (self.panel.x + 10, self.panel.y + y_offset), 
                   cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, 1)
        y_offset += line_height
        
        # 绘制FPS图表
        if len(self.fps_history) > 1:
            self._draw_fps_graph(image, y_offset + 10)
    
    def _draw_fps_graph(self, image: np.ndarray, y_offset: int) -> None:
        """
        绘制FPS图表
        
        参数:
            image: 要绘制的图像
            y_offset: Y偏移量
        """
        # 图表参数
        graph_x = self.panel.x + 10
        graph_y = self.panel.y + y_offset
        graph_width = self.panel.width - 20
        graph_height = 80
        
        # 绘制图表背景
        cv2.rectangle(image, (graph_x, graph_y), 
                     (graph_x + graph_width, graph_y + graph_height), 
                     (30, 30, 30), -1)
        
        # 绘制图表边框
        cv2.rectangle(image, (graph_x, graph_y), 
                     (graph_x + graph_width, graph_y + graph_height), 
                     (100, 100, 100), 1)
        
        # 绘制标题
        title_text = "FPS History"
        title_size = cv2.getTextSize(title_text, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)[0]
        title_x = graph_x + (graph_width - title_size[0]) // 2
        cv2.putText(image, title_text, (title_x, graph_y - 5), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1)
        
        # 绘制FPS曲线
        if len(self.fps_history) > 1:
            # 计算点的位置
            points = []
            max_fps = max(self.fps_history) if self.fps_history else 1
            
            for i, fps in enumerate(self.fps_history):
                x = graph_x + int(i * graph_width / len(self.fps_history))
                y = graph_y + graph_height - int(fps * graph_height / max_fps)
                points.append((x, y))
            
            # 绘制曲线
            for i in range(1, len(points)):
                cv2.line(image, points[i-1], points[i], (0, 255, 0), 1)
            
            # 绘制当前FPS值
            current_fps = self.fps_history[-1]
            fps_text = f"{current_fps:.1f}"
            cv2.putText(image, fps_text, (points[-1][0] + 5, points[-1][1]), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
6.5.4 增强的可视化器

最后,我们增强结果可视化器,集成UI面板并处理用户交互:

python 复制代码
# src/visualization/result_visualizer.py(增强版)

import cv2
import numpy as np
import time
from typing import List, Dict, Optional, Tuple

from src.config_manager import config_manager
from src.visualization.ui_components import UIComponent
from src.visualization.control_panel import ControlPanel
from src.visualization.status_panel import StatusPanel
from src.utils.logger import logger

class ResultVisualizer:
    """
    结果可视化器(增强版)
    """
    def __init__(self):
        """初始化结果可视化器"""
        # 初始化UI面板
        self.control_panel = None
        self.status_panel = None
        self.ui_initialized = False
        
        # 鼠标事件处理
        self.mouse_callback = None
        
        # 最近报警
        self.recent_alerts = []
        self.max_recent_alerts = 5
    
    def initialize_ui(self, image_shape: Tuple[int, int]) -> None:
        """
        初始化UI组件
        
        参数:
            image_shape: 图像形状 (height, width)
        """
        if self.ui_initialized:
            return
        
        height, width = image_shape
        
        # 创建控制面板
        panel_width = 250
        panel_height = height - 20
        self.control_panel = ControlPanel(width - panel_width - 10, 10, panel_width, panel_height)
        
        # 创建状态面板
        self.status_panel = StatusPanel(10, 10, 200, 200)
        
        # 设置鼠标回调
        self.mouse_callback = lambda event, x, y, flags, param: self._handle_mouse_event(event, x, y, flags)
        cv2.setMouseCallback(config_manager.get('ui.window_name', 'YOLOv11 Detection'), self.mouse_callback)
        
        self.ui_initialized = True
        logger.info("UI components initialized")
    
    def _handle_mouse_event(self, event: int, x: int, y: int, flags: int) -> None:
        """
        处理鼠标事件
        
        参数:
            event: 事件类型
            x: X坐标
            y: Y坐标
            flags: 事件标志
        """
        # 先检查控制面板
        if self.control_panel and self.control_panel.handle_mouse_event(event, x, y, flags):
            return
        
        # 再检查状态面板
        if self.status_panel and self.status_panel.handle_mouse_event(event, x, y, flags):
            return
    
    def visualize(self, frame: np.ndarray, detections: List[Dict], 
                 tracked_objects: Optional[np.ndarray] = None, 
                 zone_manager = None, 
                 performance_monitor = None) -> np.ndarray:
        """
        可视化检测结果
        
        参数:
            frame: 输入帧
            detections: 检测结果列表
            tracked_objects: 跟踪对象数组
            zone_manager: 区域管理器
            performance_monitor: 性能监控器
        
        返回:
            可视化结果帧
        """
        # 初始化UI(如果尚未初始化)
        if not self.ui_initialized:
            self.initialize_ui(frame.shape[:2])
        
        # 复制帧
        result_frame = frame.copy()
        
        # 绘制检测结果
        if config_manager.get('visualization.show_confidence', True):
            result_frame = self._draw_detections(result_frame, detections)
        
        # 绘制跟踪结果
        if config_manager.get('visualization.show_tracking', True) and tracked_objects is not None:
            result_frame = self._draw_tracking(result_frame, tracked_objects)
        
        # 绘制区域
        if config_manager.get('visualization.show_zones', True) and zone_manager is not None:
            result_frame = zone_manager.draw_zones(result_frame)
        
        # 绘制最近报警
        if self.recent_alerts:
            result_frame = self._draw_recent_alerts(result_frame)
        
        # 绘制性能信息
        if config_manager.get('visualization.show_fps', True) and performance_monitor is not None:
            result_frame = self._draw_performance_info(result_frame, performance_monitor)
        
        # 更新状态面板
        if self.status_panel and performance_monitor:
            self.status_panel.update_status(
                performance_monitor.fps,
                performance_monitor.inference_time * 1000,  # 转换为毫秒
                len(detections),
                len(tracked_objects) if tracked_objects is not None else 0,
                len(self.recent_alerts),
                performance_monitor.frame_count
            )
        
        # 绘制UI面板
        if self.control_panel:
            self.control_panel.draw(result_frame)
        
        if self.status_panel:
            self.status_panel.draw(result_frame)
        
        return result_frame
    
    def _draw_detections(self, image: np.ndarray, detections: List[Dict]) -> np.ndarray:
        """
        绘制检测结果
        
        参数:
            image: 输入图像
            detections: 检测结果列表
        
        返回:
            绘制结果图像
        """
        bbox_thickness = config_manager.get('visualization.bbox_thickness', 2)
        font_scale = config_manager.get('visualization.font_scale', 0.7)
        
        for detection in detections:
            # 获取边界框和类别信息
            x1, y1, x2, y2 = detection['bbox']
            class_name = detection['class_name']
            confidence = detection['confidence']
            
            # 绘制边界框
            cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), bbox_thickness)
            
            # 准备标签文本
            label = f"{class_name}: {confidence:.2f}"
            
            # 计算文本尺寸
            (label_width, label_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)
            
            # 绘制标签背景
            cv2.rectangle(image, (x1, y1 - label_height - baseline), 
                         (x1 + label_width, y1), (0, 255, 0), -1)
            
            # 绘制标签文本
            cv2.putText(image, label, (x1, y1 - baseline), 
                       cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 0), 1)
        
        return image
    
    def _draw_tracking(self, image: np.ndarray, tracked_objects: np.ndarray) -> np.ndarray:
        """
        绘制跟踪结果
        
        参数:
            image: 输入图像
            tracked_objects: 跟踪对象数组
        
        返回:
            绘制结果图像
        """
        if tracked_objects.size == 0:
            return image
        
        font_scale = config_manager.get('visualization.font_scale', 0.7)
        
        # 为每个跟踪ID分配颜色
        track_ids = tracked_objects[:, 4].astype(int)
        unique_ids = np.unique(track_ids)
        colors = {track_id: (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255)) 
                  for track_id in unique_ids}
        
        # 绘制每个跟踪对象
        for obj in tracked_objects:
            x1, y1, x2, y2, track_id = obj.astype(int)
            color = colors.get(track_id, (0, 255, 0))
            
            # 绘制边界框
            cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)
            
            # 绘制跟踪ID
            cv2.putText(image, f"ID {track_id}", (x1, y1 - 10), 
                       cv2.FONT_HERSHEY_SIMPLEX, font_scale, color, 2)
        
        return image
    
    def _draw_recent_alerts(self, image: np.ndarray) -> np.ndarray:
        """
        绘制最近报警
        
        参数:
            image: 输入图像
        
        返回:
            绘制结果图像
        """
        if not self.recent_alerts:
            return image
        
        # 绘制报警背景
        alert_bg_height = min(len(self.recent_alerts) * 25 + 40, 200)
        cv2.rectangle(image, 
                    (image.shape[1] - 310, image.shape[1] - 10), 
                    (image.shape[1] - 10, alert_bg_height), 
                    (0, 0, 0), -1)
        cv2.rectangle(image, 
                    (image.shape[1] - 310, image.shape[1] - 10), 
                    (image.shape[1] - 10, alert_bg_height), 
                    (0, 0, 255), 1)
        
        # 显示报警标题
        cv2.putText(image, "Recent Alerts", 
                   (image.shape[1] - 300, 30), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 1)
        
        # 显示报警信息
        for i, alert in enumerate(self.recent_alerts[-5:]):  # 只显示最近5条
            y_pos = 55 + i * 25
            text = f"{alert['timestamp']} - {alert['rule_name']}"
            cv2.putText(image, text, 
                       (image.shape[1] - 300, y_pos), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
        
        return image
    
    def _draw_performance_info(self, image: np.ndarray, performance_monitor) -> np.ndarray:
        """
        绘制性能信息
        
        参数:
            image: 输入图像
            performance_monitor: 性能监控器
        
        返回:
            绘制结果图像
        """
        # 绘制性能信息背景
        cv2.rectangle(image, (10, image.shape[0] - 100), (250, image.shape[0] - 10), (0, 0, 0), -1)
        cv2.rectangle(image, (10, image.shape[0] - 100), (250, image.shape[0] - 10), (100, 100, 100), 1)
        
        # 显示性能信息
        info_text = [
            f"FPS: {performance_monitor.fps:.1f}",
            f"Inference: {performance_monitor.inference_time*1000:.1f}ms",
            f"Preprocess: {performance_monitor.preprocess_time*1000:.1f}ms",
            f"Postprocess: {performance_monitor.postprocess_time*1000:.1f}ms"
        ]
        
        for i, text in enumerate(info_text):
            cv2.putText(image, text, (20, image.shape[0] - 70 + i * 25), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1)
        
        return image
    
    def add_alert(self, rule_name: str, trigger_info: Dict) -> None:
        """
        添加报警信息
        
        参数:
            rule_name: 规则名称
            trigger_info: 触发信息
        """
        from datetime import datetime
        
        alert = {
            'timestamp': datetime.now().strftime('%H:%M:%S'),
            'rule_name': rule_name,
            'trigger_info': trigger_info
        }
        
        self.recent_alerts.append(alert)
        
        # 限制最近报警列表大小
        if len(self.recent_alerts) > self.max_recent_alerts:
            self.recent_alerts.pop(0)
    
    def set_edit_zones_callback(self, callback) -> None:
        """
        设置编辑区域回调函数
        
        参数:
            callback: 回调函数
        """
        if self.control_panel:
            self.control_panel.set_edit_zones_callback(callback)
    
    def set_reset_stats_callback(self, callback) -> None:
        """
        设置重置统计回调函数
        
        参数:
            callback: 回调函数
        """
        if self.control_panel:
            self.control_panel.set_reset_stats_callback(callback)
    
    def set_export_data_callback(self, callback) -> None:
        """
        设置导出数据回调函数
        
        参数:
            callback: 回调函数
        """
        if self.control_panel:
            self.control_panel.set_export_data_callback(callback)
6.5.5 集成UI到主程序

最后,我们将UI集成到主程序中:

python 复制代码
# src/main.py(集成UI的部分)

# ... 其他导入 ...

from src.visualization.result_visualizer import ResultVisualizer

class DetectionApp:
    """
    检测应用程序主类(集成UI)
    """
    # ... 其他方法 ...
    
    def initialize(self) -> bool:
        """
        初始化所有组件(集成UI)
        
        返回:
            是否成功初始化
        """
        try:
            logger.info("Initializing detection system...")
            
            # 启动系统监控
            system_monitor.start()
            
            # 初始化性能监控
            self.performance_monitor = PerformanceMonitor()
            
            # 初始化摄像头
            camera_index = config_manager.get('camera.index', 0)
            camera_width = config_manager.get('camera.width', 640)
            camera_height = config_manager.get('camera.height', 480)
            camera_fps = config_manager.get('camera.fps', 30)
            
            self.camera = setup_camera(
                index=camera_index,
                width=camera_width,
                height=camera_height,
                fps=camera_fps
            )
            
            if not self.camera:
                raise CameraError("Failed to initialize camera")
            
            # 测试摄像头
            if not test_camera(self.camera, duration=2):
                logger.warning("Camera test failed, but continuing...")
            
            # 初始化检测器
            model_path = config_manager.get('model.path', 'models/yolov11n.pt')
            input_size = config_manager.get('model.input_size', 640)
            conf_threshold = config_manager.get('model.conf_threshold', 0.5)
            iou_threshold = config_manager.get('model.iou_threshold', 0.45)
            device = config_manager.get('model.device', 'auto')
            
            self.detector = YOLODetector(
                model_path=model_path,
                input_size=input_size,
                conf_threshold=conf_threshold,
                iou_threshold=iou_threshold,
                device=device
            )
            
            # 初始化跟踪器
            if config_manager.get('tracking.enabled', True):
                max_age = config_manager.get('tracking.max_age', 40)
                min_hits = config_manager.get('tracking.min_hits', 3)
                iou_threshold = config_manager.get('tracking.iou_threshold', 0.3)
                
                self.tracker = MultiObjectTracker(
                    max_age=max_age,
                    min_hits=min_hits,
                    iou_threshold=iou_threshold
                )
            
            # 初始化区域管理器
            if config_manager.get('zones.enabled', True):
                zones_file = config_manager.get('zones.file_path', 'data/zones/default_zones.json')
                self.zone_manager = ZoneManager((camera_height, camera_width))
                
                if not self.zone_manager.load_zones(zones_file):
                    logger.warning("No zones file found, zones will be empty")
            
            # 初始化报警系统
            if config_manager.get('alerts.enabled', True):
                rules_file = config_manager.get('alerts.file_path', 'data/alert_rules/default_rules.json')
                self.alert_system = AlertSystem(rules_file)
            
            # 初始化记录器
            if config_manager.get('recording.enabled', True):
                output_dir = config_manager.get('recording.output_dir', 'outputs/recordings')
                self.recorder = DetectionRecorder(output_dir=output_dir)
            
            # 初始化可视化器
            self.visualizer = ResultVisualizer()
            
            # 设置UI回调
            self._setup_ui_callbacks()
            
            logger.info("Detection system initialized successfully")
            return True
            
        except Exception as e:
            logger.error(f"Error initializing detection system: {e}")
            return False
    
    def _setup_ui_callbacks(self) -> None:
        """设置UI回调函数"""
        if self.visualizer:
            self.visualizer.set_edit_zones_callback(self._edit_zones)
            self.visualizer.set_reset_stats_callback(self._reset_stats)
            self.visualizer.set_export_data_callback(self._export_data)
    
    def _edit_zones(self) -> None:
        """编辑区域"""
        if self.zone_manager:
            # 获取一帧用于编辑
            ret, frame = self.camera.read()
            if ret:
                saved = self.zone_manager.edit_zones(frame)
                if saved:
                    zones_file = config_manager.get('zones.file_path')
                    self.zone_manager.save_zones(zones_file)
    
    def _reset_stats(self) -> None:
        """重置统计"""
        if self.performance_monitor:
            self.performance_monitor.reset()
        if self.recorder:
            self.recorder.stats = {
                'total_frames': 0,
                'total_detections': 0,
                'class_counts': {},
                'hourly_counts': {},
                'daily_counts': {}
            }
        logger.info("Statistics reset")
    
    def _export_data(self) -> None:
        """导出数据"""
        if self.recorder:
            self.recorder.save_statistics_to_db()
            self.recorder.export_to_csv()
            self.recorder.export_statistics_to_json()
            self.recorder.generate_report()
        logger.info("Data exported")
    
    # ... 其他方法 ...
    
    def run(self) -> None:
        """运行检测系统(集成UI)"""
        if not self.initialize():
            logger.error("Failed to initialize detection system")
            return
        
        logger.info("Starting detection system...")
        self.running = True
        
        # 设置窗口
        window_name = config_manager.get('ui.window_name', 'YOLOv11 Detection')
        cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)
        
        # 全屏模式
        if config_manager.get('ui.fullscreen', False):
            cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
        
        # 主循环
        try:
            while self.running:
                try:
                    # 记录开始时间
                    start_time = time.time()
                    
                    # 从摄像头读取帧
                    ret, frame = self.camera.read()
                    if not ret:
                        logger.error("Failed to read frame from camera")
                        self.error_count += 1
                        
                        # 尝试重新初始化摄像头
                        if self.error_count >= 3:
                            logger.warning("Attempting to reinitialize camera...")
                            self.camera.release()
                            camera_index = config_manager.get('camera.index', 0)
                            self.camera = setup_camera(index=camera_index)
                            self.error_count = 0
                        
                        if self.error_count >= self.max_errors:
                            logger.error(f"Too many errors ({self.error_count}), stopping...")
                            break
                        
                        continue
                    
                    # 重置错误计数
                    self.error_count = 0
                    
                    # 更新性能监控
                    self.performance_monitor.update()
                    
                    # 运行检测
                    detections = self.detector.detect(frame)
                    
                    # 运行跟踪
                    tracked_objects = None
                    if self.tracker:
                        dets_for_tracking = [
                            [det['bbox'][0], det['bbox'][1], det['bbox'][2]-det['bbox'][0], det['bbox'][3]-det['bbox'][1], det['confidence']]
                            for det in detections
                        ]
                        tracked_objects = self.tracker.update(dets_for_tracking)
                    
                    # 区域过滤
                    if self.zone_manager and config_manager.get('zones.filter_enabled', True):
                        detections, _ = self.zone_manager.filter_detections_by_zones(detections)
                    
                    # 检查报警
                    if self.alert_system:
                        triggered_rules = self.alert_system.check_alerts(detections, tracked_objects, self.zone_manager, frame)
                        
                        # 更新最近报警
                        for rule, trigger_info in triggered_rules:
                            self.visualizer.add_alert(rule.name, trigger_info)
                    
                    # 记录检测结果
                    if self.recorder:
                        self.recorder.record_detections(self.performance_monitor.frame_count, detections, tracked_objects)
                    
                    # 可视化结果
                    result_frame = self.visualizer.visualize(
                        frame, 
                        detections, 
                        tracked_objects, 
                        self.zone_manager,
                        self.performance_monitor
                    )
                    
                    # 显示结果
                    cv2.imshow(window_name, result_frame)
                    
                    # 处理按键
                    key = cv2.waitKey(1) & 0xFF
                    if key == ord('q'):
                        self.running = False
                    elif key == ord('f'):
                        # 切换全屏模式
                        current_mode = cv2.getWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN)
                        new_mode = cv2.WINDOW_FULLSCREEN if current_mode != cv2.WINDOW_FULLSCREEN else cv2.WINDOW_NORMAL
                        cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, new_mode)
                        config_manager.set('ui.fullscreen', new_mode == cv2.WINDOW_FULLSCREEN)
                    elif key == ord('h'):
                        # 切换控制面板可见性
                        if self.visualizer.control_panel:
                            self.visualizer.control_panel.panel.visible = not self.visualizer.control_panel.panel.visible
                    elif key == ord('s'):
                        # 切换状态面板可见性
                        if self.visualizer.status_panel:
                            self.visualizer.status_panel.panel.visible = not self.visualizer.status_panel.panel.visible
                    
                    # 控制帧率
                    max_fps = config_manager.get('detection.max_fps', 30)
                    if max_fps > 0:
                        elapsed = time.time() - start_time
                        delay = max(1, int((1/max_fps - elapsed) * 1000))
                        cv2.waitKey(delay)
                
                except Exception as e:
                    logger.error(f"Error in main loop: {e}")
                    self.error_count += 1
                    
                    if self.error_count >= self.max_errors:
                        logger.error(f"Too many errors ({self.error_count}), stopping...")
                        break
        
        except KeyboardInterrupt:
            logger.info("Detection system interrupted by user")
        except Exception as e:
            logger.error(f"Unexpected error in detection system: {e}")
        finally:
            self.cleanup()
    
    # ... 其他方法 ...

# ... 其他代码 ...

通过实现这些UI组件和面板,我们为YOLOv11实时检测系统创建了一个功能丰富且用户友好的界面。用户现在可以:

  1. 实时查看检测结果:包括边界框、类别标签和置信度。
  2. 监控系统性能:通过状态面板查看FPS、推理时间等性能指标。
  3. 调整检测参数:通过控制面板实时调整置信度阈值、IOU阈值等参数。
  4. 控制功能开关:启用/禁用跟踪、区域过滤、报警和记录功能。
  5. 查看报警信息:在界面上直接查看最近的报警信息。
  6. 管理配置:保存和加载配置文件。
  7. 编辑检测区域:通过UI编辑检测区域。
  8. 导出数据:通过UI导出检测记录和统计信息。

这个UI设计不仅提供了丰富的功能,还保持了良好的用户体验,使用户能够轻松地监控和控制实时检测系统。

七、部署与优化

7.1 系统性能测试

在部署YOLOv11与OpenCV联动的实时目标检测系统之前,进行全面的性能测试是至关重要的。性能测试不仅可以帮助我们了解系统的能力和限制,还可以识别性能瓶颈,指导我们进行有针对性的优化。本节将介绍如何设计和执行系统性能测试,以及如何分析和解释测试结果。

7.1.1 性能测试框架

首先,我们实现一个性能测试框架,用于自动化执行各种测试并收集结果:

python 复制代码
# tests/performance_test.py

import time
import psutil
import threading
import json
import os
from typing import Dict, List, Callable, Any, Optional
import numpy as np
import cv2

from src.config_manager import config_manager
from src.detection.yolo_detector import YOLODetector
from src.tracking.multi_tracker import MultiObjectTracker
from src.utils.logger import logger
from src.utils.camera_utils import setup_camera

class PerformanceTest:
    """
    性能测试类
    """
    def __init__(self, output_dir: str = "test_results"):
        """
        初始化性能测试
        
        参数:
            output_dir: 输出目录
        """
        self.output_dir = output_dir
        os.makedirs(output_dir, exist_ok=True)
        
        # 测试结果
        self.results = {}
        
        # 系统监控
        self.monitoring = False
        self.monitor_thread = None
        self.system_stats = []
        
        # 测试配置
        self.test_configs = {
            'model_sizes': ['yolov11n', 'yolov11s', 'yolov11m', 'yolov11l', 'yolov11x'],
            'input_sizes': [320, 416, 512, 640, 832],
            'batch_sizes': [1, 2, 4],
            'devices': ['cpu', 'cuda'],
            'conf_thresholds': [0.3, 0.5, 0.7],
            'iou_thresholds': [0.3, 0.5, 0.7]
        }
    
    def run_all_tests(self) -> Dict[str, Any]:
        """
        运行所有性能测试
        
        返回:
            测试结果字典
        """
        logger.info("Starting comprehensive performance tests...")
        
        # 运行基础性能测试
        self.results['basic_performance'] = self.test_basic_performance()
        
        # 运行模型大小测试
        self.results['model_size_comparison'] = self.test_model_size_comparison()
        
        # 运行输入大小测试
        self.results['input_size_comparison'] = self.test_input_size_comparison()
        
        # 运行批处理测试
        self.results['batch_size_comparison'] = self.test_batch_size_comparison()
        
        # 运行设备比较测试
        self.results['device_comparison'] = self.test_device_comparison()
        
        # 运行参数敏感性测试
        self.results['parameter_sensitivity'] = self.test_parameter_sensitivity()
        
        # 运行长期稳定性测试
        self.results['stability_test'] = self.test_stability(duration=300)  # 5分钟
        
        # 保存结果
        self.save_results()
        
        logger.info("Performance tests completed")
        return self.results
    
    def test_basic_performance(self) -> Dict[str, Any]:
        """
        基础性能测试
        
        返回:
            测试结果字典
        """
        logger.info("Running basic performance test...")
        
        # 初始化组件
        model_path = config_manager.get('model.path', 'models/yolov11n.pt')
        input_size = config_manager.get('model.input_size', 640)
        conf_threshold = config_manager.get('model.conf_threshold', 0.5)
        iou_threshold = config_manager.get('model.iou_threshold', 0.45)
        device = config_manager.get('model.device', 'auto')
        
        detector = YOLODetector(
            model_path=model_path,
            input_size=input_size,
            conf_threshold=conf_threshold,
            iou_threshold=iou_threshold,
            device=device
        )
        
        # 准备测试图像
        test_image = self._generate_test_image(input_size, input_size)
        
        # 预热
        for _ in range(10):
            detector.detect(test_image)
        
        # 测试推理性能
        inference_times = []
        preprocess_times = []
        postprocess_times = []
        
        for _ in range(100):
            start_time = time.time()
            detections = detector.detect(test_image)
            end_time = time.time()
            
            inference_times.append(detector.inference_time * 1000)  # 转换为毫秒
            preprocess_times.append(detector.preprocess_time * 1000)
            postprocess_times.append(detector.postprocess_time * 1000)
        
        # 计算统计信息
        result = {
            'model': config_manager.get('model.name', 'yolov11n'),
            'input_size': input_size,
            'device': device,
            'inference': {
                'mean': np.mean(inference_times),
                'std': np.std(inference_times),
                'min': np.min(inference_times),
                'max': np.max(inference_times),
                'median': np.median(inference_times),
                'p95': np.percentile(inference_times, 95)
            },
            'preprocess': {
                'mean': np.mean(preprocess_times),
                'std': np.std(preprocess_times),
                'min': np.min(preprocess_times),
                'max': np.max(preprocess_times)
            },
            'postprocess': {
                'mean': np.mean(postprocess_times),
                'std': np.std(postprocess_times),
                'min': np.min(postprocess_times),
                'max': np.max(postprocess_times)
            },
            'fps': 1000 / np.mean(inference_times),
            'avg_detections': np.mean([len(detector.detect(test_image)) for _ in range(10)])
        }
        
        logger.info(f"Basic performance test completed: {result['fps']:.2f} FPS")
        return result
    
    def test_model_size_comparison(self) -> List[Dict[str, Any]]:
        """
        模型大小比较测试
        
        返回:
            测试结果列表
        """
        logger.info("Running model size comparison test...")
        
        results = []
        input_size = 640
        test_image = self._generate_test_image(input_size, input_size)
        
        for model_size in self.test_configs['model_sizes']:
            logger.info(f"Testing model: {model_size}")
            
            try:
                # 初始化检测器
                model_path = f"models/{model_size}.pt"
                if not os.path.exists(model_path):
                    logger.warning(f"Model file not found: {model_path}, skipping...")
                    continue
                
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=0.5,
                    iou_threshold=0.45,
                    device='cuda' if torch.cuda.is_available() else 'cpu'
                )
                
                # 预热
                for _ in range(5):
                    detector.detect(test_image)
                
                # 测试推理性能
                inference_times = []
                for _ in range(50):
                    detector.detect(test_image)
                    inference_times.append(detector.inference_time * 1000)
                
                # 计算统计信息
                result = {
                    'model': model_size,
                    'input_size': input_size,
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps': 1000 / np.mean(inference_times),
                    'avg_detections': np.mean([len(detector.detect(test_image)) for _ in range(5)])
                }
                
                results.append(result)
                logger.info(f"Model {model_size}: {result['fps']:.2f} FPS")
                
            except Exception as e:
                logger.error(f"Error testing model {model_size}: {e}")
        
        return results
    
    def test_input_size_comparison(self) -> List[Dict[str, Any]]:
        """
        输入大小比较测试
        
        返回:
            测试结果列表
        """
        logger.info("Running input size comparison test...")
        
        results = []
        model_path = config_manager.get('model.path', 'models/yolov11n.pt')
        
        for input_size in self.test_configs['input_sizes']:
            
```python
            logger.info(f"Testing input size: {input_size}")
            
            try:
                # 初始化检测器
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=0.5,
                    iou_threshold=0.45,
                    device='cuda' if torch.cuda.is_available() else 'cpu'
                )
                
                # 准备测试图像
                test_image = self._generate_test_image(input_size, input_size)
                
                # 预热
                for _ in range(5):
                    detector.detect(test_image)
                
                # 测试推理性能
                inference_times = []
                for _ in range(50):
                    detector.detect(test_image)
                    inference_times.append(detector.inference_time * 1000)
                
                # 计算统计信息
                result = {
                    'input_size': input_size,
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps': 1000 / np.mean(inference_times),
                    'avg_detections': np.mean([len(detector.detect(test_image)) for _ in range(5)])
                }
                
                results.append(result)
                logger.info(f"Input size {input_size}: {result['fps']:.2f} FPS")
                
            except Exception as e:
                logger.error(f"Error testing input size {input_size}: {e}")
        
        return results
    
    def test_batch_size_comparison(self) -> List[Dict[str, Any]]:
        """
        批处理大小比较测试
        
        返回:
            测试结果列表
        """
        logger.info("Running batch size comparison test...")
        
        results = []
        model_path = config_manager.get('model.path', 'models/yolov11n.pt')
        input_size = 640
        
        for batch_size in self.test_configs['batch_sizes']:
            logger.info(f"Testing batch size: {batch_size}")
            
            try:
                # 初始化检测器
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=0.5,
                    iou_threshold=0.45,
                    device='cuda' if torch.cuda.is_available() else 'cpu'
                )
                
                # 准备测试图像批次
                test_images = [self._generate_test_image(input_size, input_size) for _ in range(batch_size)]
                
                # 预热
                for _ in range(5):
                    detector.detect_batch(test_images)
                
                # 测试推理性能
                inference_times = []
                for _ in range(50):
                    detector.detect_batch(test_images)
                    inference_times.append(detector.inference_time * 1000)
                
                # 计算统计信息
                result = {
                    'batch_size': batch_size,
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps_per_image': batch_size * 1000 / np.mean(inference_times),
                    'avg_detections_per_image': np.mean([len(detector.detect(test_images[0])) for _ in range(5)])
                }
                
                results.append(result)
                logger.info(f"Batch size {batch_size}: {result['fps_per_image']:.2f} FPS per image")
                
            except Exception as e:
                logger.error(f"Error testing batch size {batch_size}: {e}")
        
        return results
    
    def test_device_comparison(self) -> List[Dict[str, Any]]:
        """
        设备比较测试
        
        返回:
            测试结果列表
        """
        logger.info("Running device comparison test...")
        
        results = []
        model_path = config_manager.get('model.path', 'models/yolov11n.pt')
        input_size = 640
        test_image = self._generate_test_image(input_size, input_size)
        
        # 测试CPU
        if 'cpu' in self.test_configs['devices']:
            logger.info("Testing CPU device...")
            try:
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=0.5,
                    iou_threshold=0.45,
                    device='cpu'
                )
                
                # 预热
                for _ in range(5):
                    detector.detect(test_image)
                
                # 测试推理性能
                inference_times = []
                for _ in range(50):
                    detector.detect(test_image)
                    inference_times.append(detector.inference_time * 1000)
                
                # 计算统计信息
                result = {
                    'device': 'cpu',
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps': 1000 / np.mean(inference_times),
                    'avg_detections': np.mean([len(detector.detect(test_image)) for _ in range(5)])
                }
                
                results.append(result)
                logger.info(f"CPU: {result['fps']:.2f} FPS")
                
            except Exception as e:
                logger.error(f"Error testing CPU device: {e}")
        
        # 测试CUDA
        if 'cuda' in self.test_configs['devices'] and torch.cuda.is_available():
            logger.info("Testing CUDA device...")
            try:
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=0.5,
                    iou_threshold=0.45,
                    device='cuda'
                )
                
                # 预热
                for _ in range(5):
                    detector.detect(test_image)
                
                # 测试推理性能
                inference_times = []
                for _ in range(50):
                    detector.detect(test_image)
                    inference_times.append(detector.inference_time * 1000)
                
                # 计算统计信息
                result = {
                    'device': 'cuda',
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps': 1000 / np.mean(inference_times),
                    'avg_detections': np.mean([len(detector.detect(test_image)) for _ in range(5)])
                }
                
                results.append(result)
                logger.info(f"CUDA: {result['fps']:.2f} FPS")
                
            except Exception as e:
                logger.error(f"Error testing CUDA device: {e}")
        
        return results
    
    def test_parameter_sensitivity(self) -> Dict[str, Any]:
        """
        参数敏感性测试
        
        返回:
            测试结果字典
        """
        logger.info("Running parameter sensitivity test...")
        
        results = {}
        model_path = config_manager.get('model.path', 'models/yolov11n.pt')
        input_size = 640
        test_image = self._generate_test_image(input_size, input_size)
        
        # 测试置信度阈值
        conf_results = []
        for conf_threshold in self.test_configs['conf_thresholds']:
            logger.info(f"Testing confidence threshold: {conf_threshold}")
            
            try:
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=conf_threshold,
                    iou_threshold=0.45,
                    device='cuda' if torch.cuda.is_available() else 'cpu'
                )
                
                # 预热
                for _ in range(5):
                    detector.detect(test_image)
                
                # 测试推理性能
                inference_times = []
                detection_counts = []
                
                for _ in range(50):
                    detections = detector.detect(test_image)
                    inference_times.append(detector.inference_time * 1000)
                    detection_counts.append(len(detections))
                
                # 计算统计信息
                result = {
                    'conf_threshold': conf_threshold,
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps': 1000 / np.mean(inference_times),
                    'avg_detections': np.mean(detection_counts)
                }
                
                conf_results.append(result)
                logger.info(f"Conf threshold {conf_threshold}: {result['fps']:.2f} FPS, {result['avg_detections']:.1f} detections")
                
            except Exception as e:
                logger.error(f"Error testing confidence threshold {conf_threshold}: {e}")
        
        results['confidence_threshold'] = conf_results
        
        # 测试IOU阈值
        iou_results = []
        for iou_threshold in self.test_configs['iou_thresholds']:
            logger.info(f"Testing IOU threshold: {iou_threshold}")
            
            try:
                detector = YOLODetector(
                    model_path=model_path,
                    input_size=input_size,
                    conf_threshold=0.5,
                    iou_threshold=iou_threshold,
                    device='cuda' if torch.cuda.is_available() else 'cpu'
                )
                
                # 预热
                for _ in range(5):
                    detector.detect(test_image)
                
                # 测试推理性能
                inference_times = []
                detection_counts = []
                
                for _ in range(50):
                    detections = detector.detect(test_image)
                    inference_times.append(detector.inference_time * 1000)
                    detection_counts.append(len(detections))
                
                # 计算统计信息
                result = {
                    'iou_threshold': iou_threshold,
                    'inference_mean': np.mean(inference_times),
                    'inference_std': np.std(inference_times),
                    'fps': 1000 / np.mean(inference_times),
                    'avg_detections': np.mean(detection_counts)
                }
                
                iou_results.append(result)
                logger.info(f"IOU threshold {iou_threshold}: {result['fps']:.2f} FPS, {result['avg_detections']:.1f} detections")
                
            except Exception as e:
                logger.error(f"Error testing IOU threshold {iou_threshold}: {e}")
        
        results['iou_threshold'] = iou_results
        
        return results
    
    def test_stability(self, duration: int = 300) -> Dict[str, Any]:
        """
        长期稳定性测试
        
        参数:
            duration: 测试持续时间(秒)
        
        返回:
            测试结果字典
        """
        logger.info(f"Running stability test for {duration} seconds...")
        
        # 初始化组件
        model_path = config_manager.get('model.path', 'models/yolov11n.pt')
        input_size = config_manager.get('model.input_size', 640)
        conf_threshold = config_manager.get('model.conf_threshold', 0.5)
        iou_threshold = config_manager.get('model.iou_threshold', 0.45)
        device = config_manager.get('model.device', 'auto')
        
        detector = YOLODetector(
            model_path=model_path,
            input_size=input_size,
            conf_threshold=conf_threshold,
            iou_threshold=iou_threshold,
            device=device
        )
        
        # 准备测试图像
        test_image = self._generate_test_image(input_size, input_size)
        
        # 启动系统监控
        self._start_system_monitoring()
        
        # 测试数据
        inference_times = []
        detection_counts = []
        timestamps = []
        
        # 预热
        for _ in range(10):
            detector.detect(test_image)
        
        # 运行稳定性测试
        start_time = time.time()
        while time.time() - start_time < duration:
            timestamp = time.time()
            detections = detector.detect(test_image)
            
            inference_times.append(detector.inference_time * 1000)
            detection_counts.append(len(detections))
            timestamps.append(timestamp)
            
            # 控制测试频率
            time.sleep(0.1)
        
        # 停止系统监控
        self._stop_system_monitoring()
        
        # 计算统计信息
        result = {
            'duration': duration,
            'total_frames': len(inference_times),
            'inference': {
                'mean': np.mean(inference_times),
                'std': np.std(inference_times),
                'min': np.min(inference_times),
                'max': np.max(inference_times),
                'median': np.median(inference_times),
                'p95': np.percentile(inference_times, 95),
                'p99': np.percentile(inference_times, 99)
            },
            'detections': {
                'mean': np.mean(detection_counts),
                'std': np.std(detection_counts),
                'min': np.min(detection_counts),
                'max': np.max(detection_counts)
            },
            'fps': len(inference_times) / duration,
            'system_stats': self.system_stats
        }
        
        logger.info(f"Stability test completed: {result['fps']:.2f} FPS")
        return result
    
    def _generate_test_image(self, width: int, height: int) -> np.ndarray:
        """
        生成测试图像
        
        参数:
            width: 图像宽度
            height: 图像高度
        
        返回:
            测试图像
        """
        # 生成随机图像
        image = np.random.randint(0, 255, (height, width, 3), dtype=np.uint8)
        
        # 添加一些简单的对象(矩形)
        for _ in range(np.random.randint(1, 5)):
            x1 = np.random.randint(0, width // 2)
            y1 = np.random.randint(0, height // 2)
            x2 = x1 + np.random.randint(20, width // 2)
            y2 = y1 + np.random.randint(20, height // 2)
            color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))
            cv2.rectangle(image, (x1, y1), (x2, y2), color, -1)
        
        return image
    
    def _start_system_monitoring(self) -> None:
        """启动系统监控"""
        self.monitoring = True
        self.system_stats = []
        self.monitor_thread = threading.Thread(target=self._monitor_system)
        self.monitor_thread.daemon = True
        self.monitor_thread.start()
    
    def _stop_system_monitoring(self) -> None:
        """停止系统监控"""
        self.monitoring = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=1)
    
    def _monitor_system(self) -> None:
        """监控系统资源使用情况"""
        while self.monitoring:
            try:
                # 获取CPU使用率
                cpu_percent = psutil.cpu_percent(interval=0.1)
                
                # 获取内存使用情况
                memory = psutil.virtual_memory()
                memory_percent = memory.percent
                
                # 获取GPU使用情况(如果可用)
                gpu_percent = 0
                gpu_memory_percent = 0
                if torch.cuda.is_available():
                    gpu_percent = torch.cuda.utilization()
                    gpu_memory_percent = torch.cuda.memory_allocated() / torch.cuda.max_memory_allocated() * 100
                
                # 记录系统状态
                stat = {
                    'timestamp': time.time(),
                    'cpu_percent': cpu_percent,
                    'memory_percent': memory_percent,
                    'gpu_percent': gpu_percent,
                    'gpu_memory_percent': gpu_memory_percent
                }
                
                self.system_stats.append(stat)
                
            except Exception as e:
                logger.error(f"Error monitoring system: {e}")
    
    def save_results(self) -> None:
        """保存测试结果"""
        # 保存为JSON文件
        results_file = os.path.join(self.output_dir, f"performance_test_{int(time.time())}.json")
        with open(results_file, 'w') as f:
            json.dump(self.results, f, indent=2)
        
        logger.info(f"Test results saved to {results_file}")
        
        # 生成报告
        self._generate_report()
    
    def _generate_report(self) -> None:
        """生成性能测试报告"""
        report_file = os.path.join(self.output_dir, f"performance_report_{int(time.time())}.md")
        
        with open(report_file, 'w') as f:
            f.write("# YOLOv11 Performance Test Report\n\n")
            f.write(f"Generated at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
            
            # 基础性能测试结果
            if 'basic_performance' in self.results:
                f.write("## Basic Performance Test\n\n")
                basic = self.results['basic_performance']
                f.write(f"- Model: {basic['model']}\n")
                f.write(f"- Input Size: {basic['input_size']}\n")
                f.write(f"- Device: {basic['device']}\n")
                f.write(f"- FPS: {basic['fps']:.2f}\n")
                f.write(f"- Average Detections: {basic['avg_detections']:.2f}\n")
                f.write(f"- Inference Time (ms): {basic['inference']['mean']:.2f} ± {basic['inference']['std']:.2f}\n\n")
            
            # 模型大小比较结果
            if 'model_size_comparison' in self.results:
                f.write("## Model Size Comparison\n\n")
                f.write("| Model | FPS | Inference Time (ms) | Avg Detections |\n")
                f.write("|-------|-----|---------------------|----------------|\n")
                
                for result in self.results['model_size_comparison']:
                    f.write(f"| {result['model']} | {result['fps']:.2f} | {result['inference_mean']:.2f} | {result['avg_detections']:.2f} |\n")
                
                f.write("\n")
            
            # 输入大小比较结果
            if 'input_size_comparison' in self.results:
                f.write("## Input Size Comparison\n\n")
                f.write("| Input Size | FPS | Inference Time (ms) | Avg Detections |\n")
                f.write("|------------|-----|---------------------|----------------|\n")
                
                for result in self.results['input_size_comparison']:
                    f.write(f"| {result['input_size']} | {result['fps']:.2f} | {result['inference_mean']:.2f} | {result['avg_detections']:.2f} |\n")
                
                f.write("\n")
            
            # 批处理大小比较结果
            if 'batch_size_comparison' in self.results:
                f.write("## Batch Size Comparison\n\n")
                f.write("| Batch Size | FPS per Image | Inference Time (ms) | Avg Detections per Image |\n")
                f.write("|------------|---------------|---------------------|---------------------------|\n")
                
                for result in self.results['batch_size_comparison']:
                    f.write(f"| {result['batch_size']} | {result['fps_per_image']:.2f} | {result['inference_mean']:.2f} | {result['avg_detections_per_image']:.2f} |\n")
                
                f.write("\n")
            
            # 设备比较结果
            if 'device_comparison' in self.results:
                f.write("## Device Comparison\n\n")
                f.write("| Device | FPS | Inference Time (ms) | Avg Detections |\n")
                f.write("|--------|-----|---------------------|----------------|\n")
                
                for result in self.results['device_comparison']:
                    f.write(f"| {result['device']} | {result['fps']:.2f} | {result['inference_mean']:.2f} | {result['avg_detections']:.2f} |\n")
                
                f.write("\n")
            
            # 参数敏感性测试结果
            if 'parameter_sensitivity' in self.results:
                f.write("## Parameter Sensitivity\n\n")
                
                # 置信度阈值
                f.write("### Confidence Threshold\n\n")
                f.write("| Threshold | FPS | Inference Time (ms) | Avg Detections |\n")
                f.write("|-----------|-----|---------------------|----------------|\n")
                
                for result in self.results['parameter_sensitivity']['confidence_threshold']:
                    f.write(f"| {result['conf_threshold']} | {result['fps']:.2f} | {result['inference_mean']:.2f} | {result['avg_detections']:.2f} |\n")
                
                f.write("\n")
                
                # IOU阈值
                f.write("### IOU Threshold\n\n")
                f.write("| Threshold | FPS | Inference Time (ms) | Avg Detections |\n")
                f.write("|-----------|-----|---------------------|----------------|\n")
                
                for result in self.results['parameter_sensitivity']['iou_threshold']:
                    f.write(f"| {result['iou_threshold']} | {result['fps']:.2f} | {result['inference_mean']:.2f} | {result['avg_detections']:.2f} |\n")
                
                f.write("\n")
            
            # 稳定性测试结果
            if 'stability_test' in self.results:
                f.write("## Stability Test\n\n")
                stability = self.results['stability_test']
                f.write(f"- Duration: {stability['duration']} seconds\n")
                f.write(f"- Total Frames: {stability['total_frames']}\n")
                f.write(f"- FPS: {stability['fps']:.2f}\n")
                f.write(f"- Inference Time (ms): {stability['inference']['mean']:.2f} ± {stability['inference']['std']:.2f}\n")
                f.write(f"- Min/Max Inference Time (ms): {stability['inference']['min']:.2f} / {stability['inference']['max']:.2f}\n")
                f.write(f"- 95th Percentile Inference Time (ms): {stability['inference']['p95']:.2f}\n")
                f.write(f"- 99th Percentile Inference Time (ms): {stability['inference']['p99']:.2f}\n")
                f.write(f"- Average Detections: {stability['detections']['mean']:.2f}\n\n")
        
        logger.info(f"Performance report generated at {report_file}")

def run_performance_tests() -> None:
    """运行性能测试"""
    # 创建性能测试实例
    test = PerformanceTest()
    
    # 运行所有测试
    results = test.run_all_tests()
    
    # 打印摘要
    print("\n=== Performance Test Summary ===")
    
    if 'basic_performance' in results:
        basic = results['basic_performance']
        print(f"Basic Performance: {basic['fps']:.2f} FPS, {basic['inference']['mean']:.2f}ms inference time")
    
    if 'model_size_comparison' in results:
        print("\nModel Size Comparison:")
        for result in results['model_size_comparison']:
            print(f"  {result['model']}: {result['fps']:.2f} FPS")
    
    if 'device_comparison' in results:
        print("\nDevice Comparison:")
        for result in results['device_comparison']:
            print(f"  {result['device']}: {result['fps']:.2f} FPS")
    
    print("\nTest results and report saved to 'test_results' directory")

if __name__ == "__main__":
    run_performance_tests()

未完待续>>>详见YOLOv11与OpenCV 联动实战:读取摄像头实时视频流并用 YOLOv11 进行检测(四)

相关推荐
杨浦老苏2 小时前
开源的AI编程工作站HolyClaude
人工智能·docker·ai·编辑器·开发·群晖
Pyeako2 小时前
PyQt5 + PaddleOCR实战:打造桌面级实时文字识别工具
开发语言·人工智能·python·qt·paddleocr·pyqt5
unclejet2 小时前
数字化转型深水区:AI结对编程破解研发痛点
人工智能·结对编程
wAEWQ6Ib72 小时前
使用 C# 实现 RTF 文档转 PDF 格式
人工智能
zxsz_com_cn2 小时前
设备预测性维护模型构建方法
人工智能
chenglin0162 小时前
AI 服务企业级数据隐私与安全
网络·人工智能·安全
大数据AI人工智能培训专家培训讲师叶梓3 小时前
Merlin:面向腹部 CT 的三维视觉语言基础模型
人工智能·计算机视觉·大模型·医疗·ct·视觉大模型·医疗人工智能
喝凉白开都长肉的大胖子3 小时前
在 Matplotlib 中fontweight一般怎么设置
python·matplotlib
AI_Auto3 小时前
【智能制造】-五大AI场景重塑智能制造
人工智能·制造