2025-08-28 12:00:56 +08:00

348 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
主界面窗口
包含视频显示区域、控制按钮和车牌号显示区域
"""
import sys
import os
from PyQt5.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QFrame, QTextEdit, QGroupBox,
QCheckBox, QSpinBox, QSlider, QGridLayout
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont, QPixmap, QPalette, QImage
from .video_widget import VideoWidget
from utils.video_capture import VideoCapture
from models.yolo_detector import YOLODetector
from models.plate_recognizer import PlateRecognizerManager
class MainWindow(QMainWindow):
"""主窗口类"""
def __init__(self):
super().__init__()
self.video_capture = None
self.yolo_detector = None
self.plate_recognizer = PlateRecognizerManager('mock') # 车牌识别管理器
self.timer = QTimer()
self.use_camera = 1 # 1: 摄像头, 0: 视频文件
self.detected_plates = [] # 存储切割后的车牌图像数组
self.current_frame = None # 存储当前帧用于车牌矫正
self.init_ui()
self.init_detector()
self.init_video_capture()
self.connect_signals()
def init_ui(self):
"""初始化用户界面"""
self.setWindowTitle("车牌检测系统 - YOLO11s")
self.setGeometry(100, 100, 1200, 800)
# 创建中央widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QHBoxLayout(central_widget)
# 左侧视频显示区域
self.create_video_area(main_layout)
# 右侧控制和信息显示区域
self.create_control_area(main_layout)
# 设置布局比例
main_layout.setStretch(0, 3) # 视频区域占3/4
main_layout.setStretch(1, 1) # 控制区域占1/4
def create_video_area(self, parent_layout):
"""创建视频显示区域"""
video_frame = QFrame()
video_frame.setFrameStyle(QFrame.StyledPanel)
video_layout = QVBoxLayout(video_frame)
# 视频显示widget
self.video_widget = VideoWidget()
video_layout.addWidget(self.video_widget)
parent_layout.addWidget(video_frame)
def create_control_area(self, parent_layout):
"""创建控制和信息显示区域"""
control_frame = QFrame()
control_frame.setFrameStyle(QFrame.StyledPanel)
control_frame.setMaximumWidth(300)
control_layout = QVBoxLayout(control_frame)
# 控制按钮组
self.create_control_buttons(control_layout)
# 检测信息显示
self.create_detection_info(control_layout)
# 车牌号显示区域
self.create_plate_display(control_layout)
# 系统状态显示
self.create_status_display(control_layout)
parent_layout.addWidget(control_frame)
def create_control_buttons(self, parent_layout):
"""创建控制按钮"""
button_group = QGroupBox("控制面板")
button_layout = QVBoxLayout(button_group)
# 开始/停止按钮
self.start_btn = QPushButton("开始检测")
self.start_btn.setMinimumHeight(40)
self.start_btn.clicked.connect(self.toggle_detection)
button_layout.addWidget(self.start_btn)
# 视频源切换
self.camera_checkbox = QCheckBox("使用摄像头")
self.camera_checkbox.setChecked(True)
self.camera_checkbox.stateChanged.connect(self.toggle_video_source)
button_layout.addWidget(self.camera_checkbox)
# 检测开关
self.detection_checkbox = QCheckBox("启用检测")
self.detection_checkbox.setChecked(True)
button_layout.addWidget(self.detection_checkbox)
parent_layout.addWidget(button_group)
def create_detection_info(self, parent_layout):
"""创建检测信息显示"""
info_group = QGroupBox("检测信息")
info_layout = QVBoxLayout(info_group)
# FPS显示
self.fps_label = QLabel("FPS: 0")
self.fps_label.setFont(QFont("Arial", 12, QFont.Bold))
info_layout.addWidget(self.fps_label)
# 检测数量
self.detection_count_label = QLabel("检测到车牌: 0")
info_layout.addWidget(self.detection_count_label)
# 模型信息
self.model_info_label = QLabel("模型: YOLO11s (ONNX)")
info_layout.addWidget(self.model_info_label)
parent_layout.addWidget(info_group)
def create_plate_display(self, parent_layout):
"""创建车牌号显示区域"""
plate_group = QGroupBox("车牌识别结果")
plate_layout = QVBoxLayout(plate_group)
# 当前识别的车牌号
self.current_plate_label = QLabel("当前车牌: 未识别")
self.current_plate_label.setFont(QFont("Arial", 14, QFont.Bold))
self.current_plate_label.setStyleSheet("color: blue; padding: 10px; border: 1px solid gray;")
plate_layout.addWidget(self.current_plate_label)
# 矫正后的车牌图像显示
self.plate_image_label = QLabel("矫正后车牌图像")
self.plate_image_label.setAlignment(Qt.AlignCenter)
self.plate_image_label.setMinimumHeight(100)
self.plate_image_label.setMaximumHeight(150)
self.plate_image_label.setStyleSheet("border: 1px solid gray; background-color: #f0f0f0;")
plate_layout.addWidget(self.plate_image_label)
# 历史车牌记录
history_label = QLabel("历史记录:")
plate_layout.addWidget(history_label)
self.plate_history = QTextEdit()
self.plate_history.setMaximumHeight(150)
self.plate_history.setReadOnly(True)
plate_layout.addWidget(self.plate_history)
# 预留接口说明
interface_label = QLabel("注: 车牌识别接口已预留可接入OCR模型")
interface_label.setStyleSheet("color: gray; font-size: 10px;")
plate_layout.addWidget(interface_label)
parent_layout.addWidget(plate_group)
def create_status_display(self, parent_layout):
"""创建系统状态显示"""
status_group = QGroupBox("系统状态")
status_layout = QVBoxLayout(status_group)
self.status_label = QLabel("状态: 就绪")
status_layout.addWidget(self.status_label)
self.gpu_status_label = QLabel("GPU: 检测中...")
status_layout.addWidget(self.gpu_status_label)
parent_layout.addWidget(status_group)
# 添加弹性空间
parent_layout.addStretch()
def init_detector(self):
"""初始化YOLO检测器"""
try:
model_path = os.path.join(os.path.dirname(__file__), "..", "yolo11sth50.onnx")
self.yolo_detector = YOLODetector(model_path)
self.model_info_label.setText(f"模型: YOLO11s (ONNX) - GPU: {self.yolo_detector.use_gpu}")
self.gpu_status_label.setText(f"GPU: {'启用' if self.yolo_detector.use_gpu else '禁用'}")
except Exception as e:
self.status_label.setText(f"模型加载失败: {str(e)}")
def init_video_capture(self):
"""初始化视频捕获"""
try:
self.video_capture = VideoCapture()
self.status_label.setText("视频捕获初始化成功")
except Exception as e:
self.status_label.setText(f"视频捕获初始化失败: {str(e)}")
def connect_signals(self):
"""连接信号和槽"""
self.timer.timeout.connect(self.update_frame)
def toggle_detection(self):
"""切换检测状态"""
if self.timer.isActive():
self.stop_detection()
else:
self.start_detection()
def start_detection(self):
"""开始检测"""
if self.video_capture and self.video_capture.start_capture(self.use_camera):
# 根据视频源类型设置定时器间隔
video_fps = self.video_capture.get_video_fps()
timer_interval = int(1000 / video_fps) # 转换为毫秒
self.timer.start(timer_interval)
self.start_btn.setText("停止检测")
source_type = "摄像头" if self.use_camera else f"视频文件({video_fps:.1f}FPS)"
self.status_label.setText(f"检测中... - {source_type}")
else:
self.status_label.setText("启动失败")
def stop_detection(self):
"""停止检测"""
self.timer.stop()
if self.video_capture:
self.video_capture.stop_capture()
self.start_btn.setText("开始检测")
self.status_label.setText("已停止")
def toggle_video_source(self, state):
"""切换视频源"""
self.use_camera = 1 if state == Qt.Checked else 0
if self.timer.isActive():
self.stop_detection()
self.start_detection()
def update_frame(self):
"""更新帧"""
if not self.video_capture:
return
frame, fps = self.video_capture.get_frame()
if frame is None:
return
# 保存当前帧用于车牌矫正
self.current_frame = frame.copy()
# 更新FPS显示
self.fps_label.setText(f"FPS: {fps:.1f}")
# 进行检测
if self.detection_checkbox.isChecked() and self.yolo_detector:
detections = self.yolo_detector.detect(frame)
frame = self.yolo_detector.draw_detections(frame, detections)
# 切割车牌图像
if detections:
self.detected_plates = self.yolo_detector.crop_plates(frame, detections)
# 统计不同类型车牌数量
blue_count = sum(1 for d in detections if d['class_id'] == 0)
green_count = sum(1 for d in detections if d['class_id'] == 1)
total_count = len(detections)
self.detection_count_label.setText(f"检测到车牌: {total_count} (蓝牌:{blue_count}, 绿牌:{green_count})")
# 调用车牌识别接口(预留)
self.recognize_plates(self.detected_plates, detections)
else:
self.detection_count_label.setText("检测到车牌: 0")
# 显示帧
self.video_widget.update_frame(frame)
def recognize_plates(self, plate_images, detections):
"""车牌识别接口(预留)"""
# 这里是预留的车牌识别接口
# 可以接入OCR模型进行车牌号识别
if plate_images and detections and self.current_frame is not None:
# 获取最新检测到的车牌信息
latest_detection = detections[-1] # 取最后一个检测结果
plate_type = "Blue Plate" if latest_detection['class_id'] == 0 else "Green Plate"
confidence = latest_detection['confidence']
# 处理蓝色车牌的矫正
corrected_image = None
if latest_detection['class_id'] == 0: # 蓝色车牌
try:
bbox = latest_detection['bbox']
corrected_image = self.plate_recognizer.preprocess_blue_plate(
plate_images[-1], self.current_frame, bbox
)
self._display_plate_image(corrected_image)
except Exception as e:
print(f"蓝色车牌矫正失败: {e}")
self.plate_image_label.setText("蓝色车牌矫正失败")
elif latest_detection['class_id'] == 1: # 绿色车牌
# 绿色车牌处理预留
self.plate_image_label.setText("绿色车牌处理\n(待实现)")
# 模拟识别结果
plate_text = f"Mock {plate_type}-{len(plate_images)}"
self.current_plate_label.setText(f"Current Plate: {plate_text} (Confidence: {confidence:.2f})")
# 添加到历史记录
import datetime
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
self.plate_history.append(f"[{timestamp}] {plate_text} (Confidence: {confidence:.2f})")
def _display_plate_image(self, image):
"""在界面上显示车牌图像"""
try:
# 将OpenCV图像转换为QPixmap
if len(image.shape) == 3:
height, width, channel = image.shape
bytes_per_line = 3 * width
q_image = QImage(image.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
else:
height, width = image.shape
bytes_per_line = width
q_image = QImage(image.data, width, height, bytes_per_line, QImage.Format_Grayscale8)
# 缩放图像以适应标签大小
pixmap = QPixmap.fromImage(q_image)
scaled_pixmap = pixmap.scaled(self.plate_image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.plate_image_label.setPixmap(scaled_pixmap)
except Exception as e:
print(f"显示车牌图像失败: {e}")
self.plate_image_label.setText(f"图像显示失败: {str(e)}")
def closeEvent(self, event):
"""窗口关闭事件"""
self.stop_detection()
event.accept()