diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/License_plate_recognition.iml b/.idea/License_plate_recognition.iml deleted file mode 100644 index fb56de3..0000000 --- a/.idea/License_plate_recognition.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index fb9fc56..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9b0ce31..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 288b36b..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/CRNN_part/__pycache__/crnn_interface.cpython-39.pyc b/CRNN_part/__pycache__/crnn_interface.cpython-39.pyc new file mode 100644 index 0000000..8ccefa7 Binary files /dev/null and b/CRNN_part/__pycache__/crnn_interface.cpython-39.pyc differ diff --git a/LPRNET_part/LPRNet__iteration_74000.pth b/LPRNET_part/LPRNet__iteration_74000.pth deleted file mode 100644 index 6189faa..0000000 Binary files a/LPRNET_part/LPRNet__iteration_74000.pth and /dev/null differ diff --git a/LPRNET_part/吉CF18040.jpg b/LPRNET_part/吉CF18040.jpg deleted file mode 100644 index 29d94e6..0000000 Binary files a/LPRNET_part/吉CF18040.jpg and /dev/null differ diff --git a/LPRNET_part/藏A0DBN8.jpg b/LPRNET_part/藏A0DBN8.jpg deleted file mode 100644 index 32ddef3..0000000 Binary files a/LPRNET_part/藏A0DBN8.jpg and /dev/null differ diff --git a/OCR_part/__pycache__/ocr_interface.cpython-39.pyc b/OCR_part/__pycache__/ocr_interface.cpython-39.pyc new file mode 100644 index 0000000..4647c04 Binary files /dev/null and b/OCR_part/__pycache__/ocr_interface.cpython-39.pyc differ diff --git a/communicate.py b/communicate.py new file mode 100644 index 0000000..9a05cde --- /dev/null +++ b/communicate.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +向Hi3861设备发送JSON命令 +""" + +import socket +import json +import time +import pyttsx3 +import threading + +target_ip = "192.168.43.12" +target_port = 8081 + +def speak_text(text): + """ + 使用文本转语音播放文本 + 每次调用都创建新的引擎实例以避免并发问题 + """ + def _speak(): + try: + if text and text.strip(): # 确保文本不为空 + # 在线程内部创建新的引擎实例 + engine = pyttsx3.init() + # 设置语音速度 + engine.setProperty('rate', 150) + # 设置音量(0.0到1.0) + engine.setProperty('volume', 0.8) + + engine.say(text) + engine.runAndWait() + + # 清理引擎 + engine.stop() + del engine + except Exception as e: + print(f"语音播放失败: {e}") + + # 在新线程中播放语音,避免阻塞 + speech_thread = threading.Thread(target=_speak) + speech_thread.daemon = True + speech_thread.start() + +def send_command(cmd, text): + #cmd为1,道闸打开十秒后关闭,oled显示字符串信息(默认使用及cmd为4) + #cmd为2,道闸舵机向打开方向旋转90度,oled上不显示(仅在qt界面手动开闸时调用) + #cmd为3,道闸舵机向关闭方向旋转90度,oled上不显示(仅在qt界面手动关闸时调用) + #cmd为4,oled显示字符串信息,道闸舵机不旋转 + + command = { + "cmd": cmd, + "text": text + } + + json_command = json.dumps(command, ensure_ascii=False) + try: + # 创建UDP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(json_command.encode('utf-8'), (target_ip, target_port)) + + # 发送命令后播放语音 + if text and text.strip(): + speak_text(text) + + except Exception as e: + print(f"发送命令失败: {e}") + finally: + sock.close() \ No newline at end of file diff --git a/lightCRNN_part/__pycache__/lightcrnn_interface.cpython-39.pyc b/lightCRNN_part/__pycache__/lightcrnn_interface.cpython-39.pyc new file mode 100644 index 0000000..2833de0 Binary files /dev/null and b/lightCRNN_part/__pycache__/lightcrnn_interface.cpython-39.pyc differ diff --git a/main.py b/main.py index b9b8f21..1f9d349 100644 --- a/main.py +++ b/main.py @@ -2,14 +2,15 @@ import sys import os import cv2 import numpy as np +import time +from datetime import datetime from collections import defaultdict, deque from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, \ - QFileDialog, QFrame, QScrollArea, QComboBox, QListWidget, QListWidgetItem, QLineEdit, QMessageBox, QDialog, \ - QDialogButtonBox, QFormLayout, QTextEdit -from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread, QDateTime + QFileDialog, QFrame, QScrollArea, QComboBox, QLineEdit, QListWidget, QListWidgetItem +from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter, QPen, QColor from yolopart.detector import LicensePlateYOLO -from gate_control import GateController, WhitelistManager +from communicate import send_command #选择使用哪个模块 # from LPRNET_part.lpr_interface import LPRNmodel_predict @@ -21,42 +22,10 @@ from gate_control import GateController, WhitelistManager # from CRNN_part.crnn_interface import LPRNmodel_predict # from CRNN_part.crnn_interface import LPRNinitialize_model -class PlateInputDialog(QDialog): - """车牌输入对话框""" - def __init__(self, title, default_text=""): - super().__init__() - self.setWindowTitle(title) - self.setFixedSize(300, 100) - self.setWindowModality(Qt.ApplicationModal) - - layout = QVBoxLayout() - - # 车牌输入框 - self.plate_input = QLineEdit() - self.plate_input.setPlaceholderText("请输入车牌号") - self.plate_input.setText(default_text) - self.plate_input.setMaxLength(10) - - # 按钮 - buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - buttons.accepted.connect(self.accept) - buttons.rejected.connect(self.reject) - - layout.addWidget(QLabel("车牌号:")) - layout.addWidget(self.plate_input) - layout.addWidget(buttons) - - self.setLayout(layout) - - def get_plate_number(self): - """获取输入的车牌号""" - return self.plate_input.text().strip() - - class PlateStabilizer: """车牌识别结果稳定器""" - def __init__(self, history_size=10, confidence_threshold=0.6, stability_frames=5): + def __init__(self, history_size=10, confidence_threshold=0.6, stability_frames=10): self.history_size = history_size # 历史帧数量 self.confidence_threshold = confidence_threshold # 置信度阈值 self.stability_frames = stability_frames # 稳定帧数要求 @@ -69,6 +38,10 @@ class PlateStabilizer: self.plate_id_counter = 0 # 车牌位置追踪 self.plate_positions = {} + # 车牌最后检测时间戳 + self.plate_last_seen = {} + # 车牌帧计数器,用于跟踪每个车牌被检测的帧数 + self.plate_frame_count = {} def calculate_plate_distance(self, pos1, pos2): """计算两个车牌位置的距离""" @@ -83,6 +56,8 @@ class PlateStabilizer: def match_plates_to_history(self, current_detections): """将当前检测结果匹配到历史记录""" + import time + current_time = time.time() matched_plates = {} used_ids = set() @@ -95,9 +70,14 @@ class PlateStabilizer: for plate_id, last_pos in self.plate_positions.items(): if plate_id in used_ids: continue + + # 检查时间间隔,如果超过1秒则不匹配 + last_seen_time = self.plate_last_seen.get(plate_id, 0) + if current_time - last_seen_time > 1.0: # 超过1秒不匹配 + continue distance = self.calculate_plate_distance(bbox, last_pos) - if distance < min_distance and distance < 100: # 距离阈值 + if distance < min_distance and distance < 50: # 降低距离阈值,提高匹配精度 min_distance = distance best_match_id = plate_id @@ -105,12 +85,18 @@ class PlateStabilizer: matched_plates[best_match_id] = detection used_ids.add(best_match_id) self.plate_positions[best_match_id] = bbox + self.plate_last_seen[best_match_id] = current_time # 更新时间戳 + # 增加帧计数 + self.plate_frame_count[best_match_id] = self.plate_frame_count.get(best_match_id, 0) + 1 else: # 创建新的车牌ID new_id = f"plate_{self.plate_id_counter}" self.plate_id_counter += 1 matched_plates[new_id] = detection self.plate_positions[new_id] = bbox + self.plate_last_seen[new_id] = current_time # 设置时间戳 + # 初始化帧计数 + self.plate_frame_count[new_id] = 1 return matched_plates @@ -223,12 +209,26 @@ class PlateStabilizer: def clear_old_plates(self, current_plate_ids): """清理不再出现的车牌历史记录""" + import time + current_time = time.time() + # 移除超过一定时间未更新的车牌 plates_to_remove = [] + + # 基于时间戳清理过期的车牌(超过2秒未检测到) + for plate_id in list(self.plate_last_seen.keys()): + last_seen_time = self.plate_last_seen.get(plate_id, 0) + if current_time - last_seen_time > 2.0: # 超过2秒清理 + plates_to_remove.append(plate_id) + + # 也清理当前帧中不存在的车牌 for plate_id in self.plate_histories.keys(): if plate_id not in current_plate_ids: plates_to_remove.append(plate_id) + # 去重 + plates_to_remove = list(set(plates_to_remove)) + for plate_id in plates_to_remove: if plate_id in self.plate_histories: del self.plate_histories[plate_id] @@ -236,6 +236,10 @@ class PlateStabilizer: del self.plate_positions[plate_id] if plate_id in self.stable_results: del self.stable_results[plate_id] + if plate_id in self.plate_last_seen: + del self.plate_last_seen[plate_id] + if plate_id in self.plate_frame_count: + del self.plate_frame_count[plate_id] class CameraThread(QThread): """摄像头线程类""" @@ -478,20 +482,24 @@ class MainWindow(QMainWindow): # 添加车牌稳定器 self.plate_stabilizer = PlateStabilizer( - history_size=15, # 保存15帧历史 - confidence_threshold=0.7, # 70%置信度阈值 - stability_frames=5 # 需要5帧稳定 + history_size=25, # 保存15帧历史 + confidence_threshold=0.8, # 70%置信度阈值 + stability_frames=10 # 需要10帧稳定 ) - # 初始化道闸控制器和白名单管理器 - self.gate_controller = GateController() - self.whitelist_manager = WhitelistManager() + # 车牌记录存储 - 用于道闸控制 + self.plate_records = {} # 格式: {plate_number: {'first_time': datetime, 'sent': bool}} + + # 识别框命令发送记录 - 防止同一识别框重复发送命令 + self.frame_command_sent = {} # 格式: {plate_id: {'plate_number': str, 'command_sent': bool}} + + # 白名单存储 + self.whitelist = set() # 存储白名单车牌号 self.init_ui() self.init_detector() self.init_camera() self.init_video() - self.init_gate_control() # 初始化默认识别方法(CRNN)的模型 self.change_recognition_method(self.current_recognition_method) @@ -563,156 +571,6 @@ class MainWindow(QMainWindow): right_frame.setStyleSheet("QFrame { background-color: #fafafa; border: 2px solid #ddd; }") right_layout = QVBoxLayout(right_frame) - # 道闸控制区域 - gate_frame = QFrame() - gate_frame.setFrameStyle(QFrame.StyledPanel) - gate_frame.setStyleSheet("QFrame { background-color: #f0f8ff; border: 1px solid #b0d4f1; border-radius: 5px; }") - gate_layout = QVBoxLayout(gate_frame) - - # 道闸控制标题 - gate_title = QLabel("道闸控制") - gate_title.setAlignment(Qt.AlignCenter) - gate_title.setFont(QFont("Arial", 14, QFont.Bold)) - gate_title.setStyleSheet("QLabel { color: #1976d2; padding: 5px; }") - - # 道闸控制按钮 - gate_button_layout = QHBoxLayout() - self.open_gate_button = QPushButton("手动开闸") - self.close_gate_button = QPushButton("手动关闸") - self.open_gate_button.clicked.connect(self.manual_open_gate) - self.close_gate_button.clicked.connect(self.manual_close_gate) - - # 设置道闸按钮样式 - gate_button_style = """ - QPushButton { - background-color: #4CAF50; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #45a049; - } - QPushButton:pressed { - background-color: #3d8b40; - } - """ - self.open_gate_button.setStyleSheet(gate_button_style) - - close_button_style = """ - QPushButton { - background-color: #f44336; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #d32f2f; - } - QPushButton:pressed { - background-color: #b71c1c; - } - """ - self.close_gate_button.setStyleSheet(close_button_style) - - gate_button_layout.addWidget(self.open_gate_button) - gate_button_layout.addWidget(self.close_gate_button) - - # 白名单管理区域 - whitelist_layout = QVBoxLayout() - whitelist_label = QLabel("车牌白名单") - whitelist_label.setFont(QFont("Arial", 12, QFont.Bold)) - whitelist_label.setStyleSheet("QLabel { color: #333; padding: 5px; }") - - # 白名单按钮 - whitelist_button_layout = QHBoxLayout() - self.add_plate_button = QPushButton("添加车牌") - self.edit_plate_button = QPushButton("编辑车牌") - self.delete_plate_button = QPushButton("删除车牌") - self.add_plate_button.clicked.connect(self.add_plate_to_whitelist) - self.edit_plate_button.clicked.connect(self.edit_plate_in_whitelist) - self.delete_plate_button.clicked.connect(self.delete_plate_from_whitelist) - - # 设置白名单按钮样式 - whitelist_button_style = """ - QPushButton { - background-color: #2196F3; - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - font-weight: bold; - font-size: 11px; - } - QPushButton:hover { - background-color: #1976D2; - } - QPushButton:pressed { - background-color: #0D47A1; - } - """ - self.add_plate_button.setStyleSheet(whitelist_button_style) - self.edit_plate_button.setStyleSheet(whitelist_button_style) - self.delete_plate_button.setStyleSheet(whitelist_button_style) - - whitelist_button_layout.addWidget(self.add_plate_button) - whitelist_button_layout.addWidget(self.edit_plate_button) - whitelist_button_layout.addWidget(self.delete_plate_button) - - # 白名单列表 - self.whitelist_list = QListWidget() - self.whitelist_list.setMaximumHeight(120) - self.whitelist_list.setStyleSheet(""" - QListWidget { - border: 1px solid #ddd; - background-color: white; - border-radius: 4px; - padding: 5px; - } - QListWidget::item { - padding: 5px; - border-bottom: 1px solid #eee; - } - QListWidget::item:selected { - background-color: #e3f2fd; - color: #1976d2; - } - """) - - # 调试日志区域 - log_label = QLabel("调试日志") - log_label.setFont(QFont("Arial", 10, QFont.Bold)) - log_label.setStyleSheet("QLabel { color: #333; padding: 5px; }") - - self.log_text = QTextEdit() - self.log_text.setMaximumHeight(100) - self.log_text.setReadOnly(True) - self.log_text.setStyleSheet(""" - QTextEdit { - border: 1px solid #ddd; - background-color: #f9f9f9; - border-radius: 4px; - padding: 5px; - font-family: 'Consolas', 'Courier New', monospace; - font-size: 10px; - } - """) - - # 添加到道闸控制布局 - whitelist_layout.addWidget(whitelist_label) - whitelist_layout.addLayout(whitelist_button_layout) - whitelist_layout.addWidget(self.whitelist_list) - - gate_layout.addWidget(gate_title) - gate_layout.addLayout(gate_button_layout) - gate_layout.addLayout(whitelist_layout) - gate_layout.addWidget(log_label) - gate_layout.addWidget(self.log_text) - # 标题 title_label = QLabel("检测结果") title_label.setAlignment(Qt.AlignCenter) @@ -765,11 +623,209 @@ class MainWindow(QMainWindow): self.current_method_label.setFont(QFont("Arial", 9)) self.current_method_label.setStyleSheet("QLabel { color: #666; padding: 5px; }") - right_layout.addWidget(gate_frame) + # 白名单和手动控制区域 - 重新设计 + control_frame = QFrame() + control_frame.setFrameStyle(QFrame.StyledPanel) + control_frame.setStyleSheet(""" + QFrame { + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 10px; + } + """) + control_layout = QVBoxLayout(control_frame) + control_layout.setSpacing(12) + + # 白名单管理部分 + whitelist_group = QFrame() + whitelist_group.setStyleSheet(""" + QFrame { + background-color: white; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 8px; + } + """) + whitelist_group_layout = QVBoxLayout(whitelist_group) + whitelist_group_layout.setSpacing(8) + + # 白名单标题 + whitelist_title = QLabel("车牌白名单") + whitelist_title.setFont(QFont("Microsoft YaHei", 11, QFont.Bold)) + whitelist_title.setStyleSheet("QLabel { color: #495057; margin-bottom: 5px; }") + + # 白名单输入行 + input_row = QHBoxLayout() + input_row.setSpacing(8) + + self.whitelist_input = QLineEdit() + self.whitelist_input.setPlaceholderText("输入车牌号码") + self.whitelist_input.setStyleSheet(""" + QLineEdit { + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 12px; + background-color: white; + } + QLineEdit:focus { + border-color: #007bff; + outline: none; + } + """) + + self.add_whitelist_button = QPushButton("添加") + self.add_whitelist_button.setFixedSize(60, 32) + self.add_whitelist_button.setStyleSheet(""" + QPushButton { + background-color: #28a745; + color: white; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + QPushButton:hover { + background-color: #218838; + } + QPushButton:pressed { + background-color: #1e7e34; + } + """) + self.add_whitelist_button.clicked.connect(self.add_to_whitelist) + + self.remove_whitelist_button = QPushButton("删除") + self.remove_whitelist_button.setFixedSize(60, 32) + self.remove_whitelist_button.setStyleSheet(""" + QPushButton { + background-color: #dc3545; + color: white; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + QPushButton:hover { + background-color: #c82333; + } + QPushButton:pressed { + background-color: #bd2130; + } + """) + self.remove_whitelist_button.clicked.connect(self.remove_from_whitelist) + + input_row.addWidget(self.whitelist_input) + input_row.addWidget(self.add_whitelist_button) + input_row.addWidget(self.remove_whitelist_button) + + # 白名单列表 + self.whitelist_display = QListWidget() + self.whitelist_display.setMaximumHeight(100) + self.whitelist_display.setStyleSheet(""" + QListWidget { + border: 1px solid #e9ecef; + border-radius: 4px; + background-color: #f8f9fa; + font-size: 12px; + padding: 4px; + } + QListWidget::item { + padding: 6px 8px; + border-radius: 3px; + margin: 1px; + } + QListWidget::item:selected { + background-color: #007bff; + color: white; + } + QListWidget::item:hover { + background-color: #e9ecef; + } + """) + + whitelist_group_layout.addWidget(whitelist_title) + whitelist_group_layout.addLayout(input_row) + whitelist_group_layout.addWidget(self.whitelist_display) + + # 手动控制部分 + manual_group = QFrame() + manual_group.setStyleSheet(""" + QFrame { + background-color: white; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 8px; + } + """) + manual_group_layout = QVBoxLayout(manual_group) + manual_group_layout.setSpacing(8) + + # 手动控制标题 + manual_title = QLabel("手动控制") + manual_title.setFont(QFont("Microsoft YaHei", 11, QFont.Bold)) + manual_title.setStyleSheet("QLabel { color: #495057; margin-bottom: 5px; }") + + # 手动控制按钮行 + manual_row = QHBoxLayout() + manual_row.setSpacing(12) + + self.manual_open_button = QPushButton("开闸") + self.manual_open_button.setFixedSize(80, 36) + self.manual_open_button.setStyleSheet(""" + QPushButton { + background-color: #007bff; + color: white; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + } + QPushButton:hover { + background-color: #0056b3; + } + QPushButton:pressed { + background-color: #004085; + } + """) + self.manual_open_button.clicked.connect(self.manual_open_gate) + + self.manual_close_button = QPushButton("关闸") + self.manual_close_button.setFixedSize(80, 36) + self.manual_close_button.setStyleSheet(""" + QPushButton { + background-color: #fd7e14; + color: white; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + } + QPushButton:hover { + background-color: #e8650e; + } + QPushButton:pressed { + background-color: #d35400; + } + """) + self.manual_close_button.clicked.connect(self.manual_close_gate) + + manual_row.addWidget(self.manual_open_button) + manual_row.addWidget(self.manual_close_button) + manual_row.addStretch() + + manual_group_layout.addWidget(manual_title) + manual_group_layout.addLayout(manual_row) + + # 添加到主控制布局 + control_layout.addWidget(whitelist_group) + control_layout.addWidget(manual_group) + right_layout.addWidget(title_label) right_layout.addLayout(method_layout) right_layout.addWidget(self.count_label) right_layout.addWidget(scroll_area) + right_layout.addWidget(control_frame) right_layout.addWidget(self.current_method_label) # 添加到主布局 @@ -819,9 +875,12 @@ class MainWindow(QMainWindow): self.plate_stabilizer = PlateStabilizer( history_size=15, confidence_threshold=0.7, - stability_frames=5 + stability_frames=10 ) + # 清空识别框命令发送记录 + self.frame_command_sent = {} + # 清空右侧结果显示 self.count_label.setText("识别到的车牌数量: 0") for i in reversed(range(self.results_layout.count())): @@ -1052,7 +1111,6 @@ class MainWindow(QMainWindow): def display_frame(self, frame): """显示帧到界面""" try: - print(f"开始显示帧,帧尺寸: {frame.shape}") # 方法1: 标准方法 try: @@ -1061,7 +1119,6 @@ class MainWindow(QMainWindow): bytes_per_line = ch * w qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888) - print(f"方法1: 创建QImage,尺寸: {qt_image.width()}x{qt_image.height()}") if qt_image.isNull(): print("方法1: QImage为空,尝试方法2") raise Exception("QImage为空") @@ -1074,7 +1131,6 @@ class MainWindow(QMainWindow): # 手动缩放图片以适应标签大小,保持宽高比 scaled_pixmap = pixmap.scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.camera_label.setPixmap(scaled_pixmap) - print("方法1: 帧显示完成") return except Exception as e1: print(f"方法1失败: {str(e1)}") @@ -1144,7 +1200,6 @@ class MainWindow(QMainWindow): def update_results_display(self): """更新右侧结果显示(使用稳定化结果)""" - print(f"开始更新结果显示,当前模式: {self.current_mode}, 检测数量: {len(self.detections) if self.detections else 0}") if not self.detections: self.count_label.setText("识别到的车牌数量: 0") @@ -1154,7 +1209,6 @@ class MainWindow(QMainWindow): if child: child.setParent(None) self.last_plate_results = [] - print("无检测结果,已清空界面") return # 获取矫正图像和识别文本 @@ -1204,17 +1258,33 @@ class MainWindow(QMainWindow): # 更新存储的结果 self.last_plate_results = stable_results - - # 处理道闸控制逻辑 - for result in stable_results: - plate_number = result.get('plate_number', '') - if plate_number and plate_number != "识别失败": - # 调用道闸控制逻辑 - self.process_gate_control(plate_number) + + # 处理道闸控制逻辑 + self.handle_gate_control(stable_results) # 清理旧的车牌记录 current_plate_ids = [result['id'] for result in stable_results] self.plate_stabilizer.clear_old_plates(current_plate_ids) + + # 清理过期的识别框命令发送记录 + expired_frame_ids = [frame_id for frame_id in self.frame_command_sent.keys() + if frame_id not in current_plate_ids] + for frame_id in expired_frame_ids: + del self.frame_command_sent[frame_id] + + # 清理过期的车牌记录 - 获取当前所有稳定车牌号 + current_plate_numbers = set() + for result in stable_results: + plate_number = result.get('plate_number', '') + if plate_number and plate_number != "识别失败": + current_plate_numbers.add(plate_number) + + # 清理不再出现的车牌记录 + expired_plate_numbers = [plate_number for plate_number in self.plate_records.keys() + if plate_number not in current_plate_numbers] + for plate_number in expired_plate_numbers: + del self.plate_records[plate_number] + print("结果显示更新完成") def check_results_changed(self, new_results): @@ -1308,100 +1378,133 @@ class MainWindow(QMainWindow): if self.current_frame is not None: self.process_frame(self.current_frame) - def init_gate_control(self): - """初始化道闸控制功能""" - # 更新白名单列表显示 - self.update_whitelist_display() + def handle_gate_control(self, stable_results): - # 添加初始日志 - self.add_log("道闸控制系统已初始化") + """处理道闸控制逻辑""" + current_time = datetime.now() - # GateController的IP地址在初始化时已设置,默认为192.168.43.12 + for result in stable_results: + plate_number = result.get('plate_number', '') + plate_id = result.get('id') + + print(f"车牌ID: {plate_id}, 车牌号: {plate_number}") + + # 跳过识别失败的车牌 + if not plate_number or plate_number == "识别失败": + continue + + # 获取该车牌的帧计数,忽略最早的两帧结果 + frame_count = self.plate_stabilizer.plate_frame_count.get(plate_id, 0) + if frame_count <= 2: + print(f"车牌ID: {plate_id} 帧数: {frame_count}, 忽略前两帧") + continue + + # 检查该识别框是否已经发送过命令 + if plate_id in self.frame_command_sent: + # 如果该识别框已发送命令,跳过 + if self.frame_command_sent[plate_id]['command_sent']: + continue + else: + # 新的识别框,初始化记录 + self.frame_command_sent[plate_id] = { + 'plate_number': plate_number, + 'command_sent': False + } + + + # 检查白名单 + if plate_number not in self.whitelist: + # 非白名单车牌,发送禁行命令 + try: + message = f"{plate_number} 禁行" + send_command(4, message) + print(f"发送道闸命令: {message}") + # 标记该识别框已发送命令 + self.frame_command_sent[plate_id]['command_sent'] = True + except Exception as e: + print(f"发送道闸命令失败: {e}") + continue + + if plate_number in self.plate_records: + # 二次识别到同一车牌 + record = self.plate_records[plate_number] + if not record['sent']: + # 计算时间间隔 + 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}") + + # 标记该识别框已发送命令 + self.frame_command_sent[plate_id]['command_sent'] = True + + # 标记为已发送并清除记录,使第三次识别时重新按首次处理 + del self.plate_records[plate_number] + + except Exception as e: + print(f"发送道闸命令失败: {e}") + else: + # 首次识别到车牌 + try: + message = f"{plate_number} 通行" + send_command(1, message) + print(f"发送道闸命令: {message}") + + # 标记该识别框已发送命令 + self.frame_command_sent[plate_id]['command_sent'] = True + + # 记录车牌信息 + self.plate_records[plate_number] = { + 'first_time': current_time, + 'sent': False + } + + except Exception as e: + print(f"发送道闸命令失败: {e}") + + def add_to_whitelist(self): + """添加车牌到白名单""" + plate_number = self.whitelist_input.text().strip().upper() + if plate_number and plate_number not in self.whitelist: + self.whitelist.add(plate_number) + self.whitelist_display.addItem(plate_number) + self.whitelist_input.clear() + print(f"已添加车牌到白名单: {plate_number}") + elif plate_number in self.whitelist: + print(f"车牌已在白名单中: {plate_number}") + else: + print("请输入有效的车牌号") + + def remove_from_whitelist(self): + """从白名单中删除选中的车牌""" + current_item = self.whitelist_display.currentItem() + if current_item: + plate_number = current_item.text() + self.whitelist.discard(plate_number) + self.whitelist_display.takeItem(self.whitelist_display.row(current_item)) + print(f"已从白名单中删除车牌: {plate_number}") + else: + print("请选择要删除的车牌") def manual_open_gate(self): - """手动开闸""" - self.gate_controller.manual_open_gate() - self.add_log("手动开闸指令已发送") + """手动开闸 - 发送命令2""" + try: + send_command(2, "开闸") + print("已发送手动开闸命令") + except Exception as e: + print(f"发送开闸命令失败: {e}") def manual_close_gate(self): - """手动关闸""" - self.gate_controller.manual_close_gate() - self.add_log("手动关闸指令已发送") + """手动关闸 - 发送命令3""" + try: + send_command(3, "关闸") + print("已发送手动关闸命令") + except Exception as e: + print(f"发送关闸命令失败: {e}") - def add_plate_to_whitelist(self): - """添加车牌到白名单""" - dialog = PlateInputDialog("添加车牌", "") - if dialog.exec_() == QDialog.Accepted: - plate_number = dialog.get_plate_number() - if plate_number: - self.whitelist_manager.add_plate(plate_number) - self.update_whitelist_display() - self.add_log(f"已添加车牌到白名单: {plate_number}") - - def edit_plate_in_whitelist(self): - """编辑白名单中的车牌""" - current_item = self.whitelist_list.currentItem() - if not current_item: - QMessageBox.warning(self, "提示", "请先选择要编辑的车牌") - return - - old_plate = current_item.text() - dialog = PlateInputDialog("编辑车牌", old_plate) - if dialog.exec_() == QDialog.Accepted: - new_plate = dialog.get_plate_number() - if new_plate and new_plate != old_plate: - self.whitelist_manager.remove_plate(old_plate) - self.whitelist_manager.add_plate(new_plate) - self.update_whitelist_display() - self.add_log(f"已修改车牌: {old_plate} -> {new_plate}") - - def delete_plate_from_whitelist(self): - """从白名单中删除车牌""" - current_item = self.whitelist_list.currentItem() - if not current_item: - QMessageBox.warning(self, "提示", "请先选择要删除的车牌") - return - - plate = current_item.text() - reply = QMessageBox.question(self, "确认", f"确定要删除车牌 {plate} 吗?", - QMessageBox.Yes | QMessageBox.No) - - if reply == QMessageBox.Yes: - self.whitelist_manager.remove_plate(plate) - self.update_whitelist_display() - self.add_log(f"已从白名单删除车牌: {plate}") - - def update_whitelist_display(self): - """更新白名单列表显示""" - self.whitelist_list.clear() - for plate in self.whitelist_manager.get_whitelist(): - self.whitelist_list.addItem(plate) - - def add_log(self, message): - """添加日志消息""" - current_time = QDateTime.currentDateTime().toString("hh:mm:ss") - log_message = f"[{current_time}] {message}" - self.log_text.append(log_message) - # 限制日志行数,避免内存占用过多 - if self.log_text.document().blockCount() > 100: - cursor = self.log_text.textCursor() - cursor.movePosition(cursor.Start) - cursor.select(cursor.BlockUnderCursor) - cursor.removeSelectedText() - cursor.deleteChar() # 删除换行符 - - def process_gate_control(self, plate_number): - """处理道闸控制逻辑""" - # 检查车牌是否在白名单中 - if self.whitelist_manager.is_whitelisted(plate_number): - # 使用GateController的auto_open_gate方法,它会自动处理时间差 - self.gate_controller.auto_open_gate(plate_number) - self.add_log(f"车牌 {plate_number} 验证通过,已发送开闸指令") - else: - # 不在白名单中,发送禁行指令 - self.gate_controller.deny_access(plate_number) - self.add_log(f"车牌 {plate_number} 不在白名单中,已发送禁行指令") - def closeEvent(self, event): """窗口关闭事件""" if self.camera_thread and self.camera_thread.running: diff --git a/simple_client.py b/simple_client.py deleted file mode 100644 index 65f85d5..0000000 --- a/simple_client.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -简单的UDP客户端程序 -向Hi3861设备发送JSON命令 -""" - -import socket -import json -import time - -def send_command(): - """发送命令到Hi3861设备""" - # 目标设备信息 - target_ip = "192.168.43.12" - target_port = 8081 - - #cmd为1,道闸打开十秒后关闭,oled显示字符串信息(默认使用及cmd为4) - #cmd为2,道闸舵机向打开方向旋转90度,oled上不显示(仅在qt界面手动开闸时调用) - #cmd为3,道闸舵机向关闭方向旋转90度,oled上不显示(仅在qt界面手动关闸时调用) - #cmd为4,oled显示字符串信息,道闸舵机不旋转 - - # 创建JSON命令 - command = { - "cmd": 1, - "text": "沪AAAAAA 通行" - } - - json_command = json.dumps(command, ensure_ascii=False) - - try: - # 创建UDP socket - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - # 发送命令 - print(f"正在向 {target_ip}:{target_port} 发送命令...") - print(f"命令内容: {json_command}") - - sock.sendto(json_command.encode('utf-8'), (target_ip, target_port)) - - print("命令发送成功!") - print("设备将执行以下操作:") - print("1. 顺时针旋转舵机90度") - print("2. 在OLED屏幕上显示:沪AAAAAA") - print("3. 等待10秒") - print("4. 逆时针旋转舵机90度") - print("5. 清空OLED屏幕") - - except Exception as e: - print(f"发送命令失败: {e}") - finally: - sock.close() - -if __name__ == "__main__": - print("Hi3861 简单客户端程序") - print("=" * 30) - send_command() - print("程序结束") \ No newline at end of file diff --git a/test_lpr_real_images.py b/test_lpr_real_images.py deleted file mode 100644 index ce32954..0000000 --- a/test_lpr_real_images.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -LPRNet接口真实图片测试脚本 -测试LPRNET_part目录下的真实车牌图片 -""" - -import cv2 -import numpy as np -import os -from LPRNET_part.lpr_interface import LPRNinitialize_model, LPRNmodel_predict - -def test_real_images(): - """ - 测试LPRNET_part目录下的真实车牌图片 - """ - print("=== LPRNet真实图片测试 ===") - - # 初始化模型 - print("1. 初始化LPRNet模型...") - success = LPRNinitialize_model() - if not success: - print("模型初始化失败!") - return - - # 获取LPRNET_part目录下的图片文件 - lprnet_dir = "LPRNET_part" - image_files = [] - - if os.path.exists(lprnet_dir): - for file in os.listdir(lprnet_dir): - if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')): - image_files.append(os.path.join(lprnet_dir, file)) - - if not image_files: - print("未找到图片文件!") - return - - print(f"2. 找到 {len(image_files)} 个图片文件") - - # 测试每个图片 - for i, image_path in enumerate(image_files, 1): - print(f"\n--- 测试图片 {i}: {os.path.basename(image_path)} ---") - - try: - # 使用支持中文路径的方式读取图片 - image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR) - - if image is None: - print(f"无法读取图片: {image_path}") - continue - - print(f"图片尺寸: {image.shape}") - - # 进行预测 - result = LPRNmodel_predict(image) - print(f"识别结果: {result}") - print(f"识别车牌号: {''.join(result)}") - - except Exception as e: - print(f"处理图片 {image_path} 时出错: {e}") - import traceback - traceback.print_exc() - - print("\n=== 测试完成 ===") - -def test_image_loading(): - """ - 测试图片加载方式 - """ - print("\n=== 图片加载测试 ===") - - lprnet_dir = "LPRNET_part" - - if os.path.exists(lprnet_dir): - for file in os.listdir(lprnet_dir): - if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')): - image_path = os.path.join(lprnet_dir, file) - print(f"\n测试文件: {file}") - - # 方法1: 普通cv2.imread - img1 = cv2.imread(image_path) - print(f"cv2.imread结果: {img1 is not None}") - - # 方法2: 支持中文路径的方式 - try: - img2 = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR) - print(f"cv2.imdecode结果: {img2 is not None}") - if img2 is not None: - print(f"图片尺寸: {img2.shape}") - except Exception as e: - print(f"cv2.imdecode失败: {e}") - -if __name__ == "__main__": - # 首先测试图片加载 - test_image_loading() - - # 然后测试完整的识别流程 - test_real_images() \ No newline at end of file diff --git a/yolopart/__pycache__/detector.cpython-39.pyc b/yolopart/__pycache__/detector.cpython-39.pyc new file mode 100644 index 0000000..abb6248 Binary files /dev/null and b/yolopart/__pycache__/detector.cpython-39.pyc differ diff --git a/yolopart/detector.py b/yolopart/detector.py index 435c4fb..8f33e15 100644 --- a/yolopart/detector.py +++ b/yolopart/detector.py @@ -46,7 +46,7 @@ class LicensePlateYOLO: print(f"YOLO模型加载失败: {e}") return False - def detect_license_plates(self, image, conf_threshold=0.5): + def detect_license_plates(self, image, conf_threshold=0.6): """ 检测图像中的车牌