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):
"""
检测图像中的车牌