道闸管理上线

This commit is contained in:
2025-10-19 18:03:57 +08:00
parent 6831a8cd01
commit d649738f6c
18 changed files with 469 additions and 502 deletions

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="cnm" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="pytorh" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="cnm" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/License_plate_recognition.iml" filepath="$PROJECT_DIR$/.idea/License_plate_recognition.iml" />
</modules>
</component>
</project>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

69
communicate.py Normal file
View File

@@ -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为4oled显示字符串信息道闸舵机不旋转
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()

693
main.py
View File

@@ -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()
@@ -96,8 +71,13 @@ class PlateStabilizer:
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
# 获取矫正图像和识别文本
@@ -1205,16 +1259,32 @@ 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,99 +1378,132 @@ 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("手动关闸指令已发送")
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} 不在白名单中,已发送禁行指令")
"""手动关闸 - 发送命令3"""
try:
send_command(3, "关闸")
print("已发送手动关闸命令")
except Exception as e:
print(f"发送关闸命令失败: {e}")
def closeEvent(self, event):
"""窗口关闭事件"""

View File

@@ -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为4oled显示字符串信息道闸舵机不旋转
# 创建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("程序结束")

View File

@@ -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()

Binary file not shown.

View File

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