diff --git a/main.py b/main.py index 555f93d..0ab2bfe 100644 --- a/main.py +++ b/main.py @@ -3,10 +3,11 @@ import os import cv2 import numpy as np import time +import json from datetime import datetime from collections import defaultdict, deque from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, \ - QFileDialog, QFrame, QScrollArea, QComboBox, QLineEdit, QListWidget, QListWidgetItem + QFileDialog, QFrame, QScrollArea, QComboBox, QLineEdit, QListWidget, QListWidgetItem, QDialog, QMessageBox from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter, QPen, QColor from yolopart.detector import LicensePlateYOLO @@ -345,6 +346,79 @@ class VideoThread(QThread): break self.msleep(30) # 约30fps +class ParkingFeeDialog(QDialog): + """停车费用确认对话框""" + + def __init__(self, plate_number, parking_duration, fee_amount, parent=None): + super().__init__(parent) + self.plate_number = plate_number + self.parking_duration = parking_duration + self.fee_amount = fee_amount + self.payment_confirmed = False + self.init_ui() + + def init_ui(self): + self.setWindowTitle("停车费用确认") + self.setModal(True) + self.resize(400, 300) + + layout = QVBoxLayout() + + # 车牌号信息 + plate_label = QLabel(f"车牌号: {self.plate_number}") + plate_label.setFont(QFont("Arial", 14, QFont.Bold)) + layout.addWidget(plate_label) + + # 停车时长信息 + hours = self.parking_duration // 3600 + minutes = (self.parking_duration % 3600) // 60 + seconds = self.parking_duration % 60 + duration_text = f"停车时长: {hours}小时{minutes}分钟{seconds}秒" + duration_label = QLabel(duration_text) + duration_label.setFont(QFont("Arial", 12)) + layout.addWidget(duration_label) + + # 费用信息 + fee_label = QLabel(f"应缴费用: ¥{self.fee_amount:.2f}") + fee_label.setFont(QFont("Arial", 14, QFont.Bold)) + fee_label.setStyleSheet("color: red;") + layout.addWidget(fee_label) + + # 提示信息 + tip_label = QLabel("请确认该车辆是否已经缴费:") + tip_label.setFont(QFont("Arial", 12)) + layout.addWidget(tip_label) + + # 按钮布局 + button_layout = QHBoxLayout() + + # 已缴费按钮 + paid_button = QPushButton("已缴费 - 开闸放行") + paid_button.setFont(QFont("Arial", 12)) + paid_button.setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 10px; }") + paid_button.clicked.connect(self.confirm_payment) + button_layout.addWidget(paid_button) + + # 未缴费按钮 + unpaid_button = QPushButton("未缴费 - 拒绝放行") + unpaid_button.setFont(QFont("Arial", 12)) + unpaid_button.setStyleSheet("QPushButton { background-color: #f44336; color: white; padding: 10px; }") + unpaid_button.clicked.connect(self.reject_payment) + button_layout.addWidget(unpaid_button) + + layout.addLayout(button_layout) + self.setLayout(layout) + + def confirm_payment(self): + """确认已缴费""" + self.payment_confirmed = True + self.accept() + + def reject_payment(self): + """拒绝放行""" + self.payment_confirmed = False + self.reject() + class LicensePlateWidget(QWidget): """单个车牌结果显示组件""" @@ -505,6 +579,9 @@ class MainWindow(QMainWindow): # 车牌记录存储 - 用于道闸控制 self.plate_records = {} # 格式: {plate_number: {'first_time': datetime, 'sent': bool}} + # 加载停车费用配置 + self.parking_config = self.load_parking_config() + # 识别框命令发送记录 - 防止同一识别框重复发送命令 self.frame_command_sent = {} # 格式: {plate_id: {'plate_number': str, 'command_sent': bool}} @@ -1448,25 +1525,36 @@ class MainWindow(QMainWindow): # 计算时间间隔 time_diff = (current_time - record['first_time']).total_seconds() - # 发送时间间隔命令 - message = f"{plate_number} {int(time_diff)}sec" - try: - send_command(1, message) - print(f"发送道闸命令: {message}") - - # 标记该识别框ID已发送命令 + # 显示费用确认对话框 + if self.show_parking_fee_dialog(plate_number, time_diff): + # 用户确认缴费,发送时间间隔命令 + message = f"{plate_number} {int(time_diff)}sec" + try: + send_command(1, message) + print(f"发送道闸命令: {message}") + + # 标记该识别框ID已发送命令 + self.frame_command_sent[plate_id] = { + 'plate_number': plate_number, + 'command_sent': True + } + + # 清除记录,使第三次识别时重新按首次处理 + del self.plate_records[plate_number] + + except Exception as e: + print(f"发送道闸命令失败: {e}") + else: + # 用户拒绝缴费或未缴费,不发送开闸命令 + print(f"车牌 {plate_number} 未缴费,拒绝放行") + # 标记该识别框ID已处理,避免重复弹窗 self.frame_command_sent[plate_id] = { 'plate_number': plate_number, - 'command_sent': True + 'command_sent': False } - - # 清除记录,使第三次识别时重新按首次处理 - del self.plate_records[plate_number] - - except Exception as e: - print(f"发送道闸命令失败: {e}") else: # 首次识别到车牌号(入库) + # 入库时不收费,直接放行 try: message = f"{plate_number} 通行" send_command(1, message) @@ -1527,6 +1615,56 @@ class MainWindow(QMainWindow): except Exception as e: print(f"发送关闸命令失败: {e}") + def load_parking_config(self): + """加载停车费用配置""" + try: + config_path = os.path.join(os.path.dirname(__file__), 'parking_config.json') + with open(config_path, 'r', encoding='utf-8') as f: + config = json.load(f) + print(f"停车费用配置加载成功: {config}") + return config + except Exception as e: + print(f"加载停车费用配置失败: {e}") + # 返回默认配置 + return { + "free_parking_duration": 1800, # 30分钟免费停车 + "billing_cycle": 3600, # 1小时计费周期 + "price_per_cycle": 5.0 # 每小时5元 + } + + def calculate_parking_fee(self, parking_duration): + """计算停车费用""" + free_duration = self.parking_config.get("free_parking_duration", 1800) + billing_cycle = self.parking_config.get("billing_cycle", 3600) + price_per_cycle = self.parking_config.get("price_per_cycle", 5.0) + + # 如果停车时长在免费时间内,费用为0 + if parking_duration <= free_duration: + return 0.0 + + # 计算超出免费时间的部分 + chargeable_duration = parking_duration - free_duration + + # 计算需要收费的周期数(向上取整) + cycles = (chargeable_duration + billing_cycle - 1) // billing_cycle + + # 计算总费用 + total_fee = cycles * price_per_cycle + + return total_fee + + def show_parking_fee_dialog(self, plate_number, parking_duration): + """显示停车费用确认对话框""" + fee_amount = self.calculate_parking_fee(parking_duration) + + dialog = ParkingFeeDialog(plate_number, parking_duration, fee_amount, self) + result = dialog.exec_() + + if result == QDialog.Accepted and dialog.payment_confirmed: + return True # 确认已缴费,允许开闸 + else: + return False # 未缴费或取消,拒绝开闸 + def closeEvent(self, event): """窗口关闭事件""" if self.camera_thread and self.camera_thread.running: